├── .gitignore ├── LICENSE ├── Makefile ├── README.md └── server.go /.gitignore: -------------------------------------------------------------------------------- 1 | test.db 2 | boltdb-server 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Skye Cove 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: clean test install 2 | 3 | boltdb-server: 4 | go build 5 | 6 | test: 7 | go test 8 | 9 | install: 10 | go install 11 | 12 | clean: 13 | rm test.db boltdb-server 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BoltDB HTTP interface 2 | 3 | An HTTP wrapper around a BoltDB database. 4 | 5 | Inspired by this StackOverflow question: 6 | http://stackoverflow.com/questions/30707208/boltdb-key-value-data-store-purely-in-go 7 | 8 | ## Usage 9 | 10 | `` 11 | boltdb-server -l -db 12 | `` 13 | 14 | ## API 15 | 16 | ``` 17 | GET /v1/buckets/{bucket-name}/keys/{key-name} 18 | POST /v1/buckets/{bucket-name}/keys/{key-name} 19 | PUT /v1/buckets/{bucket-name}/keys/{key-name} 20 | ``` 21 | 22 | Buckets are automatically created if they don't already exist. 23 | 24 | Set the `Content-Type` header on a PUT/POST and it will be reused when GETting the key. 25 | 26 | ## Examples 27 | 28 | ``` 29 | $ curl -X PUT -H 'Content-Type: text/plain' -d 'asdf' localhost:9988/v1/buckets/test/keys/test 30 | $ curl localhost:9988/v1/buckets/test/keys/test 31 | asdf 32 | $ curl -X PUT -H 'Content-Type: application/json' -d '{"test":"me"}' localhost:9988/v1/buckets/test/keys/test2 33 | $ curl -v localhost:9988/v1/buckets/test/keys/test2 34 | * Hostname was NOT found in DNS cache 35 | * Trying ::1... 36 | * Connected to localhost (::1) port 9988 (#0) 37 | > GET /v1/buckets/test/keys/test2 HTTP/1.1 38 | > User-Agent: curl/7.37.1 39 | > Host: localhost:9988 40 | > Accept: */* 41 | > 42 | < HTTP/1.1 200 OK 43 | < Content-Type: application/json 44 | < Date: Mon, 08 Jun 2015 14:48:23 GMT 45 | < Content-Length: 13 46 | < 47 | * Connection #0 to host localhost left intact 48 | {"test":"me"} 49 | 50 | ``` 51 | 52 | ## TODO 53 | 54 | * Tests 55 | * Logging 56 | * Input validation 57 | * Delete keys and buckets 58 | * Key iteration, range and prefix scans 59 | 60 | 61 | -------------------------------------------------------------------------------- /server.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "flag" 5 | "fmt" 6 | "io/ioutil" 7 | "log" 8 | "net/http" 9 | "time" 10 | 11 | "github.com/boltdb/bolt" 12 | "github.com/gorilla/mux" 13 | ) 14 | 15 | type server struct { 16 | db *bolt.DB 17 | } 18 | 19 | func newServer(filename string) (s *server, err error) { 20 | s = &server{} 21 | s.db, err = bolt.Open(filename, 0600, &bolt.Options{Timeout: 1 * time.Second}) 22 | return 23 | } 24 | 25 | func (s *server) Put(bucket, key, contentType string, val []byte) error { 26 | return s.db.Update(func(tx *bolt.Tx) error { 27 | b, err := tx.CreateBucketIfNotExists([]byte(bucket)) 28 | if err != nil { 29 | return err 30 | } 31 | if err = b.Put([]byte(key), val); err != nil { 32 | return err 33 | } 34 | return b.Put([]byte(fmt.Sprintf("%s-ContentType", key)), []byte(contentType)) 35 | }) 36 | } 37 | 38 | func (s *server) Get(bucket, key string) (ct string, data []byte, err error) { 39 | s.db.View(func(tx *bolt.Tx) error { 40 | b := tx.Bucket([]byte(bucket)) 41 | r := b.Get([]byte(key)) 42 | if r != nil { 43 | data = make([]byte, len(r)) 44 | copy(data, r) 45 | } 46 | 47 | r = b.Get([]byte(fmt.Sprintf("%s-ContentType", key))) 48 | ct = string(r) 49 | return nil 50 | }) 51 | return 52 | } 53 | 54 | func (s *server) ServeHTTP(w http.ResponseWriter, r *http.Request) { 55 | vars := mux.Vars(r) 56 | 57 | if vars["bucket"] == "" || vars["key"] == "" { 58 | http.Error(w, "Missing bucket or key", http.StatusBadRequest) 59 | return 60 | } 61 | 62 | switch r.Method { 63 | case "POST", "PUT": 64 | data, err := ioutil.ReadAll(r.Body) 65 | if err != nil { 66 | http.Error(w, err.Error(), http.StatusInternalServerError) 67 | return 68 | } 69 | err = s.Put(vars["bucket"], vars["key"], r.Header.Get("Content-Type"), data) 70 | w.WriteHeader(http.StatusOK) 71 | case "GET": 72 | ct, data, err := s.Get(vars["bucket"], vars["key"]) 73 | if err != nil { 74 | http.Error(w, err.Error(), http.StatusInternalServerError) 75 | return 76 | } 77 | w.Header().Add("Content-Type", ct) 78 | w.Write(data) 79 | } 80 | } 81 | 82 | func main() { 83 | var ( 84 | addr string 85 | dbfile string 86 | ) 87 | 88 | flag.StringVar(&addr, "l", ":9988", "Address to listen on") 89 | flag.StringVar(&dbfile, "db", "/var/data/bolt.db", "Bolt DB file") 90 | flag.Parse() 91 | 92 | log.Println("Using Bolt DB file:", dbfile) 93 | log.Println("Listening on:", addr) 94 | 95 | server, err := newServer(dbfile) 96 | if err != nil { 97 | log.Fatalf("Error: %s", err) 98 | } 99 | 100 | router := mux.NewRouter() 101 | router.Handle("/v1/buckets/{bucket}/keys/{key}", server) 102 | http.Handle("/", router) 103 | 104 | log.Fatal(http.ListenAndServe(addr, nil)) 105 | } 106 | --------------------------------------------------------------------------------