Google が公開している googleapis1 のソースを使うと REST と gRPC の両プロトコルをサポートできるのでご紹介。
どうしても、HTTP/1.1 で通信したいという局面で知っておくと便利。わたしの場合、ALBで gRPC Server にフォワーディングするとき、ターゲットグループのヘルスチェック用のエンドポイントとして実装した。
そのときは envoy を使って Proxy 立てたのだけれど、今回は grpc-gateway を使ってくことにする。
とりあえず gRPCサーバーの準備
すぐ動くのが欲しかったので grpc-go の helloworld を拝借。
git clone https://github.com/grpc/grpc-go
# 以降も基本的にこのディレクトリで作業する
cd grpc-go/examples/helloworld/
# gRPCサーバー起動
go run greeter_server/main.go
# クライアントコード実行で動作確認
go run greeter_client/main.go
2020/09/14 23:02:48 Greeting: Hello world
.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;
}
REST API マッピング
helloworld.proto 修正
annotations.proto
を import して rpc の定義に option (google.api.http) {...}
を追記。
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";
// 追記
import "google/api/annotations.proto";
package helloworld;
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {
// 追記
option (google.api.http) = {
get: "/say_hello"
};
}
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
googleapis のソースを取ってくる
go get -u github.com/googleapis/googleapis
コンパイル時に参照したいだけなので git clone
とかでもOK
変更した protobuf を元に自動生成
コンパイラプラグインインストール
go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway
go get -u github.com/golang/protobuf/protoc-gen-go
gRPC Server 向け
このとき google/api/annotations.proto
を参照できるように protoc の引数に-I
のオプションを追加。 また、既存の helloworld_grpc.pb.go
と生成されたクライアントコードが重複するので申し訳ないが今回は消してしまう。
protoc \
-I. -I$GOPATH/src/github.com/googleapis/googleapis \
--go_out=plugins=grpc:. --go_opt=paths=source_relative \
helloworld/helloworld.proto
// 申し訳ないが消す
rm helloworld/helloworld_grpc.pb.go
Proxy Server 向け
grpc-gateway
のコンパイラプラグインを使って同様に自動生成。
protoc -I. -I$GOPATH/src/github.com/googleapis/googleapis \
--grpc-gateway_out=logtostderr=true,paths=source_relative:. \
helloworld/helloworld.proto
gRPC Server の実装を修正
生成された helloworld.pb.go
に合わせてサーバーの実装を修正。
package main
import (
"context"
"log"
"net"
"google.golang.org/grpc"
pb "google.golang.org/grpc/examples/helloworld/helloworld"
)
const (
port = ":50051"
)
// 追記
type server struct {}
// 修正: ポインタレシーバに修正
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
log.Printf("Received: %v", in.GetName())
return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
}
func main() {
lis, err := net.Listen("tcp", port)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
// 修正: server を代入
pb.RegisterGreeterServer(s, &server{})
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
Proxy Server 実装
package main
import (
"flag"
"net/http"
"github.com/golang/glog"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
"golang.org/x/net/context"
"google.golang.org/grpc"
gw "google.golang.org/grpc/examples/helloworld/helloworld"
)
var (
tasklistEndpoint = flag.String("tasklist_endpoint", "localhost:50051", "endpoint of YourService")
)
func run() error {
ctx := context.Background()
ctx, cancel := context.WithCancel(ctx)
defer cancel()
mux := runtime.NewServeMux()
opts := []grpc.DialOption{grpc.WithInsecure()}
err := gw.RegisterGreeterHandlerFromEndpoint(ctx, mux, *tasklistEndpoint, opts)
if err != nil {
return err
}
return http.ListenAndServe(":8080", mux)
}
func main() {
flag.Parse()
defer glog.Flush()
if err := run(); err != nil {
glog.Fatal(err)
}
}
動作検証
POSTやリクエストヘッダーの設定もできるみたいなので気になる方はどうぞ。
https://grpc-ecosystem.github.io/grpc-gateway/
参考
- https://github.com/grpc-ecosystem/grpc-gateway
- https://grpc.io/docs/languages/go/quickstart
- https://github.com/grpc/grpc-go
- https://budougumi0617.github.io/2018/02/03/grpc-gateway-for-rest-api/
- https://qiita.com/ryu3/items/b2882d4f45c7f8485030
- https://grpc-ecosystem.github.io/grpc-gateway/