NoahOrblog

某・福島にある大学のコンピュータ理工学部の大学生のお話。

gRPCでのUserAgent備忘録

Noahです

Golang で gRPC, Protocol Buffers を触る機会があり、User Agent の取得で少し戸惑ったのでメモします

gRPCとは

f:id:NoahOrberg:20170717221853p:plain

https://grpc.io/

おなじみ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)
}

ただ単に呼び出して、結果を吐いてるだけですね。

まとめ

github.com

メタデータから取得する際に、そもそも「メタデータとは?」となっていたからもうダメ

もっと学習が必要だと思った