gRPCでのUserAgent備忘録
Noahです
Golang で gRPC, Protocol Buffers を触る機会があり、User Agent の取得で少し戸惑ったのでメモします
gRPCとは
おなじみGoogleが2015年2月に発表したRPCフレームワークです
対応言語は、C++, Java, Python などはもちろん、Go や Ruby, Android Java, Node.js, Objective-C, PHP も対応しています
シリアライズはデフォルトでProtocol Buffersを利用していますが、好きなものに取り替え可能なようです
かなり速いと謳われるFlat Buffersなんていうものもあるそうですが、今回はProtocol Buffersで行きます
Protobuf定義ファイルの作成
メソッドとメッセージをProtocol Buffersで定義します
今回、リクエストを投げると User Agentを返すようにするので以下のようにします
syntax = "proto3"; package proto; service UserAgent{ rpc GetUA(UserAgentReq) returns (UserAgentRes); } message UserAgentRes { string user_agent = 1; } message UserAgentReq { }
message
で、リクエスト、レスポンスの構造を指定します
message UserAgentRes { string user_agent = 1; }
内部の string user_agent = 1
で、 = 1
としているのは、代入というより値をユニークに識別するために行っています
service
でメソッドの定義をしています
service UserAgent{ rpc GetUA(UserAgentReq) returns (UserAgentRes); }
この辺は普通の言語と変わりなく、メソッド名、引数の message
と 返り値の message
を指定します
あとは、各言語用にこれをトランスパイルします
protoc はインストール済みとして、Golang用にコンパイルするので以下のプラグインを導入します
$ go get github.com/golang/protobuf/protoc-gen-go
その後、
$ protoc --go_out=plugins=grpc:. api.proto
実行後に api.pb.go
が同じディレクトリにあることを確認してください
サーバーの記述
サーバー側を記述します
package main import ( "log" "net" "golang.org/x/net/context" "github.com/NoahOrberg/grpc-useragent/protobuf" "google.golang.org/grpc" "google.golang.org/grpc/codes" "google.golang.org/grpc/metadata" ) type UserAgentServer struct { } func NewUserAgentServer() *UserAgentServer { return &UserAgentServer{} } func (s UserAgentServer) GetUA(ctx context.Context, req *proto.UserAgentReq) (*proto.UserAgentRes, error) { log.Print(ctx) md, ok := metadata.FromIncomingContext(ctx) if !ok { return &proto.UserAgentRes{}, grpc.Errorf(codes.Internal, "Cannot get user-agent") } return &proto.UserAgentRes{ UserAgent: md["user-agent"][0], }, nil } func main() { lis, err := net.Listen("tcp", ":50052") if err != nil { log.Fatalf("failed to listen: %v", err) } s := grpc.NewServer() proto.RegisterUserAgentServer(s, NewUserAgentServer()) s.Serve(lis) }
google.golang.org/grpc/metadata
を使い、FromIncomingContext
メソッドにコンテキストを渡して呼ぶことでメタデータを取得します。
md, ok := metadata.FromIncomingContext(ctx)
メタデータを取得したら、今度はそれからUser Agentを取得します。
これはメタデータ上では配列になっていますが、暫定的に配列の最初のみを取り出して返しています。(配列として返しても、ログとしてこの配列を出力しても確認はできる)
クライアントの実装
サーバー側で認識して処理するので、正直 User Agentを返さなくてもいいのですが、一応。
package main import ( "flag" "log" "github.com/NoahOrberg/grpc-useragent/protobuf" "golang.org/x/net/context" "google.golang.org/grpc" ) var ( addrFlag = flag.String("addr", "localhost:50052", "server address host:post") ) func main() { conn, err := grpc.Dial(*addrFlag, grpc.WithInsecure()) if err != nil { log.Fatalf("Connection error: %v", err) } defer conn.Close() c := proto.NewUserAgentClient(conn) resp, err := c.GetUA(context.Background(), &proto.UserAgentReq{}) if err != nil { log.Fatalf("RPC error: %v", err) } log.Printf("\nUserAgent: `%s`", resp.UserAgent) }
ただ単に呼び出して、結果を吐いてるだけですね。
まとめ
メタデータから取得する際に、そもそも「メタデータとは?」となっていたからもうダメ
もっと学習が必要だと思った