本番環境で使えるgRPC-Gateway【前編】

[VISITS Technologies Advent Calendar 2019](https://qiita.com/advent-calendar/2019/visits) 3日目は@istshが担当します。

最近業務で、echoからgRPC-Gatewayに移行することになり、interceptorやmetadataについていろいろ調べたので、
`gRPC Serverの実装(前編)`と`gRPC Clientの実装(後編)`に分けてお送りしようと思います。

以降の説明は、[サンプルコード](https://github.com/istsh/go-grpc-sample)を使って行います。

# gRPC-Gatewayとは
gRPC-Gatewayとは、gRPCで書かれたAPIを、JSON over HTTPのAPIに変換して提供するためのミドルウェアです。 

![https___qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_317035_1efa8277-e25c-8a68-7956-e9460bd4626c.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/112715/2ec3cda0-f8ec-a80a-da96-19b9e3f1b42f.png)
(よく見る図)

# インストール
```
$ go get -u -v github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway
$ go get -u -v github.com/golang/protobuf/protoc-gen-go
```

[protoc-gen-validate](https://github.com/envoyproxy/protoc-gen-validate#installation)

```
$ # fetches this repo into $GOPATH
$ go get -d github.com/envoyproxy/protoc-gen-validate
$ # installs PGV into $GOPATH/bin
$ make build
```

# protoファイル
```user.proto
syntax = "proto3";

package v1;
option go_package = "github.com/istsh/go-grpc-sample/app/pb/v1";

import "google/api/annotations.proto";
import "validate/validate.proto";

service UserService {
    rpc CreateUser(CreateUserRequest) returns (CreateUserResponse) {
        option (google.api.http) = {
            post: "/v1/user"
            body: "*"
        };
    }
}

message CreateUserRequest {
    string email = 1 [(validate.rules).string = { min_len: 3, max_len: 254 }];
    string password = 2 [(validate.rules).string = { min_len: 8, max_len: 64 }];
}

message CreateUserResponse {
}
```

# goファイルを生成
```
$ protoc \
    proto/v1/login.proto \
    -I . \
    -I $GOPATH/src/github.com/envoyproxy/protoc-gen-validate \
    -I $GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \
    -I $GOPATH/src/github.com/grpc-ecosystem/grpc-gateway \
    --go_out=plugins=grpc:$GOPATH/src \
    --validate_out="lang=go:$GOPATH/src" \
    --grpc-gateway_out=logtostderr=true:$GOPATH/src
```
これを実行すると、`login.pb.go`と`login.pb.validate.go`が生成されるはずです。

# gRPC Serverの実装
ここからは実際のコードを見ながら解説していきます。
一部抜粋して説明するので、コード全体が見たい場合は[サンプルコード](https://github.com/istsh/go-grpc-sample)を見てください。

```app/cmd/server/main.go
package main

// 省略...

func main() {
	db := connectDB()
	defer db.Close()

	r := persistence.NewDBRepository(db)
	u := usecase.NewUserUsecase()

	listenPort, err := net.Listen("tcp", ":9090")
	if err != nil {
		logrus.Fatalln(err)
	}

	s := newGRPCServer(r, u)
	reflection.Register(s)
	s.Serve(listenPort)
}
```
上記のコードは、gRPC Serverをポート9090で起動するコードです。
DBの部分は説明する必要はないと思うので割愛します。

`newGRPCServer`が`*grpc.Server`を返してくれるので、それを用いて任意のポートでサーバーを起動します。
ではnewGRPCServerの実装を見てみましょう。

```app/cmd/server/main.go
func newGRPCServer(r repository.Repository, u usecase.UserUserCase) *grpc.Server {
	s := grpc.NewServer(
		grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(
			interceptor.RequestIDInterceptor(),
			interceptor.AuthenticationInterceptor(),
			grpc_validator.UnaryServerInterceptor(),
			grpc_recovery.UnaryServerInterceptor(),
		)),
	)

	pbv1.RegisterLoginServiceServer(s, server.NewLoginServiceServer(r, u))
	pbv1.RegisterUserServiceServer(s, server.NewUserServiceServer(r, u))

	return s
}
```
上記のコードは、インターセプターを4種類定義し、loginとuserで使うように設定しています。
`RegisterLoginServiceServer`と`RegisterUserServiceServer`は、前述のコマンドで生成したgoファイルに実装されているので、それを呼び出すだけです。
また、生成されたgoファイルには、`LoginServiceServer`や`UserServiceServer`といったインターフェースが定義されいます。
それを実装したうえでそれぞれを初期化する関数が`NewLoginServiceServer`や`NewUserServiceServer`になっています。

ここまではいろんな解説ページに載っていることですが、インターセプターに関しては情報が少ないのと、導入するプロジェクトに必要なインターフェースを選択したり、場合によっては実装することになるので、以降は上記の4種類のインターセプターについて解説します。

## RequestIDInterceptor
```requestid_interceptor.go
const (
	// XRequestIDKey is a key for getting request id.
	XRequestIDKey    = "X-Request-ID"
	unknownRequestID = "<unknown>"
)

// RequestIDInterceptor is a interceptor of access control list.
func RequestIDInterceptor() grpc.UnaryServerInterceptor {
	return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
		requestID := requestIDFromContext(ctx)
		ctx = context.WithValue(ctx, log.CtxRequestIDKey, requestID)
		return handler(ctx, req)
	}
}

func requestIDFromContext(ctx context.Context) string {
	md, ok := metadata.FromIncomingContext(ctx)
	if !ok {
		return unknownRequestID
	}

	key := strings.ToLower(XRequestIDKey)
	header, ok := md[key]
	if !ok || len(header) == 0 {
		return unknownRequestID
	}

	requestID := header[0]
	if requestID == "" {
		return unknownRequestID
	}

	return requestID
}
```
上記のインターセプターは、`context`から`metadata`を取得し、そこに`x-request-id`というキーがあれば、その値を返し、なければ`<unknown>`を返すというコードです。
`後編`で不足分は解説しますが、gRPC ClientとHTTP Request Headerなどの情報を連携する為にmetadataを使います。

## AuthenticationInterceptor
```authentication_interceptor.go
// Authenticator provides Authenticate method.
// Each service should implement this interface, otherwise, all requests will be rejected with authentication error.
type Authenticator interface {
	Authenticate(ctx context.Context, req interface{}) (context.Context, error)
}

// AuthenticationInterceptor is a interceptor of authentication.
func AuthenticationInterceptor() grpc.UnaryServerInterceptor {
	return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
		authenticator, ok := info.Server.(Authenticator)
		if !ok {
			// If Service doesn't implement Authenticator, return InternalServerError always.
			return nil, status.New(codes.Internal, "Authenticator is not implemented").Err()
		}

		ctx, err := authenticator.Authenticate(ctx, req)
		if err != nil {
			return nil, status.New(codes.Unauthenticated, fmt.Sprintf("Not authenticated: %v", err)).Err()
		}

		return handler(ctx, req)
	}
}
```
上記のインターセプターは、`Authenticate`インターフェースを実装したサービスで認証の処理を実行するコードです。
非常にシンプルですが、各サービス毎に認証の処理を実装できるようになっています。

## grpc_validator.UnaryServerInterceptor
これは`github.com/grpc-ecosystem/go-grpc-middleware/validator`のインターセプターです。
goファイルの生成コマンドで、`--validate_out`オプションをつけることで生成される`*.pb.validate.go`の検証処理をやってくれます。
protoファイルでvalidateを使っている場合はこのインターフェースは必ず使いましょう。

## grpc_recovery.UnaryServerInterceptor
これは`github.com/grpc-ecosystem/go-grpc-middleware/recovery`のインターセプターで、panicをハンドリングしてくれます。

## ChainUnaryServer
```go
s := grpc.NewServer(
    grpc.UnaryInterceptor(grpc_middleware.ChainUnaryServer(
        interceptor.RequestIDInterceptor(),
        interceptor.AuthenticationInterceptor(),
        grpc_validator.UnaryServerInterceptor(),
        grpc_recovery.UnaryServerInterceptor(),
    )),
)
```
これまでの4つのインターセプターを順番に実行するために、`grpc_middleware.ChainUnaryServer`を使います。

# まとめ
これでgRPC Server側の実装は一通り解説しました。
大切なのは、これは`gRPC Server`についての説明であって、`gRPC Client(gRPC-Gateway)`についての説明は別ということです。
また、コードをみて気がついたかもしれませんが、紹介したインターセプターはgRPC Server用のものです。
`後編`でgRPC Clientのインターセプター(ログ、リクエストIDの採番など)も解説するので、お楽しみに。