Golangでgrpc-gateway使ってみた
前回の記事で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ヘッダ有)
![f:id:yuki-toida:20181113150712p:plain](https://assets.st-note.com/img/1734688633-vqZcLWfe7M.png?width=1200)
![f:id:yuki-toida:20181113150845p:plain](https://assets.st-note.com/img/1734688634-Tsd9yp5GFd.png?width=1200)
![f:id:yuki-toida:20181113151100p:plain](https://assets.st-note.com/img/1734688635-wFbUs79xxB.png?width=1200)
動いてそうだ、これを実際にプロダクションで使うとなるとモノリスからマイクロサービスになるだろうし、.protoファイルどこに置くとか、サービス間の通信部分の実装どこに書くとか、色々複雑になってきそうだけど、やりきれたら楽しそうな気配がある。
GitHub - yuki-toida/grpc-gateway-sample