1. 程式人生 > >go語言gRPC系列(三) - 使用grpc-gateway同時提供HTTP和gRPC服務

go語言gRPC系列(三) - 使用grpc-gateway同時提供HTTP和gRPC服務

- [1. gRPC提供HTTP服務](#head1) - [1.1 存在的意義](#head2) - [1.2 程式碼示例](#head3) - [1.3 使用postman嘗試呼叫](#head4) - [1.4 gRPC客戶端程式碼呼叫](#head5) - [2. 使用grpc-gateway同時提供HTTP和gRPC服務](#head6) - [2.1 前言](#head7) - [2.2 安裝](#head8) - [2.3 目錄結構](#head9) - [2.4 示例程式碼](#head10) - [2.4.1 編寫proto描述檔案:proto/hello_http.proto](#head11) - [2.4.2 編譯proto](#head12) - [2.4.3 實現HTTP服務端](#head13) - [2.4.4 實現gRPC服務端](#head14) - [2.4.5 實現客戶端](#head15) - [2.5 執行並呼叫](#head16) ## 1. gRPC提供HTTP服務 ### 1.1 存在的意義 在某些場景下單純的RPC服務不能滿足提供的服務需求的話,還是需要提供HTTP服務作為補充,gRPC一樣可以提供`HTTP`服務。 - **注意:gRPC提供的HTTP介面是基於`HTTP 2.0`的** ### 1.2 程式碼示例 ```go package main import ( "fmt" "gomicro-quickstart/grpc_server/service" "google.golang.org/grpc" "google.golang.org/grpc/credentials" "log" "net/http" ) func main() { // 1. 引用證書 tls, err := credentials.NewServerTLSFromFile("grpc_server/keys/server.crt", "grpc_server/keys/server_no_password.key") if err != nil { log.Fatal("服務端獲取證書失敗: ", err) } // 2. new一個grpc的server,並且加入證書 rpcServer := grpc.NewServer(grpc.Creds(tls)) // 3. 將剛剛我們新建的ProdService註冊進去 service.RegisterProdServiceServer(rpcServer, new(service.ProdService)) // 4. 新建一個路由,並傳入rpcServer mux := http.NewServeMux() mux.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) { fmt.Println(request) rpcServer.ServeHTTP(writer, request) }) // 5. 定義httpServer,監聽8082 httpServer := http.Server{ Addr: ":8082", Handler: mux, } // 6. 以https形式監聽httpServer httpServer.ListenAndServeTLS("grpc_server/keys/server.crt", "grpc_server/keys/server_no_password.key") } ``` ### 1.3 使用postman嘗試呼叫 執行上述的程式碼,然後`postman`訪問`8082`埠,提示訪問這個介面需要http/2協議 ![](http://pic.codepie.fun/picgo/20200815181112.png) ### 1.4 gRPC客戶端程式碼呼叫 針對[上一節的客戶端呼叫的程式碼](https://www.cnblogs.com/baoshu/p/13507260.html#head11),我們不需要修改即可以直接訪問 > 即直接呼叫protoc產生的go檔案中的方法 我們服務端程式碼因為打印出了,http request的內容 ![](http://pic.codepie.fun/picgo/20200815181616.png) 所以我們檢視一下通過客戶端呼叫,會打印出什麼,可以看到 - 請求的路徑是`/service.ProdService/GetProductStock`,是`{服務名}/{方法名}`的格式 - 協議是:http/2 ![](http://pic.codepie.fun/picgo/20200815181959.png) ## 2. 使用grpc-gateway同時提供HTTP和gRPC服務 ### 2.1 前言 某些場景下需要同時要提供`REST API服務`和`gRPC服務`,維護兩個版本的服務顯然不太合理,所以[grpc-gateway](https://github.com/grpc-ecosystem/grpc-gateway)誕生了。 **原理**:通過protobuf的自定義option實現了一個閘道器,服務端同時開啟**gRPC**和**HTTP 1.1**服務,HTTP服務接收客戶端請求後轉換為grpc請求資料,獲取響應後轉為json資料返回給客戶端。 **按照官方的結構說明如圖**: ![](http://pic.codepie.fun/picgo/20200815183001.png) ### 2.2 安裝 執行安裝以下三個 ```shell go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-grpc-gateway go get -u github.com/grpc-ecosystem/grpc-gateway/protoc-gen-swagger go get -u github.com/golang/protobuf/protoc-gen-go ``` ### 2.3 目錄結構 這裡用到了google官方Api中的兩個proto描述檔案,直接拷貝不要做修改,裡面定義了`protocol buffer`擴充套件`的HTTP option`,為grpc的http轉換提供支援。 ![](http://pic.codepie.fun/picgo/20200815222233.png) ``` |—— hello_http/ |—— client/ |—— main.go // 客戶端 |—— server/ |—— main.go // GRPC服務端 |—— server_http/ |—— main.go // HTTP服務端 |—— proto/ |—— google // googleApi http-proto定義 |—— api |—— annotations.proto |—— annotations.pb.go |—— http.proto |—— http.pb.go |—— hello_http/ |—— hello_http.proto // proto描述檔案 |—— hello_http.pb.go // proto編譯後文件 |—— hello_http_pb.gw.go // gateway編譯後文件 ``` ### 2.4 示例程式碼 #### 2.4.1 編寫proto描述檔案:proto/hello_http.proto 在`SayHello`方法定義中增加了`http option, POST`方式,路由為`/example/echo` ``` syntax = "proto3"; package hello_http; option go_package = "hello_http"; import "google/api/annotations.proto"; // 定義Hello服務 service HelloHTTP { // 定義SayHello方法 rpc SayHello(HelloHTTPRequest) returns (HelloHTTPResponse) { // http option option (google.api.http) = { post: "/example/echo" body: "*" }; } } // HelloRequest 請求結構 message HelloHTTPRequest { string name = 1; } // HelloResponse 響應結構 message HelloHTTPResponse { string message = 1; } ``` #### 2.4.2 編譯proto ```shell $ cd proto # 編譯google.api $ protoc -I . --go_out=plugins=grpc,Mgoogle/protobuf/descriptor.proto=github.com/golang/protobuf/protoc-gen-go/descriptor:. google/api/*.proto # 編譯hello_http.proto $ protoc -I . --go_out=plugins=grpc,Mgoogle/api/annotations.proto=github.com/jergoo/go-grpc-example/proto/google/api:. hello_http/*.proto # 編譯hello_http.proto gateway $ protoc --grpc-gateway_out=logtostderr=true:. hello_http/hello_http.proto ``` #### 2.4.3 實現HTTP服務端 ```go package main import ( "net/http" "github.com/grpc-ecosystem/grpc-gateway/runtime" "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/grpclog" gw "github.com/jergoo/go-grpc-example/proto/hello_http" ) func main() { // 1. 定義一個context ctx := context.Background() ctx, cancel := context.WithCancel(ctx) defer cancel() // grpc服務地址 endpoint := "127.0.0.1:50052" mux := runtime.NewServeMux() opts := []grpc.DialOption{grpc.WithInsecure()} // HTTP轉grpc err := gw.RegisterHelloHTTPHandlerFromEndpoint(ctx, mux, endpoint, opts) if err != nil { grpclog.Fatalf("Register handler err:%v\n", err) } grpclog.Println("HTTP Listen on 8080") http.ListenAndServe(":8080", mux) } ``` #### 2.4.4 實現gRPC服務端 ```go package main import ( "fmt" "net" "net/http" pb "github.com/jergoo/go-grpc-example/proto/hello" // 引入編譯生成的包 "golang.org/x/net/context" "golang.org/x/net/trace" "google.golang.org/grpc" "google.golang.org/grpc/grpclog" ) const ( // Address gRPC服務地址 Address = "127.0.0.1:50052" ) // 定義helloService並實現約定的介面 type helloService struct{} // HelloService Hello服務 var HelloService = helloService{} // SayHello 實現Hello服務介面 func (h helloService) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloResponse, error) { resp := new(pb.HelloResponse) resp.Message = fmt.Sprintf("Hello %s.", in.Name) return resp, nil } func main() { listen, err := net.Listen("tcp", Address) if err != nil { grpclog.Fatalf("failed to listen: %v", err) } // 例項化grpc Server s := grpc.NewServer() // 註冊HelloService pb.RegisterHelloServer(s, HelloService) grpclog.Println("Listen on " + Address) s.Serve(listen) } ``` #### 2.4.5 實現客戶端 ```go package main import ( pb "github.com/jergoo/go-grpc-example/proto/hello" // 引入proto包 "golang.org/x/net/context" "google.golang.org/grpc" "google.golang.org/grpc/grpclog" ) const ( // Address gRPC服務地址 Address = "127.0.0.1:50052" ) func main() { // 連線 conn, err := grpc.Dial(Address, grpc.WithInsecure()) if err != nil { grpclog.Fatalln(err) } defer conn.Close() // 初始化客戶端 c := pb.NewHelloClient(conn) // 呼叫方法 req := &pb.HelloRequest{Name: "gRPC"} res, err := c.SayHello(context.Background(), req) if err != nil { grpclog.Fatalln(err) } grpclog.Println(res.Message) } ``` ### 2.5 執行並呼叫 依次開啟gRPC服務端和HTTP服務端 ```shel $ cd hello_http/server && go run main.go Listen on 127.0.0.1:50052 $ cd hello_http/server_http && go run main.go HTTP Listen on 8080 ``` 然後呼叫gRPC的客戶端 ``` $ cd hello_http/client && go run main.go Hello gRPC. # HTTP 請求 $ curl -X POST -k http://localhost:8080/example/echo -d '{"name": "gRPC-HTTP is working!"}' {"message":"Hello gRPC-HTTP is working!