Go言語のjson-rpcについて


Go標準のjson-rpcはあるにはあるんだけど・・・。

かなーり特殊な実装なので他の言語から利用するのツライ。 これではせっかくの言語依存しないjson-rpcの仕様が泣いちゃうよ!

かといって重厚長大な実装を引き込んでしまうのもなぁ。 ということで、できるだけミニマムな解決方法を考えてみた。

結論

github にてパッケージを公開しました。

go get -u github.com/nobonobo/jsonrpc

以上でインストール完了。

以下をserver.goとかで保存。

package main

import (
    "log"
    "net/http"

    "github.com/nobonobo/jsonrpc"
)

type Sample struct{}

func (s *Sample) Add(args *[]int, reply *int) error {
    *reply = 0
    for _, v := range *args {
        *reply += v
    }
    return nil
}

func main() {
    server := jsonrpc.NewServer()
    server.Register(&Sample{})
    http.Handle(jsonrpc.DefaultRPCPath, server)
    if err := http.ListenAndServe(":8080", nil); err != nil {
        log.Fatalln(err)
    }
}

以下のコマンドでサーバー起動

$ go run server.go

別のターミナルから
$ curl -d '{"method":"Sample.Add","params":[[1,2]]}' http://localhost:8080/jsonrpc
{"id":null,"result":3,"error":null}

という具合にPOSTメソッドによるjson-rpc呼び出しに対応しています。 「あったりまえじゃん!」って思うかもしれませんが、Go標準のはPOSTメソッド非対応なのです・・・。

仕組み

以下のようにPOSTメソッドの場合の処理を入れただけ。

case "POST":
    dst, src := net.Pipe()
    go io.Copy(src, req.Body)
    go io.Copy(w, src)
    server.ServeCodec(&codecWrapper{org.NewServerCodec(dst)})

Go標準のjson-rpcは双方向ストリーム相当のnet.Connをハンドルする仕様。 リクエストボディとレスポンスライターをnet.Pipeにラップした状態で標準のjson-rpcコーデックを呼びます。 codecWrapperはひとつのコーデックによるレスポンス書き込みがあったらコーデックを閉じます。 それに起因してここで起こしたgoroutineはすべて終了し、ServeCodecメソッドもリターンします。

つまり、既存のCodecを一往復POSTのためだけに使うようにしたのです。