先日TwitchからリリースされたRPCフレームワークTwirpを試してみました。
- Twirp: a sweet new RPC framework for Go – Twitch Blog
- twitchtv/twirp: A simple RPC framework with protobuf service definitions
Twirpとは
TwirpはgRPCと同じようにProtocol Buffersの.proto
ファイルからGoのコードを生成しますが以下のような違いがあります。
- HTTP/1.1を使用
- gRPCはHTTP/2を使用
- JSONクライアントサポート
- 他の言語でクライアントを書くのが比較的簡単
- cURL等を使ってコマンドラインからリクエストを送信しデバッグすることができる
動作に必要なツールをインストール
protoc
などのツールをインストールします。
Install and Update Twirp · twitchtv/twirp Wiki
Go(1.7+)とProtocol Bufferをインストール。(Goは環境変数の設定が必要かもしれません。)
brew install go
brew install protobuf
retoolをインストール。
go get
でtwirp
をインストールすることもできますがretool
を使うとプロジェクトごとにインストールできます。
go get github.com/twitchtv/retool
protoファイルの作成とコード生成
twirpのWikiにディレクトリ構成等のベストプラクティスがまとめてあったので、これを参考に進めてみます。
Best Practices · twitchtv/twirp Wiki
今回はhelloworld
というサービスを作ることにします。
ディレクトリを作成。
mkdir -p cmd/server
mkdir -p cmd/client
mkdir -p rpc/helloworld
protoファイルを作成
syntax = "proto3";
package twirp.example.helloworld;
option go_package = "helloworld";
service HelloWorld {
rpc Hello(HelloReq) returns (HelloResp);
}
message HelloReq {
string subject = 1;
}
message HelloResp {
string text = 1;
}
retool
でprotoc-gen-go
とprotoc-gen-twirp
をインストール。
retool add github.com/golang/protobuf/protoc-gen-go master
retool add github.com/twitchtv/twirp/protoc-gen-twirp master
コード生成コマンド実行用のMakefileを作成します。retool
でインストールしたツールを使う場合はコマンドの頭にretool do
をつけます。
gen:
retool do protoc --proto_path=. --twirp_out=. --go_out=. rpc/helloworld/service.proto
make gen
コマンドを実行するとrpc/helloworld
にservice.pg.go
とservice.twirp.go
が生成されます。
サーバーとクライアントを実装
生成されたコードを使ってサーバーとクライアントを実装します。
package main
import (
"context"
"net/http"
pb "github.com/atsaki/twirp-example/rpc/helloworld"
)
type HelloWorldServer struct{}
func (s *HelloWorldServer) Hello(ctx context.Context, req *pb.HelloReq) (resp *pb.HelloResp, err error) {
return &pb.HelloResp{Text: "Hello " + req.GetSubject()}, nil
}
func main() {
twirpHandler := pb.NewHelloWorldServer(&HelloWorldServer{}, nil)
mux := http.NewServeMux()
mux.Handle(pb.HelloWorldPathPrefix, twirpHandler)
http.ListenAndServe(":8080", mux)
}
package main
import (
"context"
"fmt"
"log"
"net/http"
"os"
pb "github.com/atsaki/twirp-example/rpc/helloworld"
)
func main() {
client := pb.NewHelloWorldProtobufClient("http://localhost:8080", &http.Client{})
subject := ""
if len(os.Args) > 1 {
subject = os.Args[1]
}
resp, err := client.Hello(context.Background(), &pb.HelloReq{Subject: subject})
if err != nil {
log.Fatal(err)
}
fmt.Println(resp.GetText())
}
サーバーを実行した状態で、
go run cmd/server/main.go
クライアントを実行すると、
go run cmd/client/main.go Twirp
以下のような文字列が出力されます。
Hello Twirp
cURLでリクエストを送信する
上の例ではProtocol Buffersのクライアントを作成しサーバーにリクエストを送信しましたが、cURLなどでリクエストを送信し結果をJSONで取得することもできます。
リクエストは以下のURIに送信します。
<package>
は.proto
ファイルでpackage
に指定した値になります。
/twirp/<package>.<service>/<method>
HTTP Routing and Serialization · twitchtv/twirp Wiki
今回の例では次のようになります。
curl -X POST \
-H "Content-Type:application/json" \
-d '{"subject": "Twirp"}' \
http://localhost:8080/twirp/twirp.example.helloworld.HelloWorld/Hello
サーバーが起動している状態で実行すると結果がJSONで返ってきます。
{"text":"Hello Twirp"}