gRPC チュートリアル 入門 ver.IntelliJ GoLand


Go言語を学び始めて、gRPCに興味が出ましたのでとりあえずチュートリアル的なことをしようとしたのですが、設定やらコマンドの書き方に結構苦労しましたのでメモ代わりに記録。
また、今回メモする内容はこちらのサイト「gRPC チュートリアルで入門しようぜ!」の内容を自身の環境に合わせた際にどういった設定をするのかなどを記載していますので、詳しい説明は本家のサイトを参照してください。

gRPCとは?

今回は理解するというよりも、触ってみようということも目的としていますので、詳しい説明は他の方々がわかりやすく説明してくださっていますので割愛させていただきます。
概要だけ少し書きますと、gRPC(ジーアールピーシー)はGoogleが2015年に開発したオープンソースのRPCです。RPC(Remote Procedure Call/遠隔手続き呼び出し)とは、ネットワーク上の他端末と通信するための仕組みのことで、これによって他端末のプログラムをリモートで実行することができるものになります。

サンプルコードを入手

  • terminalでGolandProjectsディレクトリ階層まで降りて下記のコマンドを実行し、サンプルコードをクローン。
$ git clone -b v1.35.0 https://github.com/grpc/grpc-go
  • クイックスタートサンプルディレクトリに移動。
$ cd grpc-go/examples/helloworld

examplesを実行

  • サーバーコードをコンパイルして実行します。
$ go run greeter_server/main.go
  • 別の端末から、クライアントコードをコンパイルして実行し、クライアントの出力を確認。 (私の場合は、GoLandの仮想端末でサーバー側を実行して、ホスト側のterminalでクライアント側を実行。)
$ go run greeter_client/main.go
2021/08/11 16:55:01 Greeting: Hello world

サーバー側の仮想端末では、下記のような表示が出力される。

2021/08/11 16:55:01 Received: world

grpcurl を使ってみる

greeter_client/main.go を使わずに API サーバにアクセスする手法も存在しております。
一般的に、リクエスト時の挙動確認としてcurlコマンドが使われるそうなのですが、何やら難しいそうですのでgrpcurlを使用しています。
grpcurlの詳しい説明はこちら→[備忘録] grpCurlの使い方

  • grpcurlをHomebrewでインストール
$ brew install grpcurl

インストールが成功したのを確認するために、下記のコマンドで確認。

$ grpcurl -help
Usage:
    grpcurl [flags] [address] [list|describe] [symbol]

The 'address' is only optional when used with 'list' or 'describe' and a
protoset or proto flag is provided.

If 'list' is indicated, the symbol (if present) should be a fully-qualified
service name. If present, all methods of that service are listed. If not
present, all exposed services are listed, or all services defined in protosets...
  • サーバー側を起動。
  • ホスト側のterminalで下記のコマンド実行。
$ grpcurl -plaintext localhost:50051 describe
grpc.reflection.v1alpha.ServerReflection is a service:
service ServerReflection {
  rpc ServerReflectionInfo ( stream .grpc.reflection.v1alpha.ServerReflectionRequest ) returns ( stream .grpc.reflection.v1alpha.ServerReflectionResponse );
}

helloworld.Greeter is a service:
service Greeter {
  rpc SayHello ( .helloworld.HelloRequest ) returns ( .helloworld.HelloReply );
}

grpc.reflection.v1alpha.ServerReflection という service に ServerReflectionInfo という RPC。
helloworld.Greeter という service に SayHello という RPC があることがわかる。

grpcurl を用いたリクエスト

SayHello へのリクエストは以下のように行う。(サーバー側は起動中)

$ grpcurl -plaintext -d '{"name": "Cat"}' localhost:50051 helloworld.Greeter/SayHello
{
  "message": "Hello Cat"
}

このようにJSON形式のレスポンスを出力する。
ServerReflectionInfo の方も同様。

$ grpcurl -plaintext -d '{"list_services": ""}' localhost:50051 grpc.reflection.v1alpha.ServerReflection/ServerReflectionInfo
{
  "originalRequest": {
    "listServices": ""
  },
  "listServicesResponse": {
    "service": [
      {
        "name": "grpc.reflection.v1alpha.ServerReflection"
      },
      {
        "name": "helloworld.Greeter"
      }
    ]
  }
}
$ grpcurl -plaintext -d '{"file_containing_symbol": "helloworld.Greeter"}' localhost:50051 grpc.reflection.v1alpha.ServerReflection/ServerReflectionInfo

