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
    1. 其中code只有以下最主要的部分,其他為申明gateway的rpc path(在runtime.AnnotateContext中有分別)
    2. MarshalerForRequest會自動判斷header中的Content Type,如果沒有的話已*為定
    3. AnnotateContext 會儲存gprc請求得meta在context中,以便讓grpc流處理這些服務,類似http中的X-Forwarded-For, 但不同的是http主要用來追蹤原始ip,AnnotateContext則是將remoteAddr添加到context中
    4. 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)
	}
}