gRPC gateway
Enable grpc-gateway.
Overview#
By default, gRPC and grpc-gateway wil use the same port if service was started by rk-boot. However, user can specify grpc.gwPort value in boot.yaml in order to use different ports. In this case seperated listener will be used.
Install#
go get github.com/rookie-ninja/rk-boot/v2
go get github.com/rookie-ninja/rk-grpc/v2
Options#
Name | Description | Type | Default |
---|---|---|---|
grpc.name | gRPC name | string | "" |
grpc.port | gRPC port | integer | 0 |
grpc.gwPort | gRPC gateway port | integer | same as grpc.port |
grpc.enabled | Enable gRPC | bool | false |
grpc.description | gRPC description | string | "" |
grpc.enableReflection | Enable gRPC reflection | boolean | false |
grpc.enableRkGwOption | Enable RK sytle grpc-gateway option which pass all headers to gRPC metadata | boolean | false |
grpc.noRecvMsgSizeLimit | gRPC server side message size limit | int | 4000000 |
Quick start#
1.Create and compile protocol buffer#
2.Create boot.yaml#
---
grpc:
- name: greeter
port: 8080
# gwPort: 8081 # Optional, default: gateway port will be the same as grpc port if not provided
enabled: true
enableReflection: true
enableRkGwOption: true
3.Create 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.Directory hierarchy#
$ 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.Validate#
$ 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.Enable RkServerOption#
---
grpc:
- name: greeter
port: 8080
enabled: true
enableRkGwOption: true
middleware:
logging:
enabled: true
2.Validate logs#
$ curl localhost:8080/v1/hello
gwMethod, gwPath, gwScheme, gwUserAgent will be in Event
------------------------------------------------------------------------ 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.Validate gRPC 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.Override gateway server option for marshaller#
Please refer bellow codes to override marshaller. - protobuf-go/encoding/protojson/encode.go - protobuf-go/encoding/protojson/decode.go
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#
Error mapping#
gRPC Code | gRPC Status | Gateway(Http) Code | Gateway(Http) Status |
---|---|---|---|
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.Validate error#
Expect 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.Validate error(grpc error)#
We will use status.New() to create gRPC error.
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"
}
]
}
}