{
  "originalRequest": {
    "fileContainingSymbol": "helloworld.Greeter"
  },
  "fileDescriptorResponse": {
    "fileDescriptorProto": [
      "ChBoZWxsb3dvcmxkLnByb3RvEgpoZWxsb3dvcmxkIiIKDEhlbGxvUmVxdWVzdBISCgRuYW1lGAEgASgJUgRuYW1lIiYKCkhlbGxvUmVwbHkSGAoHbWVzc2FnZRgBIAEoCVIHbWVzc2FnZTJJCgdHcmVldGVyEj4KCFNheUhlbGxvEhguaGVsbG93b3JsZC5IZWxsb1JlcXVlc3QaFi5oZWxsb3dvcmxkLkhlbGxvUmVwbHkiAEIwChtpby5ncnBjLmV4YW1wbGVzLmhlbGxvd29ybGRCD0hlbGxvV29ybGRQcm90b1ABYgZwcm90bzM="
    ]
  }
}

本家のサイトでは、この後にリクエストされてからレスポンスまでの大まかな流れが記載されていますので、
気になる方はそちらを確認してください。

Protocol Buffers 定義の更新

  • helloworld.protoファイルにage変数を追加。
 message HelloRequest {
   string name = 1;
+  int32 age = 2;
 }
  • 変更したprotoファイルをビルド。 以下のコマンドでビルドに必要なprotocコンパイラとGo言語用の protoc プラグインをインストール可能。
brew install protobuf
go get -u github.com/golang/protobuf/protoc-gen-go

example/helloworld/helloworldディレクトリまで移動して、以下のコマンドでコンパイルします。

& protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative helloworld.proto

ビルドが無事成功すると、helloworld.pd.protoファイル(44行目あたり)にAgeが追加されている。

// The request message containing the user's name.
type HelloRequest struct {
    state         protoimpl.MessageState
    sizeCache     protoimpl.SizeCache
    unknownFields protoimpl.UnknownFields

    Name     string `protobuf:"bytes,1,opt,name=name,proto3" json:"name,omitempty"`
    Age      int32  `protobuf:"varint,23,opt,name=age,proto3" json:"age,omitempty"`

  • greeter_server/main.go の更新

最後に server 側を更新。
(※ 更新後、再度サーバー側を起動しようとした際に私は以下のエラーが出ました。

$ go run greeter_server/main.go
grpcurl server does not support the reflection api

少し調べてみるとリフレクション機能を有効にしないとどうにも動かなそうな感じがしました。
ですので、下の方にある"reflection.Register(s)"を処理に加えております。
)

// Package main implements a server for Greeter service.
package main

import (
    "context"
+   "fmt"
    "log"
    "net"

    "google.golang.org/grpc"
    pb "google.golang.org/grpc/examples/helloworld/helloworld"
    "google.golang.org/grpc/reflection"
)

const (
    port = ":50051"
)

// server is used to implement helloworld.GreeterServer.
type server struct {
    pb.UnimplementedGreeterServer
}

// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
-   log.Printf("Received: %v", in.Name)
-   return &pb.HelloReply{Message: "Hello " + in.Name}, nil
+   log.Printf("Received: %v, %v", in.Name, in.Age)
+   return &pb.HelloReply{Message: fmt.Sprintf("%s is %d years old.", in.Name, in.Age)}, nil
}

func main() {
    lis, err := net.Listen("tcp", port)
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }
    s := grpc.NewServer()
        // ここの部分はcloneしてきた状態のコードのままだとコンパイルできないので下記に修正
    pb.RegisterGreeterServer(s, &server{})
    // Register reflection service on gRPC server.
    reflection.Register(s)
    if err := s.Serve(lis); err != nil {
        log.Fatalf("failed to serve: %v", err)
    }
}

修正が完了したら、APIサーバーを立ち上げたままの場合は、
いったん Ctrl + C で API サーバーを落としたのち、以下コマンドで再び起動させる。

go run greeter_server/main.go

サーバーを起動させたら、grpcurl を用いて確認。

grpcurl -plaintext -d '{"name": "Cathy", "age": 12}' localhost:50051 helloworld.Greeter/SayHello
{
  "message": "Cathy is 12 years old."
}

以上でQuick Start Guide をベースにしたチュートリアル終了。

参考ページ