├── .gitignore
├── .travis.yml
├── LICENSE
├── README.md
├── connect
├── connect.go
└── connect_test.go
├── db.go
├── goreleaser.yml
├── logo.png
├── main.go
└── utils.go
/.gitignore:
--------------------------------------------------------------------------------
1 | # Compiled Object files, Static and Dynamic libs (Shared Objects)
2 | *.o
3 | *.a
4 | *.so
5 |
6 | # Folders
7 | _obj
8 | _test
9 |
10 | # Architecture specific extensions/prefixes
11 | *.[568vq]
12 | [568vq].out
13 |
14 | *.cgo1.go
15 | *.cgo2.c
16 | _cgo_defun.c
17 | _cgo_gotypes.go
18 | _cgo_export.*
19 |
20 | _testmain.go
21 |
22 | *.exe
23 | *.test
24 | *.prof
25 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: go
2 |
3 | script:
4 | - go get github.com/schollz/boltdb-server/...
5 | - cd $GOPATH/src/github.com/schollz/boltdb-server && go build
6 | - cd $GOPATH/src/github.com/schollz/boltdb-server && ./boltdb-server &
7 | - cd $GOPATH/src/github.com/schollz/boltdb-server/connect && go test
8 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Zack
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 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | A fancy server for Bolt databases
12 |
13 | *boltdb-server* is a server and package (`connect`) for remotely interfacing
14 | with [boltdb/bolt](https://github.com/boltdb/bolt), a pure-Go embedded key/value database.
15 |
16 | Features
17 | ========
18 |
19 | - Automatic compression of values
20 | - Simple API for getting, setting, moving, popping and deleting BoltDB data
21 | - Package for adding to your Go programs
22 |
23 | Getting Started
24 | ===============
25 |
26 | ## Installing
27 |
28 | To start using the server, install Go and run `go get`:
29 |
30 | ```sh
31 | $ go get -u github.com/schollz/boltdb-server/...
32 | ```
33 |
34 | This will retrieve the library and the server.
35 |
36 | ## Run
37 |
38 | Run the server using
39 |
40 | ```sh
41 | $GOPATH/bin/boltdb-server
42 | ```
43 |
44 | Then you can use the server directly (see API below) or plug in a Go program using the connect package, [see tests for more info](https://github.com/schollz/boltdb-server/blob/master/connect/connect_test.go).
45 |
46 | ## API
47 |
48 | ```
49 | // Get map of buckets and the number of keys in each
50 | GET /v1/db//stats
51 |
52 | // Get list of all buckets
53 | GET /v1/db//buckets
54 |
55 | // Get all keys and values from a bucket
56 | GET /v1/db//bucket//numkeys
57 |
58 | // Get all keys and values from a bucket
59 | GET /v1/db//bucket//all
60 |
61 | // Get all keys and values specified by ?keys=key1,key2 or by JSON
62 | GET /v1/db//bucket//some
63 |
64 | // Delete and return first n keys
65 | GET /v1/db//bucket//pop?n=X
66 |
67 | // Get all keys in a bucket
68 | GET /v1/db//bucket//keys", handleGetKeys)
69 |
70 | // Return boolean of whether it has key
71 | GET /v1/db//bucket//haskey/
72 |
73 | // Return boolean of whether any buckets contain any keys specified by JSON
74 | GET /v1/db//haskeys
75 |
76 | // Delete database file
77 | DELETE /v1/db/
78 |
79 | // Delete bucket
80 | DELETE /v1/db//bucket/
81 |
82 | // Delete keys, where keys are specified by JSON []string
83 | DELETE /v1/db//bucket//keys
84 |
85 | // Updates a database with keystore specified by JSON
86 | POST /v1/db//bucket//update
87 |
88 | // Move keys, with buckets and keys specified by JSON
89 | POST /v1/db//move
90 |
91 | // Create buckets specified by JSON
92 | POST /v1/db//create
93 | ```
94 |
--------------------------------------------------------------------------------
/connect/connect.go:
--------------------------------------------------------------------------------
1 | // Package connect provides functionality for directly accessing a BoltDB server
2 | // instance with simple functions for posting data, getting data (such as keys and buckets),
3 | // moving data, deleting data, and popping data.
4 | //
5 | // To use, make sure that you have a boltdb-server up and running which you can do simply
6 | // with
7 | //
8 | // go get github.com/schollz/boltdb-server
9 | // $GOPATH/bin/boltdb-server
10 | //
11 | package connect
12 |
13 | import (
14 | "bytes"
15 | "encoding/json"
16 | "errors"
17 | "fmt"
18 | "net/http"
19 | )
20 |
21 | // Connection is the BoltDB server instance
22 | type Connection struct {
23 | DBName string
24 | Address string
25 | }
26 |
27 | // Open will load a connection to BoltDB
28 | func Open(address, dbname string) (*Connection, error) {
29 | c := new(Connection)
30 | c.Address = address
31 | c.DBName = dbname
32 | resp, err := http.Get(c.Address + "/v1/uptime")
33 | if err != nil {
34 | return c, err
35 | }
36 | defer resp.Body.Close()
37 | return c, nil
38 | }
39 |
40 | // DeleteDatabase deletes the database
41 | func (c *Connection) DeleteDatabase() error {
42 | req, err := http.NewRequest("DELETE", c.Address+"/v1/db/"+c.DBName, nil)
43 | if err != nil {
44 | return err
45 | }
46 |
47 | resp, err := http.DefaultClient.Do(req)
48 | if err != nil {
49 | return err
50 | }
51 | defer resp.Body.Close()
52 | return nil
53 | }
54 |
55 | // CreateBuckets inserts some buckets into the DB
56 | func (c *Connection) CreateBuckets(buckets []string) error {
57 | payloadBytes, err := json.Marshal(buckets)
58 | if err != nil {
59 | return err
60 | }
61 | body := bytes.NewReader(payloadBytes)
62 |
63 | req, err := http.NewRequest("POST", c.Address+"/v1/db/"+c.DBName+"/create", body)
64 | if err != nil {
65 | return err
66 | }
67 | req.Header.Set("Content-Type", "application/json")
68 |
69 | resp, err := http.DefaultClient.Do(req)
70 | if err != nil {
71 | return err
72 | }
73 | defer resp.Body.Close()
74 | return nil
75 | }
76 |
77 | // Post keys and values to database
78 | func (c *Connection) Post(bucket string, keystore map[string]string) error {
79 | payloadBytes, err := json.Marshal(keystore)
80 | if err != nil {
81 | return err
82 | }
83 | body := bytes.NewReader(payloadBytes)
84 |
85 | req, err := http.NewRequest("POST", c.Address+"/v1/db/"+c.DBName+"/bucket/"+bucket+"/update", body)
86 | if err != nil {
87 | return err
88 | }
89 | req.Header.Set("Content-Type", "application/json")
90 |
91 | resp, err := http.DefaultClient.Do(req)
92 | if err != nil {
93 | return err
94 | }
95 | defer resp.Body.Close()
96 | return nil
97 | }
98 |
99 | // Get keys and values from database
100 | func (c *Connection) Get(bucket string, keys []string) (map[string]string, error) {
101 | payloadBytes, err := json.Marshal(keys)
102 | if err != nil {
103 | return make(map[string]string), err
104 | }
105 | body := bytes.NewReader(payloadBytes)
106 |
107 | req, err := http.NewRequest("GET", c.Address+"/v1/db/"+c.DBName+"/bucket/"+bucket+"/some", body)
108 | if err != nil {
109 | return make(map[string]string), err
110 | }
111 | req.Header.Set("Content-Type", "application/json")
112 |
113 | resp, err := http.DefaultClient.Do(req)
114 | if err != nil {
115 | return make(map[string]string), err
116 | }
117 | defer resp.Body.Close()
118 |
119 | var target map[string]string
120 | err = json.NewDecoder(resp.Body).Decode(&target)
121 | if err != nil {
122 | return make(map[string]string), err
123 | }
124 | return target, nil
125 | }
126 |
127 | // GetAll keys and values from database
128 | func (c *Connection) GetAll(bucket string) (map[string]string, error) {
129 | resp, err := http.Get(c.Address + "/v1/db/" + c.DBName + "/bucket/" + bucket + "/all")
130 | if err != nil {
131 | return make(map[string]string), err
132 | }
133 | defer resp.Body.Close()
134 |
135 | var target map[string]string
136 | err = json.NewDecoder(resp.Body).Decode(&target)
137 | if err != nil {
138 | return make(map[string]string), err
139 | }
140 | return target, nil
141 | }
142 |
143 | // GetKeys returns all keys from database
144 | func (c *Connection) GetKeys(bucket string) ([]string, error) {
145 | resp, err := http.Get(c.Address + "/v1/db/" + c.DBName + "/bucket/" + bucket + "/keys")
146 | if err != nil {
147 | return []string{}, err
148 | }
149 | defer resp.Body.Close()
150 |
151 | var target []string
152 | err = json.NewDecoder(resp.Body).Decode(&target)
153 | return target, err
154 | }
155 |
156 | // Pop returns and deletes the first n keys from a bucket
157 | func (c *Connection) Pop(bucket string, n int) (keystore map[string]string, err error) {
158 | resp, err := http.Get(fmt.Sprintf("%s/v1/db/%s/bucket/%s/pop?n=%d", c.Address, c.DBName, bucket, n))
159 | if err != nil {
160 | return keystore, err
161 | }
162 | defer resp.Body.Close()
163 |
164 | err = json.NewDecoder(resp.Body).Decode(&keystore)
165 | return keystore, err
166 | }
167 |
168 | // HasKey checks whether a key exists, or not, in a bucket
169 | func (c *Connection) HasKey(bucket string, key string) (doesHaveKey bool, err error) {
170 | doesHaveKey = false
171 | resp, err := http.Get(fmt.Sprintf("%s/v1/db/%s/bucket/%s/haskey/%s", c.Address, c.DBName, bucket, key))
172 | if err != nil {
173 | return doesHaveKey, err
174 | }
175 | defer resp.Body.Close()
176 |
177 | err = json.NewDecoder(resp.Body).Decode(&doesHaveKey)
178 | return doesHaveKey, err
179 | }
180 |
181 | // HasKeys checks whether any of the specified keys exist in any buckets.
182 | // Returns a map of the keys and a boolean of whether they are found.
183 | func (c *Connection) HasKeys(buckets []string, keys []string) (doesHaveKeyMap map[string]bool, err error) {
184 | type QueryJSON struct {
185 | Buckets []string `json:"buckets"`
186 | Keys []string `json:"keys"`
187 | }
188 | payloadJSON := new(QueryJSON)
189 | payloadJSON.Buckets = buckets
190 | payloadJSON.Keys = keys
191 |
192 | payloadBytes, err := json.Marshal(payloadJSON)
193 | if err != nil {
194 | return doesHaveKeyMap, err
195 | }
196 | body := bytes.NewReader(payloadBytes)
197 |
198 | req, err := http.NewRequest("GET", fmt.Sprintf("%s/v1/db/%s/haskeys", c.Address, c.DBName), body)
199 | if err != nil {
200 | return doesHaveKeyMap, err
201 | }
202 | req.Header.Set("Content-Type", "application/json")
203 |
204 | resp, err := http.DefaultClient.Do(req)
205 | if err != nil {
206 | return doesHaveKeyMap, err
207 | }
208 | defer resp.Body.Close()
209 |
210 | err = json.NewDecoder(resp.Body).Decode(&doesHaveKeyMap)
211 | return doesHaveKeyMap, err
212 | }
213 |
214 | // Move moves a list of keys from one bucket to another. This function will
215 | // create the second bucket if it does not exist.
216 | func (c *Connection) Move(bucket string, bucket2 string, keys []string) (err error) {
217 | type QueryJSON struct {
218 | FromBucket string `json:"from_bucket"`
219 | ToBucket string `json:"to_bucket"`
220 | Keys []string `json:"keys"`
221 | }
222 | moveJSON := new(QueryJSON)
223 | moveJSON.FromBucket = bucket
224 | moveJSON.ToBucket = bucket2
225 | moveJSON.Keys = keys
226 |
227 | payloadBytes, err := json.Marshal(moveJSON)
228 | if err != nil {
229 | return err
230 | }
231 | body := bytes.NewReader(payloadBytes)
232 |
233 | req, err := http.NewRequest("POST", fmt.Sprintf("%s/v1/db/%s/move", c.Address, c.DBName), body)
234 | if err != nil {
235 | return err
236 | }
237 | req.Header.Set("Content-Type", "application/json")
238 |
239 | resp, err := http.DefaultClient.Do(req)
240 | if err != nil {
241 | return err
242 | }
243 | defer resp.Body.Close()
244 | return nil
245 | }
246 |
247 | // Stats returns a list of buckets and number of keys in each
248 | func (c *Connection) Stats() (stats map[string]int, err error) {
249 | resp, err := http.Get(fmt.Sprintf("%s/v1/db/%s/stats", c.Address, c.DBName))
250 | if err != nil {
251 | return stats, err
252 | }
253 | defer resp.Body.Close()
254 | if resp.StatusCode == 200 {
255 | err = json.NewDecoder(resp.Body).Decode(&stats)
256 | } else {
257 | err = errors.New("Problem getting stats")
258 | }
259 | return stats, err
260 | }
261 |
--------------------------------------------------------------------------------
/connect/connect_test.go:
--------------------------------------------------------------------------------
1 | package connect
2 |
3 | import (
4 | "fmt"
5 | "os"
6 | "path"
7 | "strconv"
8 | "testing"
9 | )
10 |
11 | // Start server with
12 | // go build; .\boltdb-server.exe
13 | var testingServer = "http://localhost:8050"
14 |
15 | func BenchmarkPost1(b *testing.B) {
16 | os.Remove(path.Join("..", "dbs", "testbench.db"))
17 | conn, _ := Open(testingServer, "testbench")
18 | b.ResetTimer()
19 | for i := 0; i < b.N; i++ {
20 | m := map[string]string{"key" + strconv.Itoa(i): "value" + strconv.Itoa(i)}
21 | conn.Post("benchkeys", m)
22 | }
23 | }
24 |
25 | func BenchmarkGetAll1(b *testing.B) {
26 | conn, _ := Open(testingServer, "testbench")
27 | b.ResetTimer()
28 | for i := 0; i < b.N; i++ {
29 | conn.GetAll("benchkeys")
30 | }
31 | }
32 |
33 | func BenchmarkPost1000(b *testing.B) {
34 | conn, _ := Open(testingServer, "testbench")
35 | m := make(map[string]string)
36 | for i := 0; i < 1000; i++ {
37 | m["key"+strconv.Itoa(i)] = "value" + strconv.Itoa(i)
38 | }
39 | b.ResetTimer()
40 | for i := 0; i < b.N; i++ {
41 | conn.Post("benchkeys", m)
42 | }
43 | }
44 |
45 | func BenchmarkGetAll1000(b *testing.B) {
46 | conn, _ := Open(testingServer, "testbench")
47 | b.ResetTimer()
48 | for i := 0; i < b.N; i++ {
49 | conn.GetAll("benchkeys")
50 | }
51 | }
52 |
53 | func BenchmarkPost100000(b *testing.B) {
54 | conn, _ := Open(testingServer, "testbench")
55 | m := make(map[string]string)
56 | for i := 0; i < 100000; i++ {
57 | m["key"+strconv.Itoa(i)] = "value" + strconv.Itoa(i)
58 | }
59 | b.ResetTimer()
60 | for i := 0; i < b.N; i++ {
61 | conn.Post("benchkeys", m)
62 | }
63 | }
64 |
65 | func BenchmarkGetAll100000(b *testing.B) {
66 | conn, _ := Open(testingServer, "testbench")
67 | b.ResetTimer()
68 | for i := 0; i < b.N; i++ {
69 | conn.GetAll("benchkeys")
70 | }
71 | }
72 |
73 | func BenchmarkGetStats100000(b *testing.B) {
74 | conn, _ := Open(testingServer, "testbench")
75 | b.ResetTimer()
76 | for i := 0; i < b.N; i++ {
77 | conn.Stats()
78 | }
79 | }
80 |
81 | func BenchmarkGetKeys(b *testing.B) {
82 | conn, _ := Open(testingServer, "testbench")
83 | b.ResetTimer()
84 | for i := 0; i < b.N; i++ {
85 | conn.GetKeys("benchkeys")
86 | }
87 | }
88 |
89 | func BenchmarkGetTwo(b *testing.B) {
90 | conn, _ := Open(testingServer, "testbench")
91 | b.ResetTimer()
92 | for i := 0; i < b.N; i++ {
93 | conn.Get("benchkeys", []string{"key1", "key20"})
94 | }
95 | }
96 |
97 | func BenchmarkHasKey(b *testing.B) {
98 | conn, _ := Open(testingServer, "testbench")
99 | b.ResetTimer()
100 | for i := 0; i < b.N; i++ {
101 | conn.HasKey("benchkeys", "key1")
102 | }
103 | }
104 |
105 | func TestGeneral(t *testing.T) {
106 | // Test opening DB that doesnt exist
107 | conn, err := Open("http://asdkfjalsjdflkasjdf", "testdb")
108 | if err == nil {
109 | t.Errorf("Should not be able to connect")
110 | }
111 |
112 | // Test opening DB
113 | conn, err = Open(testingServer, "testdb")
114 | if err != nil {
115 | t.Errorf(err.Error())
116 | }
117 |
118 | err = conn.CreateBuckets([]string{"people_locations"})
119 | if err != nil {
120 | t.Errorf(err.Error())
121 | }
122 |
123 | data := make(map[string]string)
124 | data["zack"] = "canada"
125 | data["jessie"] = "usa"
126 | err = conn.Post("people_locations", data)
127 | if err != nil {
128 | t.Errorf(err.Error())
129 | }
130 | if _, err := os.Stat(path.Join("..", "dbs", "testdb.db")); os.IsNotExist(err) {
131 | t.Errorf("Problem creating directory")
132 | }
133 |
134 | // Test HasKeys
135 | data["bob"] = "brazil"
136 | data["jill"] = "antarctica"
137 | err = conn.Post("people_locations3", data)
138 | hasKeysMap, err := conn.HasKeys([]string{"people_locations", "people_locations3"}, []string{"zack", "jessie", "bob", "jim"})
139 | if err != nil {
140 | t.Errorf(err.Error())
141 | }
142 | if hasKeysMap["jim"] != false || hasKeysMap["bob"] != true || len(hasKeysMap) != 4 {
143 | t.Errorf("Problem checking whether buckets have keys")
144 | }
145 |
146 | // Test HasKey
147 | hasKey, err := conn.HasKey("people_locations", "zack")
148 | if err != nil {
149 | t.Errorf(err.Error())
150 | }
151 | if hasKey == false {
152 | t.Errorf("Incorrectly checking whether key exists")
153 | }
154 | hasKey, err = conn.HasKey("people_locations", "askjdflasjdlkfj")
155 | if err != nil {
156 | t.Errorf(err.Error())
157 | }
158 | if hasKey != false {
159 | t.Errorf("Incorrectly checking whether key exists")
160 | }
161 |
162 | data2, err := conn.GetAll("people_locations")
163 | if err != nil {
164 | t.Errorf(err.Error())
165 | }
166 | if val, ok := data2["zack"]; ok {
167 | if val != "canada" {
168 | t.Errorf("Could not get zack back: %v", val)
169 | }
170 | } else {
171 | t.Errorf("Problem with GetAll: %v", data2)
172 | }
173 |
174 | keys, err := conn.GetKeys("people_locations")
175 | if err != nil {
176 | t.Errorf(err.Error())
177 | }
178 | if len(keys) != 2 {
179 | fmt.Println(keys)
180 | t.Errorf("Problem getting the two keys back")
181 | }
182 |
183 | // Test Pop
184 | keystore, err := conn.Pop("people_locations", 1)
185 | if err != nil {
186 | t.Errorf(err.Error())
187 | }
188 | if val, ok := keystore["jessie"]; ok {
189 | if val != "usa" {
190 | t.Errorf("Could not get jessie back")
191 | }
192 | } else {
193 | t.Errorf("Problem with Pop: %v", keystore)
194 | }
195 | keys, err = conn.GetKeys("people_locations")
196 | if err != nil {
197 | t.Errorf(err.Error())
198 | }
199 | if len(keys) != 1 {
200 | fmt.Println(keys)
201 | t.Errorf("Problem getting the one keys back")
202 | }
203 |
204 | // Test Move
205 | keys, _ = conn.GetKeys("people_locations")
206 | err = conn.Move("people_locations", "people_locations2", keys)
207 | if err != nil {
208 | t.Errorf(err.Error())
209 | }
210 | keys, err = conn.GetKeys("people_locations2")
211 | if err != nil {
212 | t.Errorf(err.Error())
213 | }
214 | if len(keys) != 1 {
215 | t.Errorf("Problem getting the one keys back")
216 | }
217 | keys, err = conn.GetKeys("people_locations")
218 | if err != nil {
219 | t.Errorf(err.Error())
220 | }
221 | if len(keys) != 0 {
222 | t.Errorf("Problem getting the one keys back")
223 | }
224 |
225 | // Test getting bucket that doesn't exist
226 | _, err = conn.GetKeys("asldkfjaslkdjf")
227 | if err == nil {
228 | t.Errorf("Should throw error, bucket does not exist")
229 | }
230 |
231 | // Test getting stats
232 | stats, err := conn.Stats()
233 | if err != nil {
234 | t.Error(err)
235 | }
236 | if stats["people_locations3"] != 4 {
237 | t.Errorf("Problem with getting stats: %v", stats)
238 | }
239 |
240 | // Test deleting database
241 | err = conn.DeleteDatabase()
242 | if err != nil {
243 | t.Error(err)
244 | }
245 | if _, err := os.Stat(path.Join("..", "dbs", "testdb.db")); os.IsExist(err) {
246 | t.Errorf("Problem deleting db")
247 | }
248 | }
249 |
--------------------------------------------------------------------------------
/db.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "errors"
5 | "io/ioutil"
6 | "os"
7 | "path"
8 | "sync"
9 | "time"
10 |
11 | "github.com/boltdb/bolt"
12 | )
13 |
14 | var dbs = struct {
15 | sync.RWMutex
16 | data map[string]*DBData
17 | }{data: make(map[string]*DBData)}
18 |
19 | type DBData struct {
20 | lastEdited time.Time
21 | db *bolt.DB
22 | }
23 |
24 | func init() {
25 | go closeDBs()
26 | }
27 |
28 | func getDB(dbname string) (*bolt.DB, error) {
29 | dbs.Lock()
30 | defer dbs.Unlock()
31 | var err error
32 | if _, ok := dbs.data[dbname]; !ok {
33 | log.Debug("Opening %s", dbname)
34 | tempDB, err2 := bolt.Open(path.Join(dbpath, dbname+".db"), 0755, nil)
35 | dbs.data[dbname] = new(DBData)
36 | dbs.data[dbname].db = tempDB
37 | err = err2
38 | }
39 | dbs.data[dbname].lastEdited = time.Now()
40 | db := dbs.data[dbname].db
41 | return db, err
42 | }
43 |
44 | func closeDBs() {
45 | for {
46 | time.Sleep(10 * time.Second)
47 | dbs.Lock()
48 | toDelete := []string{}
49 | for dbname := range dbs.data {
50 | if time.Since(dbs.data[dbname].lastEdited).Seconds() > 10 {
51 | toDelete = append(toDelete, dbname)
52 | }
53 | }
54 | dbs.Unlock()
55 |
56 | for _, dbname := range toDelete {
57 | log.Debug("Closing %s", dbname)
58 | deleteDB(dbname)
59 | }
60 | }
61 | }
62 |
63 | func deleteDB(dbname string) {
64 | dbs.Lock()
65 | if _, ok := dbs.data[dbname]; ok {
66 | dbs.data[dbname].db.Close()
67 | delete(dbs.data, dbname)
68 | }
69 | dbs.Unlock()
70 | }
71 |
72 | func getNumberKeysInBucket(dbname string, bucket string) (n int, err error) {
73 | n = 0
74 | db, err := getDB(dbname)
75 | if err != nil {
76 | return n, err
77 | }
78 |
79 | err = db.View(func(tx *bolt.Tx) error {
80 | b := tx.Bucket([]byte(bucket))
81 | if b == nil {
82 | return errors.New("Bucket does not exist")
83 | }
84 | c := b.Cursor()
85 | for k, _ := c.First(); k != nil; k, _ = c.Next() {
86 | n++
87 | }
88 | return nil
89 | })
90 | log.Trace("Found %d keys in bucket '%s' in db '%s'", n, bucket, dbname)
91 | return n, err
92 | }
93 |
94 | func getBucketNames(dbname string) (bucketNames []string, err error) {
95 | db, err := getDB(dbname)
96 | if err != nil {
97 | return bucketNames, err
98 | }
99 |
100 | err = db.View(func(tx *bolt.Tx) error {
101 | return tx.ForEach(func(name []byte, _ *bolt.Bucket) error {
102 | bucketNames = append(bucketNames, string(name))
103 | return nil
104 | })
105 | })
106 | return bucketNames, err
107 | }
108 |
109 | func createDatabase(dbname string, buckets []string) error {
110 | db, err := getDB(dbname)
111 | if err != nil {
112 | return err
113 | }
114 |
115 | return db.Update(func(tx *bolt.Tx) error {
116 | for _, bucket := range buckets {
117 | _, err2 := tx.CreateBucketIfNotExists([]byte(bucket))
118 | if err2 != nil {
119 | return err2
120 | }
121 | }
122 | return nil
123 | })
124 | }
125 |
126 | // updateDatabase
127 | func updateDatabase(dbname string, bucket string, keystore map[string]string) error {
128 | db, err := getDB(dbname)
129 | if err != nil {
130 | return err
131 | }
132 |
133 | return db.Update(func(tx *bolt.Tx) error {
134 | b, err2 := tx.CreateBucketIfNotExists([]byte(bucket))
135 | if err2 != nil {
136 | return err2
137 | }
138 | for key, value := range keystore {
139 | err2 := b.Put([]byte(key), compressStringToByte(value))
140 | if err2 != nil {
141 | return err
142 | }
143 | }
144 | return err
145 | })
146 | }
147 |
148 | func getKeysFromDatabase(dbname string, bucket string) (keys []string, err error) {
149 | db, err := getDB(dbname)
150 | if err != nil {
151 | return []string{}, err
152 | }
153 |
154 | numKeys := 0
155 | err = db.View(func(tx *bolt.Tx) error {
156 | // Assume bucket exists and has keys
157 | b := tx.Bucket([]byte(bucket))
158 | if b == nil {
159 | return errors.New("Bucket does not exist")
160 | }
161 | c := b.Cursor()
162 | for k, _ := c.First(); k != nil; k, _ = c.Next() {
163 | numKeys++
164 | }
165 |
166 | keys = make([]string, numKeys)
167 | numKeys = 0
168 | c = b.Cursor()
169 | for k, _ := c.First(); k != nil; k, _ = c.Next() {
170 | keys[numKeys] = string(k)
171 | numKeys++
172 | }
173 | return nil
174 | })
175 | return
176 | }
177 |
178 | func getFromDatabase(dbname string, bucket string, keys []string) (map[string]string, error) {
179 | keystore := make(map[string]string)
180 |
181 | db, err := getDB(dbname)
182 | if err != nil {
183 | return keystore, err
184 | }
185 |
186 | if len(keys) == 0 {
187 | // Get all keys
188 | err = db.View(func(tx *bolt.Tx) error {
189 | // Assume bucket exists and has keys
190 | b := tx.Bucket([]byte(bucket))
191 | if b == nil {
192 | return errors.New("Bucket does not exist")
193 | }
194 | c := b.Cursor()
195 | for k, v := c.First(); k != nil; k, v = c.Next() {
196 | if v != nil {
197 | keystore[string(k)] = decompressByteToString(v)
198 | }
199 | }
200 | return nil
201 | })
202 | } else {
203 | // Get specified keys
204 | err = db.View(func(tx *bolt.Tx) error {
205 | b := tx.Bucket([]byte(bucket))
206 | if b == nil {
207 | return errors.New("Bucket does not exist")
208 | }
209 | for _, key := range keys {
210 | v := b.Get([]byte(key))
211 | if v != nil {
212 | keystore[key] = decompressByteToString(v)
213 | }
214 | }
215 | return nil
216 | })
217 | }
218 | return keystore, err
219 | }
220 |
221 | func deleteDatabase(dbname string) error {
222 | // Check if there is a specific match in the dbpath, otherwise it may
223 | // be an attack
224 | files, _ := ioutil.ReadDir(path.Join(dbpath))
225 | foundDB := false
226 | for _, f := range files {
227 | if f.Name() == dbname+".db" {
228 | foundDB = true
229 | }
230 | }
231 | if !foundDB {
232 | return errors.New("Could not find '" + dbname + "'")
233 | }
234 |
235 | _, err := getDB(dbname)
236 | if err != nil {
237 | return err
238 | }
239 | deleteDB(dbname)
240 |
241 | if _, err := os.Stat(path.Join(dbpath, dbname+".db")); os.IsNotExist(err) {
242 | return err
243 | }
244 | return os.Remove(path.Join(dbpath, dbname+".db"))
245 | }
246 |
247 | func deleteKeys(dbname string, bucket string, keys []string) error {
248 | db, err := getDB(dbname)
249 | if err != nil {
250 | return err
251 | }
252 |
253 | return db.Update(func(tx *bolt.Tx) error {
254 | b := tx.Bucket([]byte(bucket))
255 | if b == nil {
256 | return errors.New("Bucket does not exist")
257 | }
258 | for _, key := range keys {
259 | b.Delete([]byte(key))
260 | }
261 | return err
262 | })
263 | }
264 |
265 | func deleteBucket(dbname string, bucket string) error {
266 | db, err := getDB(dbname)
267 | if err != nil {
268 | return err
269 | }
270 |
271 | return db.Update(func(tx *bolt.Tx) error {
272 | return tx.DeleteBucket([]byte(bucket))
273 | })
274 | }
275 |
276 | func pop(dbname string, bucket string, n int) (map[string]string, error) {
277 | keystore := make(map[string]string)
278 |
279 | db, err := getDB(dbname)
280 | if err != nil {
281 | return keystore, err
282 | }
283 |
284 | err = db.Update(func(tx *bolt.Tx) error {
285 | b := tx.Bucket([]byte(bucket))
286 | if b == nil {
287 | return errors.New("Bucket does not exist")
288 | }
289 |
290 | c := b.Cursor()
291 | for k, v := c.First(); k != nil; k, v = c.Next() {
292 | b.Delete(k)
293 | keystore[string(k)] = decompressByteToString(v)
294 | if len(keystore) == n {
295 | break
296 | }
297 | }
298 | return nil
299 | })
300 | return keystore, err
301 | }
302 |
303 | func moveBuckets(dbname string, bucket1 string, bucket2 string, keys []string) error {
304 | db, err := getDB(dbname)
305 | if err != nil {
306 | return err
307 | }
308 |
309 | return db.Update(func(tx *bolt.Tx) error {
310 | b := tx.Bucket([]byte(bucket1))
311 | if b == nil {
312 | return errors.New("Bucket does not exist")
313 | }
314 | b2, _ := tx.CreateBucketIfNotExists([]byte(bucket2))
315 | for _, key := range keys {
316 | val := b.Get([]byte(key))
317 | if val != nil {
318 | b.Delete([]byte(key))
319 | b2.Put([]byte(key), val)
320 | } else {
321 | return errors.New("Could not find key: " + key)
322 | }
323 | }
324 | return nil
325 | })
326 | }
327 |
328 | func hasKeys(dbname string, buckets []string, keys []string) (doesHaveKeyMap map[string]bool, err error) {
329 | doesHaveKeyMap = make(map[string]bool)
330 |
331 | db, err := getDB(dbname)
332 | if err != nil {
333 | return doesHaveKeyMap, err
334 | }
335 |
336 | for _, key := range keys {
337 | doesHaveKeyMap[key] = false
338 | }
339 |
340 | err = db.View(func(tx *bolt.Tx) error {
341 | for _, bucket := range buckets {
342 | b := tx.Bucket([]byte(bucket))
343 | if b == nil {
344 | continue
345 | }
346 | for _, key := range keys {
347 | v := b.Get([]byte(key))
348 | if v != nil {
349 | doesHaveKeyMap[string(key)] = true
350 | }
351 | }
352 | }
353 | return nil
354 | })
355 | return doesHaveKeyMap, err
356 | }
357 |
358 | func hasKey(dbname string, bucket string, key string) (doesHaveKey bool, err error) {
359 | doesHaveKey = false
360 |
361 | db, err := getDB(dbname)
362 | if err != nil {
363 | return doesHaveKey, err
364 | }
365 |
366 | err = db.View(func(tx *bolt.Tx) error {
367 | b := tx.Bucket([]byte(bucket))
368 | if b == nil {
369 | return errors.New("Bucket does not exist")
370 | }
371 | v := b.Get([]byte(key))
372 | if v != nil {
373 | doesHaveKey = true
374 | }
375 | return nil
376 | })
377 | return doesHaveKey, err
378 | }
379 |
--------------------------------------------------------------------------------
/goreleaser.yml:
--------------------------------------------------------------------------------
1 | # goreleaser.yml
2 | # Build customization
3 | build:
4 | binary_name: boltdb-server
5 | goos:
6 | - windows
7 | - darwin
8 | - linux
9 | goarch:
10 | - amd64
11 | # Archive customization
12 | archive:
13 | format: zip
14 | replacements:
15 | amd64: 64-bit
16 | darwin: macOS
17 | linux: linux
18 | windows: windows
19 | files:
20 | - README.md
21 | - LICENSE
22 |
--------------------------------------------------------------------------------
/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/schollz/boltdb-server/023403d380dcfd94517a2e14c8e4006e293a5025/logo.png
--------------------------------------------------------------------------------
/main.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "net/http"
6 | "os"
7 | "path"
8 | "strconv"
9 | "strings"
10 | "time"
11 |
12 | "github.com/gin-gonic/gin"
13 | "github.com/jcelliott/lumber"
14 | "gopkg.in/urfave/cli.v1"
15 | )
16 |
17 | // Testing
18 | // curl -H "Content-Type: application/json" -X POST -d '{"bucket":"food","keystore":{"username":"xyz","password":"xyz"}}' 'http://zack:123@localhost:8080/'
19 |
20 | var version string
21 | var port, dbpath string
22 | var compressOn, verbose bool
23 | var log *lumber.ConsoleLogger
24 |
25 | func main() {
26 | // flag.BoolVar(&verbose, "verbose", false, "verbosity")
27 | // flag.BoolVar(&compressOn, "compress", false, "use compression")
28 | // flag.StringVar(&dbpath, "db", path.Join(".", "dbs"), "path to the database")
29 | // flag.StringVar(&port, "port", "8080", "port to listen on")
30 | // flag.Parse()
31 | app := cli.NewApp()
32 | app.Name = "boltdb-server"
33 | app.Usage = "fancy server for connecting to a BoltDB keystore"
34 | app.Version = version
35 | app.Compiled = time.Now()
36 | app.Action = func(c *cli.Context) error {
37 | dbpath = c.GlobalString("db")
38 | compressOn = c.GlobalBool("compress")
39 | verbose = c.GlobalBool("debug")
40 | port = c.GlobalString("port")
41 | os.MkdirAll(dbpath, 0755)
42 |
43 | if verbose {
44 | log = lumber.NewConsoleLogger(lumber.TRACE)
45 | } else {
46 | log = lumber.NewConsoleLogger(lumber.WARN)
47 | }
48 |
49 | startTime := time.Now()
50 |
51 | gin.SetMode(gin.ReleaseMode)
52 | r := gin.Default()
53 | r.GET("/v1/api", func(c *gin.Context) {
54 | c.String(200, `
55 |
56 | // Get map of buckets and the number of keys in each
57 | GET /v1/db//stats
58 |
59 | // Get list of all buckets
60 | GET /v1/db//buckets
61 |
62 | // Get all keys and values from a bucket
63 | GET /v1/db//bucket//numkeys
64 |
65 | // Get all keys and values from a bucket
66 | GET /v1/db//bucket//all
67 |
68 | // Get all keys and values specified by ?keys=key1,key2 or by JSON
69 | GET /v1/db//bucket//some
70 |
71 | // Delete and return first n keys
72 | GET /v1/db//bucket//pop?n=X
73 |
74 | // Get all keys in a bucket
75 | GET /v1/db//bucket//keys", handleGetKeys)
76 |
77 | // Return boolean of whether it has key
78 | GET /v1/db//bucket//haskey/
79 |
80 | // Return boolean of whether any buckets contain any keys specified by JSON
81 | GET /v1/db//haskeys
82 |
83 | // Delete database file
84 | DELETE /v1/db/
85 |
86 | // Delete bucket
87 | DELETE /v1/db//bucket/
88 |
89 | // Delete keys, where keys are specified by JSON []string
90 | DELETE /v1/db//bucket//keys
91 |
92 | // Updates a database with keystore specified by JSON
93 | POST /v1/db//bucket//update
94 |
95 | // Move keys, with buckets and keys specified by JSON
96 | POST /v1/db//move
97 |
98 | // Create buckets specified by JSON
99 | POST /v1/db//create
100 |
101 | `)
102 | })
103 | r.GET("/v1/uptime", func(c *gin.Context) {
104 | c.JSON(200, gin.H{
105 | "uptime": time.Since(startTime).String(),
106 | })
107 | })
108 | r.GET("/v1/db/:dbname/stats", handleGetDBStats) // Get map of buckets and the number of keys in each
109 | r.GET("/v1/db/:dbname/buckets", handleGetBuckets) // Get list of all buckets
110 | r.GET("/v1/db/:dbname/bucket/:bucket/numkeys", handleGetNumKeys) // Get all keys and values from a bucket (no parameters)
111 | r.GET("/v1/db/:dbname/bucket/:bucket/all", handleGet) // Get all keys and values from a bucket (no parameters)
112 | r.GET("/v1/db/:dbname/bucket/:bucket/some", handleGet) // Get all keys and values specified by ?keys=key1,key2 or by JSON
113 | r.GET("/v1/db/:dbname/bucket/:bucket/pop", handlePop) // Delete and return first n keys + values, where n specified by ?n=100
114 | r.GET("/v1/db/:dbname/bucket/:bucket/keys", handleGetKeys) // Get all keys in a bucket (no parameters)
115 | r.GET("/v1/db/:dbname/bucket/:bucket/haskey/:key", handleHasKey) // Return boolean of whether it has key
116 | r.GET("/v1/db/:dbname/haskeys", handleHasKeys) // Return boolean of whether any of the buckets contain the keys
117 | // TODO: r.GET("/v1/db/:dbname/bucket/:bucket/data", getDataArchive) // Creates archive with keys as filenames and values as contents, returns archive
118 |
119 | r.DELETE("/v1/db/:dbname", handleDeleteDatabase) // Delete database file (no parameters)
120 | r.DELETE("/v1/db/:dbname/bucket/:bucket", handleDeleteBucket) // Delete bucket (no parameters)
121 | r.DELETE("/v1/db/:dbname/bucket/:bucket/keys", handleDeleteKeys) // Delete keys, where keys are specified by JSON []string
122 | //
123 | r.POST("/v1/db/:dbname/bucket/:bucket/update", handleUpdate) // Updates a database with keystore specified by JSON
124 | r.POST("/v1/db/:dbname/move", handleMove) // Move keys, with buckets and keys specified by JSON
125 | r.POST("/v1/db/:dbname/create", handleCreateDB) // Move keys, with buckets and keys specified by JSON
126 |
127 | fmt.Printf("boltdb-server (v.%s) running on http://%s:%s\n", version, GetLocalIP(), port)
128 | r.Run(":" + port) // listen and serve on 0.0.0.0:8080
129 | return nil
130 | }
131 | app.Flags = []cli.Flag{
132 | cli.StringFlag{
133 | Name: "port, p",
134 | Value: "8050",
135 | Usage: "port to use to listen",
136 | },
137 | cli.StringFlag{
138 | Name: "db",
139 | Value: path.Join(".", "dbs"),
140 | Usage: "path to use for storing databases",
141 | },
142 | cli.BoolFlag{
143 | Name: "compress, c",
144 | Usage: "turn on compression",
145 | },
146 | cli.BoolFlag{
147 | Name: "debug",
148 | Usage: "turn on debug mode",
149 | },
150 | }
151 | app.Run(os.Args)
152 |
153 | }
154 |
155 | func handleHasKeys(c *gin.Context) {
156 | dbname := c.Param("dbname")
157 |
158 | type QueryJSON struct {
159 | Keys []string `json:"keys"`
160 | Buckets []string `json:"buckets"`
161 | }
162 | var json QueryJSON
163 | if c.BindJSON(&json) != nil {
164 | c.String(http.StatusBadRequest, "Problem binding keys")
165 | return
166 | }
167 |
168 | doesHaveKeyMap, err := hasKeys(dbname, json.Buckets, json.Keys)
169 | if err != nil {
170 | c.String(http.StatusInternalServerError, err.Error())
171 | return
172 | }
173 | c.JSON(http.StatusOK, doesHaveKeyMap)
174 | }
175 |
176 | func handleCreateDB(c *gin.Context) {
177 | dbname := c.Param("dbname")
178 |
179 | var json []string
180 | if c.BindJSON(&json) != nil {
181 | c.String(http.StatusBadRequest, "Problem binding keys")
182 | return
183 | }
184 |
185 | err := createDatabase(dbname, json)
186 | if err != nil {
187 | c.String(http.StatusInternalServerError, err.Error())
188 | return
189 | }
190 | c.JSON(http.StatusOK, "Created db")
191 | }
192 |
193 | func handleHasKey(c *gin.Context) {
194 | dbname := c.Param("dbname")
195 | bucket := c.Param("bucket")
196 | key := c.Param("key")
197 | doesHaveKey, err := hasKey(dbname, bucket, key)
198 | if err != nil {
199 | c.String(http.StatusInternalServerError, err.Error())
200 | return
201 | }
202 | c.JSON(http.StatusOK, doesHaveKey)
203 | }
204 |
205 | func handleGetDBStats(c *gin.Context) {
206 | dbname := c.Param("dbname")
207 | bucketNames, err := getBucketNames(dbname)
208 | if err != nil {
209 | c.String(http.StatusInternalServerError, err.Error())
210 | return
211 | }
212 | stats := make(map[string]int)
213 | for _, bucket := range bucketNames {
214 | stats[bucket], err = getNumberKeysInBucket(dbname, bucket)
215 | if err != nil {
216 | c.String(http.StatusInternalServerError, err.Error())
217 | return
218 | }
219 | }
220 | c.JSON(http.StatusOK, stats)
221 | }
222 |
223 | func handleGetNumKeys(c *gin.Context) {
224 | dbname := c.Param("dbname")
225 | bucket := c.Param("bucket")
226 | n, err := getNumberKeysInBucket(dbname, bucket)
227 | if err != nil {
228 | c.String(http.StatusInternalServerError, err.Error())
229 | return
230 | }
231 | c.JSON(http.StatusOK, n)
232 | }
233 |
234 | func handleGetBuckets(c *gin.Context) {
235 | dbname := c.Param("dbname")
236 | bucketNames, err := getBucketNames(dbname)
237 | if err != nil {
238 | c.String(http.StatusInternalServerError, err.Error())
239 | return
240 | }
241 | c.JSON(http.StatusOK, bucketNames)
242 | }
243 |
244 | func handleDeleteDatabase(c *gin.Context) {
245 | dbname := c.Param("dbname")
246 | err := deleteDatabase(dbname)
247 | if err != nil {
248 | c.String(http.StatusInternalServerError, err.Error())
249 | return
250 | }
251 | c.String(http.StatusOK, "Deleted database")
252 | }
253 |
254 | func handleDeleteBucket(c *gin.Context) {
255 | dbname := c.Param("dbname")
256 | bucket := c.Param("bucket")
257 | err := deleteBucket(dbname, bucket)
258 | if err != nil {
259 | c.String(http.StatusInternalServerError, err.Error())
260 | return
261 | }
262 | c.String(http.StatusOK, "Deleted bucket")
263 | }
264 |
265 | func handleDeleteKeys(c *gin.Context) {
266 | dbname := c.Param("dbname")
267 | bucket := c.Param("bucket")
268 | var keys []string
269 | if c.BindJSON(&keys) != nil {
270 | c.String(http.StatusBadRequest, "Problem binding keys")
271 | return
272 | }
273 | err := deleteKeys(dbname, bucket, keys)
274 | if err != nil {
275 | c.String(http.StatusInternalServerError, err.Error())
276 | return
277 | }
278 | c.String(http.StatusOK, "Deleted keys")
279 | }
280 |
281 | func handleUpdate(c *gin.Context) {
282 | dbname := c.Param("dbname")
283 | bucket := c.Param("bucket")
284 | var json map[string]string
285 | if c.BindJSON(&json) != nil {
286 | c.String(http.StatusBadRequest, "Problem binding keystore")
287 | return
288 | }
289 | err := updateDatabase(dbname, bucket, json)
290 | if err != nil {
291 | c.String(http.StatusInternalServerError, err.Error())
292 | return
293 | }
294 | c.String(http.StatusOK, fmt.Sprintf("Inserted %d things into %s", len(json), bucket))
295 | }
296 |
297 | func handleGetKeys(c *gin.Context) {
298 | dbname := c.Param("dbname")
299 | bucket := c.Param("bucket")
300 | keystore, err := getKeysFromDatabase(dbname, bucket)
301 | if err != nil {
302 | c.String(http.StatusInternalServerError, err.Error())
303 | return
304 | }
305 | c.JSON(http.StatusOK, keystore)
306 | }
307 |
308 | func handlePop(c *gin.Context) {
309 | dbname := c.Param("dbname")
310 | bucket := c.Param("bucket")
311 | nQuery := c.DefaultQuery("n", "0")
312 | num, err := strconv.Atoi(nQuery)
313 | if err != nil {
314 | c.String(http.StatusInternalServerError, err.Error())
315 | return
316 | }
317 | if num <= 0 {
318 | c.String(http.StatusBadRequest, "Must specify n > 0")
319 | return
320 | }
321 | keystore, err := pop(dbname, bucket, num)
322 | if err != nil {
323 | c.String(http.StatusInternalServerError, err.Error())
324 | return
325 | }
326 | c.JSON(http.StatusOK, keystore)
327 | }
328 |
329 | func handleGet(c *gin.Context) {
330 | dbname := c.Param("dbname")
331 | bucket := c.Param("bucket")
332 | keysQuery := c.DefaultQuery("keys", "")
333 | json := []string{}
334 | if c.BindJSON(&json) != nil && keysQuery != "" {
335 | json = strings.Split(keysQuery, ",")
336 | }
337 | // If requested some without providing keys, throw error
338 | if len(json) == 0 && strings.Contains(c.Request.RequestURI, "some") {
339 | c.String(http.StatusBadRequest, "Must provide keys")
340 | return
341 | }
342 | // Get keys and values
343 | keystore, err := getFromDatabase(dbname, bucket, json)
344 | if err != nil {
345 | c.String(http.StatusInternalServerError, err.Error())
346 | return
347 | }
348 | c.JSON(http.StatusOK, keystore)
349 | return
350 | }
351 |
352 | func handleMove(c *gin.Context) {
353 | dbname := c.Param("dbname")
354 | type QueryJSON struct {
355 | FromBucket string `json:"from_bucket"`
356 | ToBucket string `json:"to_bucket"`
357 | Keys []string `json:"keys"`
358 | }
359 | var json QueryJSON
360 | if c.BindJSON(&json) != nil {
361 | c.String(http.StatusBadRequest, "Must provide keys, from_bucket and to_bucket")
362 | return
363 | }
364 | // Get keys and values
365 | err := moveBuckets(dbname, json.FromBucket, json.ToBucket, json.Keys)
366 | if err != nil {
367 | log.Error("Could not move %v from %s to %s", json.Keys, json.FromBucket, json.ToBucket)
368 | c.String(http.StatusInternalServerError, err.Error())
369 | return
370 | }
371 | log.Trace("Moved %v from %s to %s", json.Keys, json.FromBucket, json.ToBucket)
372 | c.JSON(http.StatusOK, fmt.Sprintf("Moved keys"))
373 | }
374 |
--------------------------------------------------------------------------------
/utils.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "bytes"
5 | "compress/flate"
6 | "io"
7 | "math/rand"
8 | "time"
9 | "net"
10 | "strings"
11 | )
12 |
13 | // GetLocalIP returns the local ip address
14 | func GetLocalIP() string {
15 | addrs, err := net.InterfaceAddrs()
16 | if err != nil {
17 | return "localhost"
18 | }
19 | bestIP := "localhost"
20 | for _, address := range addrs {
21 | // check the address type and if it is not a loopback the display it
22 | if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() {
23 | if ipnet.IP.To4() != nil && (strings.Contains(ipnet.IP.String(), "192.168.1") || strings.Contains(ipnet.IP.String(), "192.168")) {
24 | return ipnet.IP.String()
25 | }
26 | }
27 | }
28 | return bestIP
29 | }
30 |
31 | func compressStringToByte(s string) []byte {
32 | if compressOn {
33 | return compressByte([]byte(s))
34 | }
35 | return []byte(s)
36 | }
37 |
38 | func decompressByteToString(b []byte) string {
39 | if compressOn {
40 | return string(decompressByte(b))
41 | }
42 | return string(b)
43 | }
44 |
45 | // compressByte returns a compressed byte slice.
46 | func compressByte(src []byte) []byte {
47 | compressedData := new(bytes.Buffer)
48 | compress(src, compressedData, 9)
49 | return compressedData.Bytes()
50 | }
51 |
52 | // decompressByte returns a decompressed byte slice.
53 | func decompressByte(src []byte) []byte {
54 | compressedData := bytes.NewBuffer(src)
55 | deCompressedData := new(bytes.Buffer)
56 | decompress(compressedData, deCompressedData)
57 | return deCompressedData.Bytes()
58 | }
59 |
60 | // compress uses flate to compress a byte slice to a corresponding level
61 | func compress(src []byte, dest io.Writer, level int) {
62 | compressor, _ := flate.NewWriter(dest, level)
63 | compressor.Write(src)
64 | compressor.Close()
65 | }
66 |
67 | // compress uses flate to decompress an io.Reader
68 | func decompress(src io.Reader, dest io.Writer) {
69 | decompressor := flate.NewReader(src)
70 | io.Copy(dest, decompressor)
71 | decompressor.Close()
72 | }
73 |
74 | // http://stackoverflow.com/questions/22892120/how-to-generate-a-random-string-of-a-fixed-length-in-golang
75 | var src = rand.NewSource(time.Now().UnixNano())
76 |
77 | const letterBytes = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"
78 | const (
79 | letterIdxBits = 6 // 6 bits to represent a letter index
80 | letterIdxMask = 1<= 0; {
89 | if remain == 0 {
90 | cache, remain = src.Int63(), letterIdxMax
91 | }
92 | if idx := int(cache & letterIdxMask); idx < len(letterBytes) {
93 | b[i] = letterBytes[idx]
94 | i--
95 | }
96 | cache >>= letterIdxBits
97 | remain--
98 | }
99 |
100 | return string(b)
101 | }
102 |
--------------------------------------------------------------------------------