Golangでgrpc-gateway使ってみた
- grpc-gateway とは
- 登場人物
- .protoから.bp.goと.bp.gw.goを生成する
- リバースプロキシサーバ実装
- echo/main.go サーバ実装
- auth/main.go サーバ実装
- 動作確認
前回の記事でgRPC
を使ったので、今回はgrpc-gateway
を使ってみる。
grpc-gateway とは
RESTfulなJSON APIをgRPCに変換するリバースプロキシを生成してくれるprotocのプラグイン。
RESTfulなJSON APIのバックエンドでgRPCを使いたいという案件に最適。
ドキュメント見ながらミニマムで実装してみる。
Usage | grpc-gateway
登場人物
1つのgateway(リバースプロキシサーバ)
に、2つのservice(gRPCサーバ)
がぶら下がっている構成で組む。
gateway/main.go
echo/main.go
- ポート9090
- gRPCで受けたパラメータをそのまま返すサーバ
auth/main.go
- ポート9091
Authorization
ヘッダが入ってないとエラーをレスポンス- gRPCで受けたパラメータをそのまま返すサーバ
.proto
から.bp.go
と.bp.gw.go
を生成する
.proto
から、gRPCのインターフェースを定義した.bp.go
を生成するコマンド。
protoc -I/usr/local/include -I. \ -I$GOPATH/src \ -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \ --go_out=plugins=grpc:. \ proto/*.proto
.proto
から、リバースプロキシとして動作するためのインターフェースを定義した.bp.gw.go
生成
protoc -I/usr/local/include -I. \ -I$GOPATH/src \ -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \ --grpc-gateway_out=logtostderr=true:. \ proto/*.proto
ちなみに、swagger
も吐き出せるので便利。
protoc -I/usr/local/include -I. \ -I$GOPATH/src \ -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \ --swagger_out=logtostderr=true:. \ proto/*.proto
リバースプロキシサーバ実装
echoとauthの2つのgRPCサーバへのプロキシサーバを
grpc-ecosystem.github.io
ここを見ながらリクエストをパイプランするmatcher
とfilter
も実装してみた、特に意味はない。
ポート9090、9091へプロキシ
package main import ( "flag" "fmt" "net/http" "github.com/golang/glog" "github.com/golang/protobuf/proto" "github.com/grpc-ecosystem/grpc-gateway/runtime" "golang.org/x/net/context" "google.golang.org/grpc" gw "github.com/yuki-toida/grpc-gateway-sample/proto" ) func main() { flag.Parse() defer glog.Flush() ctx, cancel := context.WithCancel(context.Background()) defer cancel() mux := runtime.NewServeMux(runtime.WithIncomingHeaderMatcher(matcher), runtime.WithForwardResponseOption(filter)) opts := []grpc.DialOption{grpc.WithInsecure()} if err := gw.RegisterEchoServiceHandlerFromEndpoint(ctx, mux, "localhost:9090", opts); err != nil { panic(err) } if err := gw.RegisterAuthServiceHandlerFromEndpoint(ctx, mux, "localhost:9091", opts); err != nil { panic(err) } if err := http.ListenAndServe(":8080", mux); err != nil { glog.Fatal(err) } } func matcher(headerName string) (string, bool) { ok := headerName != "Ignore" return headerName, ok } func filter(ctx context.Context, w http.ResponseWriter, resp proto.Message) error { w.Header().Set("X-Filter", "FilterValue") return nil }
echo/main.go サーバ実装
何の変哲もないgRPCサーバ、ポート9090でリッスン
package main import ( "context" "net" pb "github.com/yuki-toida/grpc-gateway-sample/proto" "google.golang.org/grpc" ) func main() { listener, err := net.Listen("tcp", ":9090") if err != nil { panic(err) } server := grpc.NewServer() pb.RegisterEchoServiceServer(server, &EchoServiceServer{}) server.Serve(listener) } type EchoServiceServer struct{} func (s *EchoServiceServer) Post(c context.Context, m *pb.Message) (*pb.Message, error) { return m, nil } func (s *EchoServiceServer) Get(c context.Context, p *pb.Param) (*pb.Param, error) { return p, nil }
auth/main.go サーバ実装
grpc.UnaryInterceptor(authentication)
でHTTPヘッダーにAuthorizationの有無を判定している
ポート9091でリッスン
package main import ( "context" "fmt" "net" "google.golang.org/grpc/codes" "google.golang.org/grpc/status" pb "github.com/yuki-toida/grpc-gateway-sample/proto" "google.golang.org/grpc" "google.golang.org/grpc/metadata" ) func main() { listener, err := net.Listen("tcp", ":9091") if err != nil { panic(err) } opts := []grpc.ServerOption{grpc.UnaryInterceptor(authentication)} server := grpc.NewServer(opts...) pb.RegisterAuthServiceServer(server, &AuthServiceServer{}) server.Serve(listener) } func authentication(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) { md, ok := metadata.FromIncomingContext(ctx) if !ok { return nil, status.Error(codes.Unauthenticated, "not found metadata") } values := md["authorization"] if len(values) == 0 { return nil, status.Error(codes.Unauthenticated, "not found metadata") } return handler(ctx, req) } type AuthServiceServer struct{} func (s *AuthServiceServer) Get(c context.Context, p *pb.Param) (*pb.Param, error) { return p, nil }
動作確認
echoサーバ
authサーバ (authorizationヘッダ無)
authサーバ (authorizationヘッダ有)
動いてそうだ、これを実際にプロダクションで使うとなるとモノリスからマイクロサービスになるだろうし、.protoファイル
どこに置くとか、サービス間の通信部分の実装どこに書くとか、色々複雑になってきそうだけど、やりきれたら楽しそうな気配がある。
GitHub - yuki-toida/grpc-gateway-sample