自由帳

とりとめのない学習メモです。主に Web サービスのシステム基盤や運用に関することを書いています

gRPC の Go の QuickStart チュートリアルをやった時の備忘

gRPC の Go の QuickStart チュートリアルをやった時のメモです。

プロトコルバッファコンパイラのインストール

プロトコルバッファコンパイラをインストールします。

$ sudo apt install -y protobuf-compiler

protoc コマンドが利用可能になります。

% protoc --version
libprotoc 3.14.0

プロトコル バッファ コンパイラ(protoc コマンド) は C++ で記述されており、これだけだと C++ に対してしか使えません。 各言語向けのコードを生成するには, 各言語のプロトコルバッファコンパイラプラグインをインストールする必要があります。

Go の場合は以下です。

$ go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28
$ go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2

protoc コンパイラプラグインを見つけられるように PATH を更新します。

$ export PATH="$PATH:$(go env GOPATH)/bin"

サンプルを触ってみる

helloworld/helloworld/helloworld.proto を見てみます。

syntax = "proto3";

option go_package = "google.golang.org/grpc/examples/helloworld/helloworld";
option java_multiple_files = true;
option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";

package helloworld;

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
}

// The request message containing the user's name.
message HelloRequest {
  string name = 1;
}

// The response message containing the greetings
message HelloReply {
  string message = 1;
}

Protocol Buffer v3の基本構文

ここでProtocol Buffer v3の構文を確認してみます。

Message 定義

Protocol Buffer v3 の Message の定義の基本形は以下のようです。

message Message型 {
    型 フィールド名 = フィールド番号
    型 フィールド名 = フィールド番号
    :
}

Service 定義

gRPCではこの機能を使い、プロトコル定義ファイル内で定義されたサービスに対応するコードを生成するようになっている。サービスは次のように定義できる。 Message を RPC で使用する場合は、 .proto ファイルで RPC Service Interface を定義することになり、これがあると、プロトコルバッファーコンパイラーによって、選択した言語でサービスインターフェースコードとスタブが生成されるようです。 * Language Guide (proto 3) | Protocol Buffers Documentation

service Service名 {
  rpc メソッド名(引数(リクエスト)となるMessage型型) returns (戻り値(レスポンス)のMessage型型);
}

Protocol Buffer v3を見たのでチュートリアルに戻ります。 helloworld/helloworld.pb.go に、プロトコルバッファーコンパイラー (protoc) によって生成されたと思われるコードもすでに配置されています。

チュートリアルにある通りにService定義にメソッドを追加してみます。

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
  // Sends another greeting
  rpc SayHelloAgain (HelloRequest) returns (HelloReply) {}
}

プロトコルバッファーコンパイラーでコンパイルして、gRPC コードを再生成します

% protoc --go_out=. --go_opt=paths=source_relative \
    --go-grpc_out=. --go-grpc_opt=paths=source_relative \
    helloworld/helloworld.proto

helloworld/helloworld.pb.go は以下のような差分になりました。 SayHelloAgain()メソッドが追加され、インターフェースにも定義されています。

@@ -1,22 +1,8 @@
-// Copyright 2015 gRPC authors.
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
 // Code generated by protoc-gen-go-grpc. DO NOT EDIT.
 // versions:
-// - protoc-gen-go-grpc v1.3.0
-// - protoc             v4.25.2
-// source: examples/helloworld/helloworld/helloworld.proto
+// - protoc-gen-go-grpc v1.2.0
+// - protoc             v3.14.0
+// source: helloworld/helloworld.proto

 package helloworld

