GoでgRPC通信を試してみた
公式のチュートリアルのソースから引っ張ってきたものを使って自分で理解できるように書いていきます。
gRPC概要
gRPCはHTTP/2.0上で動作するので以下4つの通信方法をサポートします。
1. 単純な単項リモートプロシージャコール(RPC) –クライアントはサーバーに要求を送信し、関数呼び出しの動作と同様に応答を待ちます。
2. サーバー側ストリーミングRPC–クライアントはサーバーに要求を送信し、メッセージのストリームを取得し、メッセージがなくなるまでストリームから読み取ります。
3. クライアント側のストリーミングRPC–クライアントは一連のメッセージを書き込み、サーバーに送信してから、サーバーがすべてのメッセージを読み取って応答を返すのを待ちます。
4. 双方向ストリーミングRPC–クライアントとサーバーの両方が独立してメッセージを送信し、順序を維持します。
本記事の全ソースのgithubはこちら
プロトコルバッファのインストール
gRPCには一般的にプロトコルバッファを使用するらしいのでインストールします
$ brew install protobuf
$ protoc --version # Ensure compiler version is 3+
Go用のプロトコルコンパイラプラグインをインストール
プラグインを入れる。ついでにモジュールモードに明示的にしておく
$ export GO111MODULE=on # Enable module mode
$ go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.26
$ go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.1
protoのパスを参照できるようにしておく。(versionによってはデフォルトのパスで問題ない)
$ export PATH="$PATH:$(go env GOPATH)/bin"
protoファイルの作成
./proto以下にgreeter.protoを作成する
option go_packageに生成先を明示しないと私はうまくコンパイルできませんでした。将来的には必須のオプションになるとされています。
syntax = "proto3";
// メッセージ型などの名前の衝突を避けるためにパッケージ名を指定します。
package hello;
// コードが自動生成されるディレクトリを指定しています。
option go_package = "pb/";
service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
message HelloRequest {
string name = 1;
}
message HelloReply {
string message = 1;
}
プロトファイルをコンパイルする
pb.goファイルが生成されます
$ protoc --go_out=. --go-grpc_out=require_unimplemented_servers=false:. ./proto/greeter.proto
注: require_unimplemented_servers=false を明示しない場合、gRPCサーバに
mustEmbedUnimplementedPostServiceServerの実装が必要です。
gRPCサーバを作成する
# server/server.go
serviceは実処理の部分です。汚くてすみません。。
package main
import (
"context"
"log"
"net"
pb "github.com/leslesnoa/grpcdemo01/pb"
"google.golang.org/grpc/reflection"
"google.golang.org/grpc"
)
// ---service----
type GreeterService struct {
}
func (s *GreeterService) SayHello(ctx context.Context, message *pb.HelloRequest) (*pb.HelloReply, error) {
log.Printf("Received: %v", message.Name)
return &pb.HelloReply{Message: "Hello " + message.Name}, nil
}
// -------------
func main() {
addr := ":50051"
lis, err := net.Listen("tcp", addr)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
pb.RegisterGreeterServer(s, &GreeterService{})
reflection.Register(server)
log.Printf("gRPC server listening on " + addr)
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
gRPCクライアントを作成
# client/client.go
注意:grpc.WithInsecure()は認証しないで通信するという意味で動作確認などで使い、実運用では使用しません。
package main
import (
"context"
"log"
"os"
"time"
"github.com/leslesnoa/grpcdemo01/pb"
"google.golang.org/grpc"
)
func main() {
addr := "localhost:50051"
conn, err := grpc.Dial(addr, grpc.WithInsecure())
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
c := pb.NewGreeterClient(conn)
name := os.Args[1]
ctx := context.Background()
r, err := c.SayHello(ctx, &pb.HelloRequest{Name: name})
if err != nil {
log.Fatalf("could not greet: %v", err)
}
log.Printf("Greeting: %s", r.Message)
}
動作確認
server,clientのディレクトリに移動してサーバを起動すると、レスポンスが得られます
# /server
$ go run server.go
2021/11/12 23:46:25 gRPC server listening on :50051
# /client
引数に任意のメッセージを書いて実行する
$ go run client.go world
2021/11/12 23:17:57 Greeting: Hello world
想定通りの結果となりました!
gRPC CLIの導入方法
gRPC CLIを入れることで動作確認が簡単になるので導入方法を記載
$ brew tap grpc/grpc
$ brew install grpc
grpc_cli とすれば起動できます。
定義メソッドを参照できたりします。細かいところは調べてる途中です
$ grpc_cli ls localhost:50051 .Greeter
$ grpc_cli ls localhost:50051 .Greeter -l
Interceptorでログ出力してみる
Interceptor とは、gRPC のそれぞれの RPC の前後に処理を挟むことができる機能です。RPC 共通のログ出⼒や認証処理、統計⽤情報の収集などさまざまな応⽤ができます
Interceptor は次のように 4 つの種類があります。Client か Server の違いと、Unaryか Stream の違いで分かれています。Unary と Stream というのは「2.1 RPC」内の「4種類の RPC」です。
• Unary Client Interceptor
• Stream Client Interceptor
• Unary Server Interceptor
• Stream Server Interceptor
単純なInterceptorを作る
ここでは Hello, world のクライアント側に
Unary Client Interceptor を実装してみます。
この例では unaryClientInterceptor
という関数をつくり、実際の RPC の処理 (invoker) の前後にログ出⼒を追加しています。
関数を定義したあとは、grpc.Dial() の引数に grpc.WithUnaryInterceptor(...) を加えることで gRPC 接続に Interceptor を組み込んで完成です。
// server/server.go
package main
import (
"context"
"log"
"os"
"github.com/leslesnoa/grpcdemo01/pb"
"google.golang.org/grpc"
)
func main() {
addr := "localhost:50051"
conn, err := grpc.Dial(addr, grpc.WithInsecure(), grpc.WithUnaryInterceptor(unaryInterceptor))
// conn, err := grpc.Dial(addr, grpc.WithInsecure())
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
c := pb.NewGreeterClient(conn)
name := os.Args[1]
ctx := context.Background()
r, err := c.SayHello(ctx, &pb.HelloRequest{Name: name})
if err != nil {
log.Fatalf("could not greet: %v", err)
}
log.Printf("Greeting: %s", r.Message)
}
func unaryInterceptor(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
log.Printf("before call: %s, request: %+v", method, req)
err := invoker(ctx, method, req, reply, cc, opts...)
log.Printf("after call: %s, response: %+v", method, reply)
return err
}
サーバを起動してクライアント側を実行してみると、SayHelloの呼び出し前後でログが出ていることがわかります。
go run client.go world
2021/11/13 18:30:12 before call: /hello.Greeter/SayHello, request: name:"world"
2021/11/13 18:30:12 after call: /hello.Greeter/SayHello, response: message:"Hello world"
2021/11/13 18:30:12 Greeting: Hello world
Interceptorはさまざまな応用ができるため、もっと詳しく知りたい場合は以下を参照すること
gRPC便利ツール
grpcurlを使ってみる
grpcurlはgRPCサーバに対してcurlのように通信が行えるクライアントツールです。
$ brew install grpcurl
grpcurl で SayHello を呼び出してみる
それでは、grpcurl を使っておなじみの SayHello を呼び出してみます。次のようにコマンドを打つと SayHello を呼ぶことができます。
引数やオプション指定がいくつかあるので、それぞれについて説明します。
grpcurl の引数・オプション例
-plaintext TLS を使わずに認証・暗号化なしで通信
-proto ... RPC を定義した.proto ファイルの場所
localhost:50051 接続先の名前 (IP アドレスとポート番号)
pb.Greeter/SayHello 呼び出す RPC の定義名
$ grpcurl -plaintext -proto greeter.proto localhost:50051 pb.Greeter/SayHello
{
"message": "Hello "
}
さらに-d ... オプションを加えると JSON 形式でパラメータを付けた呼び出しができます。実際に name パラメータに world を指定して送ると次のようになります。
grpcurl -plaintext -proto greeter.proto -d '{"name": "world"}' localhost:50051 hello.Greeter/SayHello
{
"message": "Hello world"
}
gRPCサーバの疎通確認ができました。
BloomRPC
grpcurl はコマンドラインから打ててお⼿軽ですが、引数やオプションが多くて少し覚えづらいところはあります。BloomRPC は GUI ベースの gRPCよび出しツールです。コマンドラインに⽐べてより視覚的で覚えやすいデザインとなっています。GitHub 上で公開されています。
◆使い⽅
• Import Protos から.proto ファイルを読み込む
• 左のパネルから呼び出したい RPC を選択
• 中央上の⼊⼒欄にサーバーの IP アドレスとポート番号を⼊⼒
• 中央左に送信するパラメータを JSON 形式で記述