跳转至

gRPC gateway

启动 grpc-gateway

概述#

rk-boot 将会默认把 gRPC 和 grpc-gateway 的端口绑定到一个端口中。用户可以通过 grpc.gwPort 设置不同的端口。 这时候,gRPC 与 gRPC gateway 将会使用不同的 listener。

安装#

go get github.com/rookie-ninja/rk-boot/v2
go get github.com/rookie-ninja/rk-grpc/v2

通用选项#

名字 描述 类型 默认值
grpc.name gRPC 服务名称 string ""
grpc.port gRPC 服务端口 integer 0
grpc.gwPort gRPC gateway 服务端口 integer grpc.port 一致
grpc.enabled gRPC 服务启动开关 bool false
grpc.description gRPC 服务的描述 string ""
grpc.enableReflection 启动 gRPC 反射功能 boolean false
grpc.enableRkGwOption 启动 RK 自定义 Gateway Option,此 option 会默认透传所有 Header boolean false
grpc.noRecvMsgSizeLimit 从 gRPC 服务端取消 4MB 最大接收限制 int 4000000

快速开始#

1.创建并编译 protocol buffer#

使用 buf 编译 protocol buf

2.创建 boot.yaml#

---
grpc:
  - name: greeter
    port: 8080
#   gwPort: 8081                  # 可选项,如果不指定,会使用与 port 一样的端口
    enabled: true
    enableReflection: true
    enableRkGwOption: true

3.创建 main.go#

package main

import (
  "context"
  "github.com/rookie-ninja/rk-boot/v2"
  "github.com/rookie-ninja/rk-demo/api/gen/v1"
  "github.com/rookie-ninja/rk-grpc/v2/boot"
  "google.golang.org/grpc"
)

func main() {
  boot := rkboot.NewBoot()

  // register grpc
  entry := rkgrpc.GetGrpcEntry("greeter")
  entry.AddRegFuncGrpc(registerGreeter)
  entry.AddRegFuncGw(greeter.RegisterGreeterHandlerFromEndpoint)

  // Bootstrap
  boot.Bootstrap(context.TODO())

  // Wait for shutdown sig
  boot.WaitForShutdownSig(context.TODO())
}

func registerGreeter(server *grpc.Server) {
  greeter.RegisterGreeterServer(server, &GreeterServer{})
}

type GreeterServer struct{}

func (server *GreeterServer) Hello(_ context.Context, _ *greeter.HelloRequest) (*greeter.HelloResponse, error) {
  return &greeter.HelloResponse{
    Message: "hello!",
  }, nil
}

4.文件夹结构#

$ tree
.
├── Makefile
├── README.md
├── api
│   ├── gen
│      ├── google
│         ├── api
│            ├── annotations.pb.go
│            ├── http.pb.go
│            └── httpbody.pb.go
│         └── rpc
│             ├── code.pb.go
│             ├── error_details.pb.go
│             └── status.pb.go
│      └── v1
│          ├── greeter.pb.go
│          ├── greeter.pb.gw.go
│          ├── greeter.swagger.json
│          └── greeter_grpc.pb.go
│   └── v1
│       ├── greeter.proto
│       └── gw_mapping.yaml
├── boot.yaml
├── buf.gen.yaml
├── buf.yaml
├── go.mod
├── go.sum
├── main.go
└── third-party
    └── googleapis
        ├── LICENSE
        ├── README.grpc-gateway
        └── google
            ├── api
               ├── annotations.proto
               ├── http.proto
               └── httpbody.proto
            └── rpc
                ├── code.proto
                ├── error_details.proto
                └── status.proto

5.验证#

$ go run main.go
2022-04-17T18:15:55.603+0800    INFO    boot/grpc_entry.go:960  Bootstrap grpcEntry     {"eventId": "46a79118-2966-4b55-a062-8714e6ac54ac", "entryName": "greeter", "entryType": "gRPCEntry"}
------------------------------------------------------------------------
endTime=2022-04-17T18:15:55.603368+08:00
startTime=2022-04-17T18:15:55.603117+08:00
elapsedNano=251193
timezone=CST
ids={"eventId":"46a79118-2966-4b55-a062-8714e6ac54ac"}
app={"appName":"","appVersion":"","entryName":"greeter","entryType":"gRPCEntry"}
env={"arch":"amd64","domain":"*","hostname":"lark.local","localIP":"192.168.101.5","os":"darwin"}
payloads={"grpcPort":8080,"gwPort":8080}
counters={}
pairs={}
timing={}
remoteAddr=localhost
operation=Bootstrap
resCode=OK
eventStatus=Ended
EOE

Restful API

$ curl localhost:8080/v1/hello             
{"message":"hello!"}

gRPC

$ grpcurl -plaintext localhost:8080 api.v1.Greeter.Hello 
{
  "message": "hello!"
}

Cheers#

Gateway server option#

