grpc gateway 學習
紀錄grpc gateway 使用
安裝包
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
go install github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway@latest
clone google proto library
git clone https://github.com/googleapis/googleapis.git ~/googleapis
編寫proto
- 是先找到annotations.proto檔案,會在剛剛clone下來的底下,將它移到本地目錄,包括他使用到http.proto
synatx = "proto3";
option go_package = ".";
import "annotations.proto";
message EchoRequest {
string name = 1;
}
message EchoResponse {
string name = 1;
}
service Echo {
rpc Echo(EchoRequest) returns (EchoResponse){
option (google.api.http) = {
post: "/echo"
body: "*"
};
}
}
生成檔案
- 使用protoc
protoc \
-I . \
-I /path/to/googleapis \
--go_out . \
--go-grpc_out . \
--grpc-gateway_out . \
*.proto
撰寫http請求
- main.go
func main() {
flag.Parse()
go func() {
server.InitGrpcServer(*port)
}()
server.GrpcGatewayServer(*port)
}
- gateway.go
func GrpcGatewayServer(port int) {
localIP := tools.GetLocalIP()
ctxr := context.Background()
ctx, cancel := context.WithCancel(ctxr)
defer cancel()
mux := runtime.NewServeMux()
opts := []grpc.DialOption{grpc.WithTransportCredentials(insecure.NewCredentials())}
err := protobuf.RegisterEchoHandlerFromEndpoint(ctx, mux, fmt.Sprintf("%s:%d", localIP, port), opts)
tools.HandelError("register grpc gateway server", err)
tools.Log("register grpc gateway server success")
tools.Log(fmt.Sprintf("grpc gateway server listening on port %s:%d", localIP, 7111))
http.ListenAndServe(fmt.Sprintf("%s:%d", localIP, 7111), mux)
}
Request
curl -i -X POST -H "Content-Type: application/json" -d "{\"name\":\"peter\"}" http://:7111/echo
使用pool取代endpoint
Pool 核心取出UserClient
func GetGRPCUserClient(cfg *configs.EtcdGrpcCfg) protobuf.UserClient {
// ...
conn := pool.GetConn()
newClient := protobuf.NewUserClient(conn)
// ...
return newClient
}
將grpc service註冊到gateway上
conn := grpcclient.GetGRPCUserClient(gw.Cfg)
if err := pb.RegisterUserHandlerClient(ctx, mux, conn); err != nil {
tools.ErrorMsg(fmt.Sprintf("Failed to register handler for %s: %v", gw.ServiceName, err))
} else {
tools.Logf("Successfully registered handler for %s", gw.ServiceName)
}
分析RegisterUserHandlerClient
- 在他註解裡面有一段話
RegisterUserHandlerClient 將service User(在proto中定義)傳入mux中, 其中的handler將是在grpc endpoin中implement的UserClient Note: 傳入的UserClient如果不經過正規方式(creating a grpc client etc.),將不會自動掉用interceptors 並且client會忽略http middleware
- 解析核心code
- 其中code只有以下最主要的部分,其他為申明gateway的rpc path(在runtime.AnnotateContext中有分別)
- MarshalerForRequest會自動判斷header中的Content Type,如果沒有的話已*為定
- AnnotateContext 會儲存gprc請求得meta在context中,以便讓grpc流處理這些服務,類似http中的X-Forwarded-For, 但不同的是http主要用來追蹤原始ip,AnnotateContext則是將remoteAddr添加到context中
- forward_User_RegisterUser_0 則是將grpc中的resp藉由Restful返回
func RegisterUserHandlerClient(ctx context.Context, mux *runtime.ServeMux, client UserClient) error {
mux.Handle(http.MethodPost, pattern_User_RegisterUser_0, func(w http.ResponseWriter, req *http.Request, pathParams map[string]string) {
inboundMarshaler, outboundMarshaler := runtime.MarshalerForRequest(mux, req)
annotatedContext, err := runtime.AnnotateContext(ctx, mux, req, path, runtime.WithHTTPPathPattern(path))
annotatedContext = runtime.NewServerMetadataContext(annotatedContext, md)
forward_User_RegisterUser_0(annotatedContext, mux, outboundMarshaler, w, req, resp, mux.GetForwardResponseOptions()...)
})
為什麼要分析源碼
- 就由剛剛解析的結果可以知道,他會是依照傳入的client去註冊mux的client,因此沒辦法將所有service都開同一個port上,儘管皆為同一個service
獲得傳入的Token驗證
- 新增runtime.WithIncomingHeaderMatcher方法並傳入自己得驗證
- 將會對傳入的header做httpRequest,如果符合Matcher邏輯,將值傳入metadata中
mux := runtime.NewServeMux(runtime.WithIncomingHeaderMatcher(tools.Matcher))
func Matcher(key string) (string, bool) {
switch strings.ToLower(key) {
case "authorization":
return key, true
default:
return runtime.DefaultHeaderMatcher(key)
}
}