Go:gRPC-Gateway 完全指南

大家好,我是程序員幽鬼。

gRPC 越來越流行,相關的插件也很多,今天介紹的就是一個 gRPC 插件。


gRPC-Gateway 是一個插件,它爲 gRPC 服務生成反向代理服務器,將 Restful/JSON 轉換爲 gRPC,反之亦然。

換句話說,gRPC-Gateway 將在你的 gRPC 服務上創建一個層,該層將充當客戶端的 Restful/JSON 服務。gRPC-Gateway 從 gRPC 服務的 Protocol Buffer 的定義生成代碼。

1、介紹

gRPC-Gateway 是 protoc 的插件,將從 gRPC 定義生成 Go 代碼。

生成的代碼可以用作獨立服務器或安裝在現有代碼庫上。gRPC-Gateway 是高度可定製的,支持從 protoc 文件生成開放 API 文檔。

在本教程指南中,我們將詳細介紹獨立服務器以及與現有代碼的集成。查看此流程圖以瞭解 gRPC 網關的工作原理。

圖片

gRPC-Gateway flowchart diagram

2、爲什麼選擇 gRPC-Gateway?

gRPC 網關爲 gRPC 服務構建代理,該代理充當客戶端的 Restful/JSON 應用程序。它開啓了使用相同代碼庫同時支持 Restful/JSON 和 gRPC 的可能性。有兩個主要的場景:

  1. 舊版客戶端可能不支持 gRPC 並需要 Restful/JSON 接口

  2. 瀏覽器可能不支持開箱即用的 gRPC;因此對於想要與 gRPC 服務交互的 Web 客戶端,gRPC-Gateway 是首選選項。

最常見的 gRPC-Gateway 模式是創建單個 gRPC 網關服務器(可能在多臺機器上運行),作爲客戶端的代理與多個 gRPC 服務交互。

下圖解釋了此服務的工作原理。

圖片

gRPC-Gateway and service requests flowchart diagram

gRPC 網關生成的反向代理被水平擴展以在多臺機器上運行,並且在這些實例之前使用負載均衡器。單個實例可以託管多個 gRPC 服務的反向代理。

3、設置 gRPC 網關

gRPC-Gateway 是 protoc 的插件。在使用它之前,必須在系統上安裝 protocol buffer 編譯器。按照官方 gRPC 網站 [1] 上的指南,根據你使用的操作系統在你的系統上安裝 protoc。

gRPC-Gateway 使用並生成 Go 代碼。要安裝 Go,請按照官方 [2] 網站上的指南進行操作。只要你的系統上安裝了 Go,你就可以安裝 gRPC-Gateway 插件了。

創建一個名爲 grpc-gateway-demo 的目錄,該目錄將保存 gRPC-Gateway 項目。爲了構建 protocol buffer 和生成 gRPC 網關反向代理,將使用 Buf。你可以按照官方網站 [3] 上的指南安裝 Buf 。

項目結構

所有的 Protocol Buffers 文件都將在 proto目錄中,而 Go 文件將在 root。要設置 Go 項目,請使用 go mod init grpc-gateway-demo 並創建一個 main.go 文件。你的項目應如下所示:

├── main.go
├── go.mod
└── proto

配置 Buf

Buf 需要三個不同的文件來生成存根和反向代理。

buf.gen.yaml

這些文件指定編譯器應該使用的所有插件和相關選項。

使用 Buf,你可以簡單地在 YAML 文件中指定名稱和選項。Buf 還允許構建代碼使用遠程插件(即,指定的插件將在構建過程中由 Buf 自動下載並由本地系統上的 Buf 維護)。

version: v1
plugins:
  # generate go structs for protocol buffer defination
  - remote: buf.build/library/plugins/go:v1.27.1-1
    out: gen/go
    opt:
      - paths=source_relative
  # generate gRPC stubs in golang
  - remote: buf.build/library/plugins/go-grpc:v1.1.0-2
    out: gen/go
    opt:
      - paths=source_relative
  # generate reverse proxy from protocol definations
  - remote: buf.build/grpc-ecosystem/plugins/grpc-gateway:v2.6.0-1
    out: gen/go
    opt:
      - paths=source_relative
  # generate openapi documentation for api
  - remote: buf.build/grpc-ecosystem/plugins/openapiv2:v2.6.0-1
    out: gen/openapiv2