@@ -32,16 +18,13 @@ import (
 // Requires gRPC-Go v1.32.0 or later.
 const _ = grpc.SupportPackageIsVersion7

-const (
-       Greeter_SayHello_FullMethodName = "/helloworld.Greeter/SayHello"
-)
-
 // GreeterClient is the client API for Greeter service.
 //
 // For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
 type GreeterClient interface {
        // Sends a greeting
        SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error)
+       SayHelloAgain(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error)
 }

 type greeterClient struct {
@@ -54,7 +37,16 @@ func NewGreeterClient(cc grpc.ClientConnInterface) GreeterClient {

 func (c *greeterClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) {
        out := new(HelloReply)
-       err := c.cc.Invoke(ctx, Greeter_SayHello_FullMethodName, in, out, opts...)
+       err := c.cc.Invoke(ctx, "/helloworld.Greeter/SayHello", in, out, opts...)
+       if err != nil {
+               return nil, err
+       }
+       return out, nil
+}
+
+func (c *greeterClient) SayHelloAgain(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) {
+       out := new(HelloReply)
+       err := c.cc.Invoke(ctx, "/helloworld.Greeter/SayHelloAgain", in, out, opts...)
        if err != nil {
                return nil, err
        }
@@ -67,6 +59,7 @@ func (c *greeterClient) SayHello(ctx context.Context, in *HelloRequest, opts ...
 type GreeterServer interface {
        // Sends a greeting
        SayHello(context.Context, *HelloRequest) (*HelloReply, error)
+       SayHelloAgain(context.Context, *HelloRequest) (*HelloReply, error)
        mustEmbedUnimplementedGreeterServer()
 }

@@ -77,6 +70,9 @@ type UnimplementedGreeterServer struct {
 func (UnimplementedGreeterServer) SayHello(context.Context, *HelloRequest) (*HelloReply, error) {
        return nil, status.Errorf(codes.Unimplemented, "method SayHello not implemented")
 }
+func (UnimplementedGreeterServer) SayHelloAgain(context.Context, *HelloRequest) (*HelloReply, error) {
+       return nil, status.Errorf(codes.Unimplemented, "method SayHelloAgain not implemented")
+}
 func (UnimplementedGreeterServer) mustEmbedUnimplementedGreeterServer() {}

 // UnsafeGreeterServer may be embedded to opt out of forward compatibility for this service.
@@ -100,7 +96,7 @@ func _Greeter_SayHello_Handler(srv interface{}, ctx context.Context, dec func(in
        }
        info := &grpc.UnaryServerInfo{
                Server:     srv,
-               FullMethod: Greeter_SayHello_FullMethodName,
+               FullMethod: "/helloworld.Greeter/SayHello",
        }
        handler := func(ctx context.Context, req interface{}) (interface{}, error) {
                return srv.(GreeterServer).SayHello(ctx, req.(*HelloRequest))
@@ -108,6 +104,24 @@ func _Greeter_SayHello_Handler(srv interface{}, ctx context.Context, dec func(in
        return interceptor(ctx, in, info, handler)
 }

+func _Greeter_SayHelloAgain_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
+       in := new(HelloRequest)
+       if err := dec(in); err != nil {
+               return nil, err
+       }
+       if interceptor == nil {
+               return srv.(GreeterServer).SayHelloAgain(ctx, in)
+       }
+       info := &grpc.UnaryServerInfo{
+               Server:     srv,
+               FullMethod: "/helloworld.Greeter/SayHelloAgain",
+       }
+       handler := func(ctx context.Context, req interface{}) (interface{}, error) {
+               return srv.(GreeterServer).SayHelloAgain(ctx, req.(*HelloRequest))
+       }
+       return interceptor(ctx, in, info, handler)
+}
+
 // Greeter_ServiceDesc is the grpc.ServiceDesc for Greeter service.
 // It's only intended for direct use with grpc.RegisterService,
 // and not to be introspected or modified (even as a copy)
@@ -119,7 +133,11 @@ var Greeter_ServiceDesc = grpc.ServiceDesc{
                        MethodName: "SayHello",
                        Handler:    _Greeter_SayHello_Handler,
                },
+               {
+                       MethodName: "SayHelloAgain",
+                       Handler:    _Greeter_SayHelloAgain_Handler,
+               },
        },
        Streams:  []grpc.StreamDesc{},
-       Metadata: "examples/helloworld/helloworld/helloworld.proto",
+       Metadata: "helloworld/helloworld.proto",
 }

greeter_server/main.go を修正します。

func (s *server) SayHelloAgain(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
        return &pb.HelloReply{Message: "Hello again " + in.GetName()}, nil
}

greeter_client/main.go を修正します。

r, err = c.SayHelloAgain(ctx, &pb.HelloRequest{Name: *name})
if err != nil {
        log.Fatalf("could not greet: %v", err)
}
log.Printf("Greeting: %s", r.GetMessage())

実行

% go run greeter_server/main.go
% go run greeter_client/main.go --name=Alice
Greeting: Hello Alice
Greeting: Hello again Alice

まとめ

gRPC の Go の QuickStart チュートリアルをやった時のログを記載しました。

参考