ʕ ゚皿゚ ʔ GolangからLevelDBを使う
cgoとLevelDBを使って、タイトルのとおりのものを作ってみた。頑張ればRiakとかInfluxdbみたいなのを書けるかもナー。
ʕ ゚皿゚ ʔ cgo楽しいおシーゴォー
コードはすべてGithubにある。
yuroyoro/leveldb-go-sample · GitHub
なお、この実装はあくまで個人的な練習で作ったものなので、まともにLevelDBをGoから使うならばInfluxdbでも使ってるlevigoがおすすめ。
LevelDBはあらかじめinstallしてある想定。 mac osxなのでbrew install leveldbで入った。
cgoでLevelDBをwrapする
まずは、cgoを使ったLevelDBの簡単なwrapperを用意する。単にLevelDBを使うだけなら、感覚的にはsqlite3みたいに、leveldb_openでopenして得られるleveldb_t構造体を使ってputやgetを呼び出し、終わったらcloseすればいい。
leveldb.go から抜粋。
package main // #cgo LDFLAGS: -lleveldb // #include <stdlib.h> // #include "leveldb/c.h" import "C" import ( "errors" "unsafe" ) // C Level pointer holder type LevelDB struct { CLevelDB *C.leveldb_t Name string } // Open LevelDB with given name func OpenLevelDB(path string) (leveldb *LevelDB, err error) { cpath := C.CString(path) // convert path to c string defer C.leveldb_free(unsafe.Pointer(cpath)) // allocate LevelDB Option struct to open opt := C.leveldb_options_create() defer C.leveldb_free(unsafe.Pointer(opt)) // set open option C.leveldb_options_set_create_if_missing(opt, C.uchar(1)) // open leveldb var cerr *C.char cleveldb := C.leveldb_open(opt, cpath, &cerr) if cerr != nil { defer C.leveldb_free(unsafe.Pointer(cerr)) return nil, errors.New(C.GoString(cerr)) } return &LevelDB{cleveldb, path}, nil }
上記のOpenLevelDBで、leveldb_openでdatabaseを開く。optionは色々指定できるのだが、サンプルなのでcreate_if_missingだけ指定している。
// Put key, value to database func (db *LevelDB) Put(key, value string) (err error) { opt := C.leveldb_writeoptions_create() // write option defer C.leveldb_free(unsafe.Pointer(opt)) k := C.CString(key) // copy defer C.leveldb_free(unsafe.Pointer(k)) v := C.CString(value) defer C.leveldb_free(unsafe.Pointer(v)) var cerr *C.char C.leveldb_put(db.CLevelDB, opt, k, C.size_t(len(key)), v, C.size_t(len(value)), &cerr) if cerr != nil { defer C.leveldb_free(unsafe.Pointer(cerr)) return errors.New(C.GoString(cerr)) } return } func (db *LevelDB) Get(key string) (value string, err error) { opt := C.leveldb_readoptions_create() // write option defer C.leveldb_free(unsafe.Pointer(opt)) k := C.CString(key) // copy defer C.leveldb_free(unsafe.Pointer(k)) var vallen C.size_t var cerr *C.char cvalue := C.leveldb_get(db.CLevelDB, opt, k, C.size_t(len(key)), &vallen, &cerr) if cerr != nil { defer C.leveldb_free(unsafe.Pointer(cerr)) return "", errors.New(C.GoString(cerr)) } if cvalue == nil { return "", nil } defer C.leveldb_free(unsafe.Pointer(cvalue)) return C.GoString(cvalue), nil }
put/getはこんな感じ。C.CStringでkeyやvalueをCの*charに変換しているが、これはmemcpyが走る(ハズ?)なので効率的ではない。levigoの実装では、[]byteに変換してunsafe.Pointerでgoのbyte列のpointerをC側に渡す実装になっているようだ。
net/httpでRESTっぽいガワをつける
goを勉強した事がある人なら誰しもがnet/httpを使った簡単なkvsを書いたことがあるはず。今回はバックエンドをLevelDBにして、net/httpでREST的なガワをつけてみる。
まずはBackend側の実装。 backend.go から。
package main import ( "log" ) type Backend struct { db *LevelDB putch chan putRequest delch chan delRequest quitch chan bool } type putRequest struct { key string val string } type delRequest struct { key string } func NewBackend(dbname string) (backend *Backend, err error) { db, err := OpenLevelDB(dbname) if err != nil { return } log.Printf("LevelDB opened : name -> %s", dbname) backend = &Backend{ db, make(chan putRequest), make(chan delRequest), make(chan bool), } return } func (backend *Backend) Start() { go func() { for { select { case putreq := <-backend.putch: backend.db.Put(putreq.key, putreq.val) log.Printf("Backend.Put(%s, %v)\n", putreq.key, putreq.val) case delreq := <-backend.delch: backend.db.Delete(delreq.key) log.Printf("Backend.Delete(%s)\n", delreq.key) case <-backend.quitch: close(backend.putch) close(backend.delch) close(backend.quitch) log.Printf("Backend stoped") return } } }() log.Printf("Backend started") } func (backend *Backend) Shutdown() { backend.quitch <- true } func (backend *Backend) Get(key string) (val string, err error) { return backend.db.Get(key) } func (backend *Backend) Put(key, val string) { backend.putch <- putRequest{key, val} } func (backend *Backend) Delete(key string) { backend.delch <- delRequest{key} }
NewBackendで先ほど用意したwrapperを使ってLevelDBをopenして、goroutine経由でput/deleteするように実装している。本来ならば、goroutineをsyncして同期っぽいAPIにするべきなのだろうが、面倒なのでサボっている。
次に、net/httpを使ってhttpを処理するserver側。 server.go から。
package main import ( "flag" "io/ioutil" "log" "net/http" ) func main() { // parse command line arguments dbname := flag.String("name", "./testdb", "Open the database with the specified name") addr := flag.String("addr", ":5050", "listen address") flag.Parse() // open database and start backend backend, err := NewBackend(*dbname) if err != nil { log.Fatal("can't open database", err) } backend.Start() defer backend.Shutdown() // listen and serve http http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) { switch r.Method { case "GET": HandleGet(w, r, backend) case "POST", "PUT": HandlePut(w, r, backend) case "DELETE": HandleDelete(w, r, backend) } }) log.Printf("Server listening on : %s", *addr) http.ListenAndServe(*addr, nil) } func HandleGet(w http.ResponseWriter, r *http.Request, backend *Backend) { key := r.URL.Path[len("/"):] val, err := backend.Get(key) if err != nil { w.WriteHeader(http.StatusInternalServerError) return } if val == "" { w.WriteHeader(http.StatusNotFound) return } w.WriteHeader(http.StatusOK) w.Write([]byte(val)) } func HandlePut(w http.ResponseWriter, r *http.Request, backend *Backend) { key := r.URL.Path[len("/"):] defer r.Body.Close() val, err := ioutil.ReadAll(r.Body) if err != nil { w.WriteHeader(http.StatusInternalServerError) return } backend.Put(key, string(val)) w.WriteHeader(http.StatusCreated) } func HandleDelete(w http.ResponseWriter, r *http.Request, backend *Backend) { key := r.URL.Path[len("/"):] backend.Delete(key) w.WriteHeader(http.StatusNoContent) }
起動時の引数でdbのpathとportを貰ってlisten。httpでGET/POST/PUT/DELETEをそれぞれ処理している。見たとおりの実装で、特に難しいことはしていない。
Dockerfile
Dockerfileもあるので、手元でdockerが動けば動作させてみることができる。Dockerhubでimageを配布しようとしたのだが、なぜかpendingのままでimageをbuildしてくれない( ;゚皿゚)。
ʕ ゚皿゚ ʔ Golangからv8を使う
cgoとlibv8を使って、タイトルのとおりのものを作ってみた。頑張ればnodeみたいなのをgoで書けるかもナー。
ʕ ゚皿゚ ʔ cgo楽しいおシーゴォー
基本的な方法は以下の記事にあるとおりだが、v8のバージョンが上がっていたりするので、多少の手直しをしてある。
Embedding V8 Javascript Engine and Go | Brave New Method
コードはすべてGithubにある
yuroyoro/golang_v8_Embedding_sample · GitHub
まず、libv8を使う以下のようなwrapperをc++で用意して、cgoから使えるようにしておく。
#ifndef _V8WRAPPER_H #define _V8WRAPPER_H #ifdef __cplusplus extern "C" { #endif // compiles and executes javascript and returns the script return value as string char * runv8(const char *jssrc); #ifdef __cplusplus } #endif #endif // _V8WRAPPER_H
#include <v8.h> #include <string.h> #include "v8wrapper.h" using namespace v8; char * runv8(const char *jssrc) { // Get the default Isolate created at startup. Isolate* isolate = Isolate::GetCurrent(); // Create a stack-allocated handle scope. HandleScope handle_scope(isolate); // Create a new context. Handle<Context> context = Context::New(isolate); // Enter the context for compiling and running the hello world script. Context::Scope context_scope(context); // Create a string containing the JavaScript source code. Handle<String> source = String::New(jssrc); // Compile the source code. Handle<Script> script = Script::Compile(source); // Run the script to get the result. Handle<Value> result = script->Run(); // The JSON.stringify function object Handle<Object> global = context->Global(); Handle<Object> JSON = global->Get(String::New("JSON"))->ToObject(); Handle<Function> JSON_stringify = Handle<Function>::Cast(JSON->Get(String::New("stringify"))); Handle<Value> args[] = { result }; // stringify result Local<Value> json = JSON_stringify->Call(JSON, 1, args); // Convert the result to an UTF8 string and print it. String::Utf8Value utf8(json); // return result as string, must be deallocated in cgo wrapper return strdup(*utf8); }
runv8(char)は、引数の文字列のjavascriptをv8で実行し、結果の値をJSON.stringifyした結果の文字列を返す。
なお、サンプルなのでエラー処理はしていない( ;゚皿゚)。
で、こいつを適当にmakeする。以下のmakefileはosx用
V8_INC=/usr/local/Cellar/v8/3.19.18.4/include V8_LIBDIR=/usr/local/Cellar/v8/3.19.18.4/lib/libv8.dylib CC=g++ CFLAGS= -I$(V8_INC) -I/usr/include -lv8 -dynamiclib -o $(TARGET) SOURCES=v8wrapper.cc OBJECTS=$(SOURCES:.cc=.o) $(V8_DYLIB) TARGET=libv8wrapper.dylib all: $(TARGET) $(TARGET): $(OBJECTS) $(CC) $(CFLAGS) $< -o $@ clean: rm $(TARGET) $(OBJECTS)
あとは、cgoでこのv8warpperをlinkして使えばいい。
package main // #cgo LDFLAGS: -L. -lv8wrapper -lv8 -lstdc++ // #include <stdlib.h> // #include "v8wrapper.h" import "C" import ( "encoding/json" "fmt" "unsafe" ) func RunV8(script string, result interface{}) error { // convert Go string to nul terminated C-string cstr := C.CString(script) defer C.free(unsafe.Pointer(cstr)) // run script and convert returned C-string to Go string rcstr := C.runv8(cstr) defer C.free(unsafe.Pointer(rcstr)) jsonstr := C.GoString(rcstr) fmt.Printf("Runv8 json -> %s\n", jsonstr) // unmarshal result err := json.Unmarshal([]byte(jsonstr), result) if err != nil { return err } fmt.Printf("Runv8 Result -> %T: %#+v\n", result, result) return nil }
RunV8では、'encoding/json'を利用して、runv8の戻り値である文字列のJSONを、goの値にunmarshalする。
以下のように文字列でscriptをRunV8関数に渡せばinterface型で結果を取得できる。
package main import ( "fmt" ) func main() { scripts := []string{ "null", "true", "123", "457.78", "[10, 20, 30]", "'Hello, World'", "new Date()", `obj = {"foo": [1, 2], "bar": {"baz": true, "hoge": "fuga"}}`, } for _, s := range scripts { fmt.Printf("Script -> %s\n", s) var res interface{} RunV8(s, &res) fmt.Printf("Result -> %T: %#+v\n\n", res, res) } }
実行結果
Script -> null Result -> <nil>: <nil> Script -> true Result -> bool: true Script -> 123 Result -> float64: 123 Script -> 457.78 Result -> float64: 457.78 Script -> [10, 20, 30] Result -> []interface {}: []interface {}{10, 20, 30} Script -> 'Hello, World' Result -> string: "Hello, World" Script -> new Date() Result -> string: "2014-06-11T08:58:43.951Z" Script -> obj = {"foo": [1, 2], "bar": {"baz": true, "hoge": "fuga"}} Result -> map[string]interface {}: map[string]interface {}{"foo":[]interface {}{1, 2}, "bar":map[string]interface {}{"baz":true, "hoge":"fuga"}}
このように、JSONをstructにmappingさせることもできる。
package main import ( "fmt" ) type Foo struct { Foo []int `json:"foo"` Bar Bar `json:"bar"` } type Bar struct { Baz bool `json:"baz"` Hoge string `json:"hoge"` } func main() { script := `obj = {"foo": [1, 2], "bar": {"baz": true, "hoge": "fuga"}}` var result Foo fmt.Printf("Script -> %s\n", script) RunV8(string(script), &result) fmt.Printf("Result -> %T: %#+v\n", result, result) }
実行結果
Script -> obj = {"foo": [1, 2], "bar": {"baz": true, "hoge": "fuga"}} Result -> main.Foo: main.Foo{Foo:[]int{1, 2}, Bar:main.Bar{Baz:true, Hoge:"fuga"}}