前言
gRPC-Gateway 可以讓專案同時支援 gRPC 以及 HTTP API 的服務。
做法
建立專案。
1 2 3
   | mkdir grpc-gateway-go-example cd grpc-gateway-go-example go mod init
   | 
 
目錄結構如下:
1 2 3 4
   | |- client/ |- gen/ |- proto/ |- server/
   | 
 
安裝套件
安裝相關套件。
1 2 3 4 5
   | go install \     github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-grpc-gateway \     github.com/grpc-ecosystem/grpc-gateway/v2/protoc-gen-openapiv2 \     google.golang.org/protobuf/cmd/protoc-gen-go \     google.golang.org/grpc/cmd/protoc-gen-go-grpc
   | 
 
執行後,會在 $GOBIN 目錄生成以下執行檔:
1 2 3 4
   | protoc-gen-grpc-gateway protoc-gen-openapiv2 protoc-gen-go protoc-gen-go-grpc
   | 
 
定義服務
下載所需定義檔。
1 2
   | mkdir -p proto/google/api cp ../grpc-gateway/third_party/googleapis/google/api/* ./proto/google/api 
   | 
 
新增 hello.proto 檔:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
   | syntax = "proto3";
  option go_package = ".;hello";
  import "google/api/annotations.proto";
  service HelloService {   rpc SayHello (HelloRequest) returns (HelloResponse) {     option (google.api.http) = {       post: "/hello"       body: "*"     };   } }
  message HelloRequest {   string greeting = 1; }
  message HelloResponse {   string reply = 1; }
   | 
 
使用以下指令,生成 hello.pb.go 和 hello_grpc.pb.go 檔:
1 2 3 4
   | protoc -I ./proto \     --go_out=./gen \     --go-grpc_out=./gen \     ./proto/hello.proto
   | 
 
使用以下指令,生成 hello.pb.gw.go 檔:
1 2 3 4
   | protoc -I ./proto --grpc-gateway_out ./gen \     --grpc-gateway_opt logtostderr=true \     --grpc-gateway_opt paths=source_relative \     ./proto/hello.proto
   | 
 
實作服務端
在 server 資料夾新增 main.go 檔:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
   | package main
  import ( 	"context" 	"github.com/grpc-ecosystem/grpc-gateway/v2/runtime" 	gw "github.com/memochou1993/grpc-go-example/gen" 	"google.golang.org/grpc" 	"log" 	"net" 	"net/http" )
  const ( 	grpcServerEndpoint = ":8080" 	httpServerEndpoint = ":8890" )
  type service struct { 	gw.UnimplementedHelloServiceServer }
  func (s *service) SayHello(ctx context.Context, r *gw.HelloRequest) (*gw.HelloResponse, error) { 	log.Printf("Request received: %s", r.GetGreeting()) 	return &gw.HelloResponse{Reply: "Hello, " + r.GetGreeting()}, nil }
  func httpServer() { 	ctx := context.Background() 	mux := runtime.NewServeMux() 	opts := []grpc.DialOption{grpc.WithInsecure()} 	if err := gw.RegisterHelloServiceHandlerFromEndpoint(ctx, mux, grpcServerEndpoint, opts); err != nil { 		log.Fatalln(err.Error()) 	} 	log.Fatalln(http.ListenAndServe(httpServerEndpoint, mux)) }
  func grpcServer() { 	ln, err := net.Listen("tcp", grpcServerEndpoint) 	if err != nil { 		log.Fatalln(err.Error()) 	} 	s := grpc.NewServer() 	gw.RegisterHelloServiceServer(s, new(service)) 	log.Fatalln(s.Serve(ln)) }
  func main() { 	go grpcServer() 	httpServer() }
   | 
 
使用終端機執行服務端程式:
使用 curl 指令呼叫 API。
1 2
   | curl -d '{"greeting":"world"}' -H "Content-Type: application/json" -X POST http://localhost:8890/hello {"reply":"Hello, world"}
  | 
 
實作客戶端
在 client 資料夾新增 main.go 檔:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
   | package main
  import ( 	"context" 	pb "github.com/memochou1993/grpc-go-example" 	"google.golang.org/grpc" 	"log" 	"time" )
  func main() { 	ctx, cancel := context.WithTimeout(context.Background(), time.Second) 	defer cancel()
  	 	addr := "127.0.0.1:8080" 	conn, err := grpc.DialContext(ctx, addr, grpc.WithInsecure(), grpc.WithBlock()) 	if err != nil { 		log.Fatalln(err.Error()) 	} 	defer conn.Close()
  	c := pb.NewHelloServiceClient(conn) 	 	r, err := c.SayHello(ctx, &pb.HelloRequest{Greeting: "World!"}) 	if err != nil { 		log.Fatalln(err.Error()) 	} 	log.Printf("Response received: %s", r.GetReply()) }
   | 
 
使用終端機執行客戶端程式:
1 2
   | go run client/main.go Response received: Hello, World!
   | 
 
程式碼