2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Google の protobuf で gRPC Server に REST のエンドポイントをマッピング

Last updated at Posted at 2020-09-15

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 ファイルの中身はこんな感じになっている。

grpc-go/examples/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;
}

REST API マッピング

helloworld.proto 修正

annotations.proto を import して rpc の定義に option (google.api.http) {...} を追記。

grpc-go/examples/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";

// 追記
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 に合わせてサーバーの実装を修正。

grpc-go/examples/helloworld/greeter_server/main.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 実装

grpc-go/examples/helloworld/greeter_proxy/main.go
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)
	}
}

動作検証

左:サーバーとプロキシ起動、右:リクエスト
スクリーンショット 2020-09-15 15.00.18.png

POSTやリクエストヘッダーの設定もできるみたいなので気になる方はどうぞ。
https://grpc-ecosystem.github.io/grpc-gateway/

参考

  1. もしくは、https://github.com/grpc-ecosystem/grpc-gateway/tree/master/third_party/googleapis

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?