buf.yaml

該文件應位於所有 proto 文件的根目錄中。這些文件指定編譯 proto 文件(例如 Google API)所需的依賴項。

 version: v1
 deps:
 # adding well known types by google
  - buf.build/googleapis/googleapis
buf.work.yaml

此文件指定工作空間中包含 Protocol Buffer 定義的所有文件夾 / 目錄。

version: v1
directories:
  - proto

完成後,你的項目結構應與此類似:

├── buf.gen.yaml
├── buf.work.yaml
├── go.mod
├── main.go
└── proto
    ├── buf.yaml

在項目根目錄中你可以通過運行 buf build 命令來測試你的配置。

4、使用 gRPC 網關

到目前爲止,你已將 gRPC-Gateway 設置爲插件,但現在出現的問題是如何定義基本的 API 規範,如 HTTP 方法、URL 或請求正文。

爲了定義這些規範選項在 Protocol Buffers 上的 rpc 方法定義中使用的 service 內容,下面的示例將使其更加清晰。

proto/hello/hello_world.proto:

// define syntax used in proto file
syntax = "proto3";
// options used by gRPC golang plugin(not related to gRPC gateway)
option go_package = "github.com/anshulrgoyal/grpc-gateway-demo;grpc_gateway_demo";

// well know type by google, gRPC gateway uses HTTP annotation.
import "google/api/annotations.proto";

package hello_world;

// simple message
message HelloRequest {
  string name = 1;
}

message HelloReply {
  string message = 1;
}

// a gRPC service
service Greeter {
 // SayHello is a rpc call and a option is defined for it
  rpc SayHello (HelloRequest) returns (HelloReply) {
  // option type is http
    option (google.api.http) = {
    // this is url, for RESTfull/JSON api and method
    // this line means when a HTTP post request comes with "/v1/sayHello" call this rpc method over this service
      post: "/v1/sayHello"
      body: "*"
    };
  }
}

option關鍵字用於爲 Rest 請求添加規範。選擇該 option 方法並指定該請求的路徑。

在上面的例子中,post 是請求的 HTTP 方法,/v1/sayHello 是響應。

你現在可以在項目根目錄中使用 buf generate 命令來構建代碼。

命令完成後,項目的根目錄中應該有一個 gen 目錄,裏面有 Go 代碼。這些文件包含 gRPC 和 gRPC 網關反向代理的存根。openapiv2 包含 Swagger UI 的開放 API 文檔。