1.启动 rkServerOption#

---
grpc:
  - name: greeter
    port: 8080
    enabled: true
    enableRkGwOption: true
    middleware:
      logging:
        enabled: true

2.验证日志#

$ curl localhost:8080/v1/hello

gwMethod, gwPath, gwScheme, gwUserAgent 将会记录到日志中

------------------------------------------------------------------------
endTime=2021-07-09T21:03:43.518106+08:00
...
payloads={"grpcMethod":"Hello","grpcService":"api.v1.Greeter","grpcType":"unaryServer","gwMethod":"GET","gwPath":"/v1/hello","gwScheme":"http","gwUserAgent":"curl/7.64.1"}
...

3.验证传入的 metadata#

func (server *GreeterServer) Hello(ctx context.Context, _ *greeter.HelloRequest) (*greeter.HelloResponse, error) {
    fmt.Println(rkgrpcctx.GetIncomingHeaders(ctx))

    return &greeter.HelloResponse{
        Message: "hello!",
    }, nil
}
map[:authority:[0.0.0.0:8080] accept:[*/*] content-type:[application/grpc] user-agent:[grpc-go/1.44.1-dev] x-forwarded-for:[127.0.0.1] x-forwarded-host:[localhost:8080] x-forwarded-method:[GET] x-forwarded-path:[/v1/hello] x-forwarded-remote-addr:[127.0.0.1:49273] x-forwarded-scheme:[http] x-forwarded-user-agent:[curl/7.64.1]]

4.覆盖 gateway server option for marshaller#

在某些场景,我们希望覆盖掉默认的 gateway server option 的 marshaller。比如过,让 gateway 的返回值为下划线格式,而不是默认的驼峰格式。

请参考如下的源代码,rk-boot 的选项都是来源于如下 struct。

grpc:
  - name: greeter                                     # Required
    port: 8080                                        # Required
    enabled: true                                     # Required
    enableRkGwOption: true                            # Optional, default: false
    gwOption:                                         # Optional, default: nil
      marshal:                                        # Optional, default: nil
        multiline: false                              # Optional, default: false
        emitUnpopulated: false                        # Optional, default: false
        indent: ""                                    # Optional, default: false
        allowPartial: false                           # Optional, default: false
        useProtoNames: false                          # Optional, default: false
        useEnumNumbers: false                         # Optional, default: false
      unmarshal:                                      # Optional, default: nil
        allowPartial: false                           # Optional, default: false
        discardUnknown: false                         # Optional, default: false

Cheers#

错误映射#

grpc-gateway 中,我们需要了解 gateway 到 grpc 的错误映射,即 http 到 grpc 的错误映射。

这是默认的 grpc-gateway 中的错误映射

gRPC 错误码 GRPC 错误码描述 Gateway(Http) 错误码 Gateway(Http) 错误码描述
0 OK 200 OK
1 CANCELLED 408 Request Timeout
2 UNKNOWN 500 Internal Server Error
3 INVALID_ARGUMENT 400 Bad Request
4 DEADLINE_EXCEEDED 504 Gateway Timeout
5 NOT_FOUND 404 Not Found
6 ALREADY_EXISTS 409 Conflict
7 PERMISSION_DENIED 403 Forbidden
8 RESOURCE_EXHAUSTED 429 Too Many Requests
9 FAILED_PRECONDITION 400 Bad Request
10 ABORTED 409 Conflict
11 OUT_OF_RANGE 400 Bad Request
12 UNIMPLEMENTED 501 Not Implemented
13 INTERNAL 500 Internal Server Error
14 UNAVAILABLE 503 Service Unavailable
15 DATA_LOSS 500 Internal Server Error
16 UNAUTHENTICATED 401 Unauthorized

1.验证错误(标准 Go 语言错误)#

根据错误映射,500 将会返回。

func (server *GreeterServer) Greeter(ctx context.Context, request *greeter.GreeterRequest) (*greeter.GreeterResponse, error) {
    return nil, errors.New("error triggered manually")
}

$ curl localhost:8080/v1/hello
{
    "error":{
        "code":500,
        "status":"Internal Server Error",
        "message":"error triggered manually",
        "details":[]
    }
}

2.验证错误(grpc 错误)#

我们需要通过 status.New() 来创建 gRPC 错误。

我们推荐使用 rkgrpcerr 库中的函数来创建错误。

func (server *GreeterServer) Greeter(ctx context.Context, request *greeter.GreeterRequest) (*greeter.GreeterResponse, error) {
    return nil, rkgrpcerr.PermissionDenied("permission denied manually").Err()
}
$ curl localhost:8080/v1/hello
{
    "error":{
        "code":403,
        "status":"Forbidden",
        "message":"permission denied manually",
        "details":[
            {
                "code":7,
                "status":"PermissionDenied",
                "message":"[from-grpc] permission denied manually"
            }
        ]
    }
}

Cheers#