gen
|-- go
|   `-- hello
|       |-- hello_world.pb.go
|       |-- hello_world.pb.gw.go
|       `-- hello_world_grpc.pb.go
`-- openapiv2
    `-- hello
        `-- hello_world.swagger.json

實現服務

本教程將在 Go 中實現 gRPC 服務器。任何 gRPC 實現對於 gRPC 網關都可以正常工作。

使用 Go 的優點是你可以在同一進程中運行 gRPC 服務和 gRPC-Gateway 生成的代碼。這是 Go 的 Greeter 服務實現。

sever/main.go:

package main
import (
    "context"
    "fmt"
    "log"
    "net"
    // importing generated stubs
    gen "grpc-gateway-demo/gen/go/hello"
    "google.golang.org/grpc"
)
// GreeterServerImpl will implement the service defined in protocol buffer definitions
type GreeterServerImpl struct {
    gen.UnimplementedGreeterServer
}
// SayHello is the implementation of RPC call defined in protocol definitions.
// This will take HelloRequest message and return HelloReply
func (g *GreeterServerImpl) SayHello(ctx context.Context, request *gen.HelloRequest) (*gen.HelloReply, error) {
    return &gen.HelloReply{
        Message: fmt.Sprintf("hello %s",request.Name),
    },nil
}
func main() {
    // create new gRPC server
    server := grpc.NewServer()
    // register the GreeterServerImpl on the gRPC server
    gen.RegisterGreeterServer(server, &GreeterServerImpl{})
    // start listening on port :8080 for a tcp connection
    if l, err := net.Listen("tcp"":8080"); err != nil {
        log.Fatal("error in listening on port :8080", err)
    } else {
        // the gRPC server
        if err:=server.Serve(l);err!=nil {
            log.Fatal("unable to start server",err)
        }
    }
}

上述文件是 gRPC 服務的基本實現。它偵聽端口 8080。你可以在任何 gRPC 客戶端上對其進行測試。

在 gRPC 網關代理上註冊服務

gRPC 網關代理支持的每個 gRPC 服務器都需要在其上進行註冊。

在底層,gRPC 網關服務器將創建一個 gRPC 客戶端並使用它向提供的端點發出 gRPC 請求。你可以提供各種 DailOptions 註冊函數。

proxy/main.go

package main
import (
    "context"
    "log"
    "net"
    "net/http"
    "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
    "google.golang.org/grpc"
    gen "grpc-gateway-demo/gen/go/hello"
)

func main() {
    // creating mux for gRPC gateway. This will multiplex or route request different gRPC service
    mux:=runtime.NewServeMux()
    // setting up a dail up for gRPC service by specifying endpoint/target url
    err := gen.RegisterGreeterHandlerFromEndpoint(context.Background(), mux, "localhost:8080"[]grpc.DialOption{grpc.WithInsecure()})
    if err != nil {
        log.Fatal(err)
    }
    // Creating a normal HTTP server
    server:=http.Server{
        Handler: mux,
    }
    // creating a listener for server
    l,err:=net.Listen("tcp",":8081")
    if err!=nil {
        log.Fatal(err)
    }
    // start server
    err = server.Serve(l)
    if err != nil {
        log.Fatal(err)
    }
}

ServerMux 是一個多路複用器,它將根據 JSON/Restful 請求的路徑將請求路由到各種註冊服務。

grpc.WithInsecure() dial 選項用於允許服務在不使用身份驗證的情況下連接到 gRPC 。localhost:8080 是一個 gPRC 服務正在運行的 URL - 因爲 Greet(gRPC 服務構建之前看到)服務正在端口 8080 上運行,所以 localhost:8080 被使用。

一旦註冊了處理程序,mux 就可以處理 HTTP 請求了。在這裏,http 包中的 Go 標準 HTTP 服務器被使用。你也可以自由地使用其他實現,本文稍後將通過 gRPC 網關代理使用 Gin 來演示這一點 [4]。

ServerMux 實現 ServeHTTP 接口——它可以像Handler在 HTTP 服務器中一樣使用。服務在 8081 端口上運行。

要啓動服務,只需在項目目錄的根目錄中運行  go run proxy/main.go

使用路徑參數

現在,如果你想讓 v1/sayHello API 在 POST 調用中成爲 GET 調用並將數據作爲路徑參數傳遞,那麼在完成 gRPC 網關設置後,你無需更改代碼中的任何內容 — 只需更改協議緩衝區定義並重新生成存根,你都可以使用新的 API。

message HelloRequest {
  string name = 1;
}

service Greeter {
  rpc SayHello (HelloRequest) returns (HelloReply) {
    option (google.api.http) = {
     get:"/v1/sayHello/{name}"
    };
  }
}

上述代碼段中提供的路徑是 /v1/sayHello/{name}。你可以使用請求有效負載(HelloRequest 在本例中)中的任何鍵作爲路徑參數。如果你使用帶有 path 的 GET 請求/v1/sayHello/jane,該請求將被路由到 Greeter.sayHello gRPC 調用。你可以在 URL 中使用任意數量的路徑參數。

現在你對 gRPC 網關及其設置有了一些基本的瞭解。

我們使用的示例只是對 gRPC 網關的介紹,但要在生產環境中運行某些東西,你需要進行日誌記錄、跟蹤和錯誤處理。

5、常見的使用模式

對於任何準備好用於生產的系統,它都應該有一些錯誤處理並允許某種錯誤日誌記錄。

添加日誌記錄

本文的這一部分將演示如何將中間件與 gRPC 網關生成的代理一起使用。

ServerMux 實現了一個 Handler 接口,因此你可以使用任何中間件來包裝 ServerMux 和記錄傳入和輸出請求。

type Handler interface {
  ServeHTTP(ResponseWriter, *Request)
}

要創建用於日誌記錄的中間件,你可以從 *Request 中提取與 HTTP 請求相關的信息,並從正在使用的 httpsnoop 包中提取有關響應的信息。

func withLogger(handler http.Handler) http.Handler {
    // the create a handler
    return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
        // pass the handler to httpsnoop to get http status and latency
        m:=httpsnoop.CaptureMetrics(handler,writer,request)
        // printing exracted data
        log.Printf("http[%d]-- %s -- %s\n",m.Code,m.Duration,request.URL.Path)
    })
}

withLogger 方法將封裝 Handler 接口並調用 snoop 以提取信息。在後臺,該 ServerHTTP 方法由 httpsnoop 包調用。

server := http.Server{
  Handler: withLogger(mux),
}

這與 Go 生態系統中使用的任何其他處理程序沒有什麼不同。由於 ServerMux 是一個普通的處理程序,任何可用的中間件也可以與 gRPC 網關生成的反向代理一起使用。

錯誤處理

gRPC 網關已經帶有將 gRPC 錯誤代碼轉換爲客戶端使用的 HTTP 狀態的映射。例如,它會自動將衆所周知的和使用過的 gRPC 代碼映射到 HTTP 狀態。

InvalidArgument 轉換爲 400(錯誤請求)。如需完整列表,你可以查看此鏈接 [5]。如果你有自定義要求,例如需要非常規狀態代碼,則可以使用 WithErrorhandler 帶有錯誤處理函數的選項——所有錯誤都將通過請求和響應編寫器傳遞給該函數。

runtime.WithErrorHandler(
  func(ctx context.Context, mux *runtime.ServeMux, marshaler runtime.Marshaler, writer http.ResponseWriter, request *http.Request, err error) {}
)

錯誤處理函數獲取以下參數:

  1. ctx:上下文,保存有關執行的元數據

  2. mux:這是 ServerMux;它保存有關服務器的配置數據,例如應將哪個標頭傳遞給響應

  3. marshaler:將 Protocol Buffer 響應轉換爲 JSON 響應

  4. writer:這是客戶端的響應編寫器

  5. request:這請求包含客戶端發送的信息的對象

  6. err:gRPC 服務發送的錯誤

這是一個簡單的 WithErrorHandler 例子。在此示例中,無論錯誤如何,發生錯誤時請求的 HTTP 狀態都會更改爲 400

mux: = runtime.NewServeMux(
        runtime.WithErrorHandler(func(ctx context.Context, mux *runtime.ServeMux, marshaler runtime.Marshaler, writer http.ResponseWriter, request *http.Request, err error) {
            //creating a new HTTTPStatusError with a custom status, and passing error
            newError:=runtime.HTTPStatusError{
                HTTPStatus: 400,
                Err:        err,
            }
            // using default handler to do the rest of heavy lifting of marshaling error and adding headers
            runtime.DefaultHTTPErrorHandler(ctx,mux,marshaler,writer,request,&newError)
        }))

通過創建一個新錯誤並將其傳遞給 DefaultHTTPErrorHandler。重要的是要注意,DefaultHTTPErrorHandler 在將錯誤轉換爲有效的 JSON 響應時,在後臺執行了大量工作——儘可能嘗試使用它。

HTTP 頭和 gRPC 元數據

gRPC 和 Restful/JSON 以不同的方式傳遞元數據。

在 Restful/JSON HTTP 中,標頭用於發送 HTTP 頭,而 gRPC 通過根據所使用的語言提供元數據接口來抽象發送元數據。

gRPC 網關提供了一個簡單的映射接口來將 gRPC 元數據轉換爲 HTTP 標頭,反之亦然。它還允許使用兩種不同的方法來處理標頭到元數據的轉換。

首先,WithOutgoingHeaderMatcher 處理從 gRPC 網關返回到客戶端的標頭。它將元數據轉換爲 HTTP 標頭(即,任何由 gRPC 服務傳遞的元數據都將作爲 HTTP 標頭髮送回客戶端)。

var allowedHeaders=map[string]struct{}{
    "x-request-id"{},
}
func isHeaderAllowed(s string)( string,bool) {
// check if allowedHeaders contain the header
    if _,isAllowed:=allowedHeaders[s];isAllowed {
// send uppercase header
       return strings.ToUpper(s),true
    }
// if not in the allowed header, don't send the header
     return s, false
}
// usage
mux:=runtime.NewServeMux(
// convert header in response(going from gateway) from metadata received.
runtime.WithOutgoingHeaderMatcher(isHeaderAllowed))

此方法接受一個字符串,如果將標頭傳遞給客戶端,則返回 true,否則返回 false。

其次,WithMetadata處理傳入的 HTTP 標頭(即 cookie、內容類型等)。它最常見的用例是獲取身份驗證令牌並將其傳遞給元數據。此處提取的 HTTP 標頭將在元數據中發送到 gRPC 服務。

mux := runtime.NewServeMux(
  // handle incoming headers
  runtime.WithMetadata(func(ctx context.Context, request *http.Request) metadata.MD {
  header := request.Header.Get("Authorization")
  // send all the headers received from the client
  md := metadata.Pairs("auth",header)
  return md
}),

它接受一個請求並返回元數據的函數。請注意轉換爲元數據的標頭,因爲客戶端、瀏覽器、負載均衡器和 CDN 都在其中。gRPC 的密鑰也有一些限制。

這是一個完整的例子:

package main
import (
    "context"
    "log"
    "net"
    "net/http"
    "strings"
    "github.com/felixge/httpsnoop"
    "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
    "google.golang.org/grpc"
    "google.golang.org/grpc/metadata"
    gen "grpc-gateway-demo/gen/go/hello"
)
func withLogger(handler http.Handler) http.Handler {
    return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
        m:=httpsnoop.CaptureMetrics(handler,writer,request)
        log.Printf("http[%d]-- %s -- %s\n",m.Code,m.Duration,request.URL.Path)
    })
}
var allowedHeaders=map[string]struct{}{
    "x-request-id"{},
}
func isHeaderAllowed(s string)( string,bool) {
    // check if allowedHeaders contain the header
    if _,isAllowed:=allowedHeaders[s];isAllowed {
        // send uppercase header
        return strings.ToUpper(s),true
    }
    // if not in the allowed header, don't send the header
    return s, false
}
func main() {
    // creating mux for gRPC gateway. This will multiplex or route request different gRPC service
    mux := runtime.NewServeMux(
        // convert header in response(going from gateway) from metadata received.
        runtime.WithOutgoingHeaderMatcher(isHeaderAllowed),
        runtime.WithMetadata(func(ctx context.Context, request *http.Request) metadata.MD {
            header:=request.Header.Get("Authorization")
            // send all the headers received from the client
            md:=metadata.Pairs("auth",header)
            return md
        }),
        runtime.WithErrorHandler(func(ctx context.Context, mux *runtime.ServeMux, marshaler runtime.Marshaler, writer http.ResponseWriter, request *http.Request, err error) {
            //creating a new HTTTPStatusError with a custom status, and passing error
            newError:=runtime.HTTPStatusError{
                HTTPStatus: 400,
                Err:        err,
            }
            // using default handler to do the rest of heavy lifting of marshaling error and adding headers
            runtime.DefaultHTTPErrorHandler(ctx,mux,marshaler,writer,request,&newError)
        }))
    // setting up a dail up for gRPC service by specifying endpoint/target url
    err := gen.RegisterGreeterHandlerFromEndpoint(context.Background(), mux, "localhost:8080"[]grpc.DialOption{grpc.WithInsecure()})
    if err != nil {
        log.Fatal(err)
    }
    // Creating a normal HTTP server
    server:=http.Server{
        Handler: withLogger(mux),
    }
    // creating a listener for server
    l,err:=net.Listen("tcp",":8081")
    if err!=nil {
        log.Fatal(err)
    }
    // start server
    err = server.Serve(l)
    if err != nil {
        log.Fatal(err)
    }
}

查詢參數

默認支持查詢參數。你可以使用消息定義中的相同鍵將它們添加到路徑中。因此,如果你 HelloResponse 中有一個名爲 last_name 的密鑰,你可以輸入路徑 v1/sayHello/anshul?last_name=goyal 而無需更改網關代碼中的任何內容。

自定義響應

gRPC-Gateway 允許你在原始案例或 camelCase 中自定義響應中的鍵。。默認情況下它是 camelCase,但你可以編輯 Marshaler 配置來更改它。

mux := runtime.NewServeMux(runtime.WithMarshalerOption(runtime.MIMEWildcard, &runtime.HTTPBodyMarshaler{
            Marshaler: &runtime.JSONPb{
                MarshalOptions: protojson.MarshalOptions{
                    UseProtoNames:   true,
                    EmitUnpopulated: true,
                },
                UnmarshalOptions: protojson.UnmarshalOptions{
                    DiscardUnknown: true,
                },
            },
        }),)

6、將 gRPC-Gateway 與 Gin 一起使用

Gin 是一個非常流行的 Go web 框架。你可以將 gRPC-Gateway 與 Gin 一起使用,因爲它只是一個處理程序。它將允許你在你的服務器上添加可能不是由 gRPC-Gateway 生成的其他路由。

package main
import (
    "context"
    "log"
    "net/http"
    "strings"
    "github.com/gin-gonic/gin"
    "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
    "google.golang.org/grpc"
    "google.golang.org/grpc/metadata"
    gen "grpc-gateway-demo/gen/go/hello"
)
var allowedHeaders=map[string]struct{}{
    "x-request-id"{},
}
func isHeaderAllowed(s string)( string,bool) {
    // check if allowedHeaders contain the header
    if _,isAllowed:=allowedHeaders[s];isAllowed {
        // send uppercase header
        return strings.ToUpper(s),true
    }
    // if not in the allowed header, don't send the header
    return s, false
}
func main() {
    // creating mux for gRPC gateway. This will multiplex or route request different gRPC service
    mux:=runtime.NewServeMux(
        // convert header in response(going from gateway) from metadata received.
        runtime.WithOutgoingHeaderMatcher(isHeaderAllowed),
        runtime.WithMetadata(func(ctx context.Context, request *http.Request) metadata.MD {
            header:=request.Header.Get("Authorization")
            // send all the headers received from the client
            md:=metadata.Pairs("auth",header)
            return md
        }),
        runtime.WithErrorHandler(func(ctx context.Context, mux *runtime.ServeMux, marshaler runtime.Marshaler, writer http.ResponseWriter, request *http.Request, err error) {
            //creating a new HTTTPStatusError with a custom status, and passing error
            newError:=runtime.HTTPStatusError{
                HTTPStatus: 400,
                Err:        err,
            }
            // using default handler to do the rest of heavy lifting of marshaling error and adding headers
            runtime.DefaultHTTPErrorHandler(ctx,mux,marshaler,writer,request,&newError)
        }))
    // setting up a dail up for gRPC service by specifying endpoint/target url
    err := gen.RegisterGreeterHandlerFromEndpoint(context.Background(), mux, "localhost:8080"[]grpc.DialOption{grpc.WithInsecure()})
    if err != nil {
        log.Fatal(err)
    }
    // Creating a normal HTTP server
    server:=gin.New()
    server.Use(gin.Logger())
    server.Group("v1/*{grpc_gateway}").Any("",gin.WrapH(mux))
    // additonal route
    server.GET("/test", func(c *gin.Context) {
        c.String(http.StatusOK,"Ok")
    })

    // start server
    err = server.Run(":8081")
    if err != nil {
        log.Fatal(err)
    }
}

只需使用 gin.WrapH 帶有通配符路徑的方法,你就可以在服務器上使用 gin 了。如果需要,它允許你添加到服務器的路由。你還可以使用 HandlePath 將路由直接添加到 ServerMux 。

err = mux.HandlePath("GET""test", func(w http.ResponseWriter, r *http.Request, pathParams map[string]string) {
    w.Write([]byte("ok")
})

7、在同一端口上運行反向代理和 gRPC 服務

可以在一個端口上運行這兩種服務。你可以通過使用 cmux 包來做到這一點。

cmux 將通過區分使用的協議來拆分 gRPC 流量和 RestFull/JSON,因爲 gRPC 將使用 HTTP2,而 RestFull/JSON 將使用 HTTP1。

package main
import (
    "context"
    "fmt"
    "log"
    "net"
    "net/http"
    "github.com/felixge/httpsnoop"
    "github.com/grpc-ecosystem/grpc-gateway/v2/runtime"
    "github.com/soheilhy/cmux"
    // importing generated stubs
    gen "grpc-gateway-demo/gen/go/hello"
    "google.golang.org/grpc"
)
// GreeterServerImpl will implement the service defined in protocol buffer definitions
type GreeterServerImpl struct {
    gen.UnimplementedGreeterServer
}
// SayHello is the implementation of RPC call defined in protocol definitions.
// This will take HelloRequest message and return HelloReply
func (g *GreeterServerImpl) SayHello(ctx context.Context, request *gen.HelloRequest) (*gen.HelloReply, error) {
    if err:=request.Validate();err!=nil {
        return nil,err
    }
    return &gen.HelloReply{
        Message: fmt.Sprintf("hello %s %s",request.Name,request.LastName),
    },nil
}
func main() {
    // create new gRPC server
    grpcSever := grpc.NewServer()
    // register the GreeterServerImpl on the gRPC server
    gen.RegisterGreeterServer(grpcSever, &GreeterServerImpl{})
    // creating mux for gRPC gateway. This will multiplex or route request different gRPC service
    mux:=runtime.NewServeMux()
    // setting up a dail up for gRPC service by specifying endpoint/target url
    err := gen.RegisterGreeterHandlerFromEndpoint(context.Background(), mux, "localhost:8081"[]grpc.DialOption{grpc.WithInsecure()})
    if err != nil {
        log.Fatal(err)
    }
    // Creating a normal HTTP server
    server:=http.Server{
        Handler: withLogger(mux),
    }
    // creating a listener for server
    l,err:=net.Listen("tcp",":8081")
    if err!=nil {
        log.Fatal(err)
    }
    m := cmux.New(l)
    // a different listener for HTTP1
    httpL := m.Match(cmux.HTTP1Fast())
    // a different listener for HTTP2 since gRPC uses HTTP2
    grpcL := m.Match(cmux.HTTP2())
    // start server
    // passing dummy listener
    go server.Serve(httpL)
    // passing dummy listener
    go grpcSever.Serve(grpcL)
    // actual listener
    m.Serve()
}
func withLogger(handler http.Handler) http.Handler {
    return http.HandlerFunc(func(writer http.ResponseWriter, request *http.Request) {
        m:=httpsnoop.CaptureMetrics(handler,writer,request)
        log.Printf("http[%d]-- %s -- %s\n",m.Code,m.Duration,request.URL.Path)
    })
}

8、結論

本教程解釋了爲你的 gRPC 服務構建出色的 gRPC-Gateway 反向代理所需的所有要素。

自 gRPC-Gateway 以來,ServerMux 現在只是一個處理程序,你可以通過添加更多中間件(如主體壓縮、身份驗證和恐慌處理)來構建它。

你還可以使用 gRPC 網關配置。所有的代碼示例都可以在這裏 [6] 找到。

原文鏈接:https://dev.to/logrocket/an-all-in-one-guide-to-grpc-gateway-4g11

參考資料

[1]

官方 gRPC 網站: https://grpc.io/docs/protoc-installation/

[2]

官方: https://go.dev/doc/install

[3]

你可以按照官方網站: https://docs.buf.build/installation

[4]

使用 Gin 來演示這一點: https://blog.logrocket.com/building-microservices-go-gin/

[5]

鏈接: https://github.com/grpc-ecosystem/grpc-gateway/blob/7094a052b3287b9e99f52d95230789ab34d2d7c4/runtime/errors.go#L36

[6]

這裏: https://github.com/anshulrgoyal/grpc-gateway-demo


歡迎關注「幽鬼」,像她一樣做團隊的核心。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/uUj2eIoDnpss2RvzovhqKQ