设计一个合理的 API 是一件不容易的事情,同时,API 还会产生各种不同的错误。为了能让 API 使用者对于 API 的错误有一个清晰的视图,定义一个标准的 RPC 错误类型是非常重要的事情。
考虑范围
一个合理的 RPC 错误,需要考虑如下几个方面。
- 包含错误码,错误信息
- 错误信息可扩展
- 考虑可读性
- 可解析性,即,用户可通过代码解析错误码,并采取有效行为
- 避免内部错误益处,例如,Nil point error
RPC 错误码结构
代码语言:txt复制{
"error":{
"code":500,
"status":"Internal Server Error",
"message":"Panic manually!",
"details":[]
}
}
例子
我们介绍 rk-boot 库,一个快速启动 GRPC 的企业级 golang 微服务框架。
通过 rk-boot ,用户可以轻松搭建 GRPC 微服务,里面同时集成了 Panic 捕捉以及标准错误类型。
- 安装
go get github.com/rookie-ninja/rk-boot
go get github.com/rookie-ninja/rk-grpc
返回自定义错误
在 GRPC 中,一般会配合 grpc-gateway 一起使用,这时候,会出现一个问题,GRPC 和 grpc-gateway 的错误映射。 我们要做到的是,让 grpc-gateway 始终对 GRPC 的错误保持一致的错误映射。
我们以 demo 代码为例子,在这个 demo 中,我们已经集成了 rk-boot。
注意,这里,在 boot.yaml 中需要开启 enableRkGwOption,才可以把 GRPC 错误映射成 grpc-gateway 错误。
- boot.yaml
---
grpc:
- name: greeter # Name of grpc entry
port: 8080 # Port of grpc entry
enabled: true # Enable grpc entry
enableReflection: true
enableRkGwOption: true
- main.go
// Copyright (c) 2021 rookie-ninja
//
// Use of this source code is governed by an Apache-style
// license that can be found in the LICENSE file.
package main
import (
"context"
"errors"
"github.com/rookie-ninja/rk-boot"
"github.com/rookie-ninja/rk-grpc/boot"
"github.com/rookie-ninja/rk-common/error"
"github.com/rookie-ninja/rk-demo/api/gen/v1"
"google.golang.org/grpc"
)
// Application entrance.
func main() {
// Create a new boot instance.
boot := rkboot.NewBoot()
// Get grpc entry with name
grpcEntry := boot.GetEntry("greeter").(*rkgrpc.GrpcEntry)
grpcEntry.AddRegFuncGrpc(registerGreeter)
grpcEntry.AddRegFuncGw(greeter.RegisterGreeterHandlerFromEndpoint)
// Bootstrap
boot.Bootstrap(context.Background())
// Wait for shutdown sig
boot.WaitForShutdownSig(context.Background())
}
func registerGreeter(server *grpc.Server) {
greeter.RegisterGreeterServer(server, &GreeterServer{})
}
type GreeterServer struct{}
func (server *GreeterServer) Greeter(ctx context.Context, request *greeter.GreeterRequest) (*greeter.GreeterResponse, error) {
// Return error to client
return nil, rkerror.InvalidArgument("Invalid argument, triggered manually", errors.New("this is detail")).Err()
}
代码语言:txt复制$ go run main.go
- 给 grpc-gateway 发送请求
代码语言:txt复制我们可以看到,针对于 grpc-gateway 的请求,rk-boot 把 GRPC 的错误,映射成了 400 错误。同时,Details 里,添加了两条信息。GRPC 错误 我们自行添加的错误(这里,rk-boot 会默认把错误代码设置成 Unknown)
curl "localhost:8080/v1/greeter?name=rk-dev"
{
"error":{
"code":400,
"status":"Bad Request",
"message":"Invalid argument, triggered manually",
"details":[
{
"code":3,
"status":"InvalidArgument",
"message":"[from-grpc] Invalid argument, triggered manually"
},
{
"code":2,
"status":"Unknown",
"message":"this is detail"
}
]
}
}
- 给 GRPC 发送请求
代码语言:txt复制Details 里,添加了两条信息。GRPC 错误 我们自行添加的错误(这里,rk-boot 会默认把错误代码设置成 Unknown)
$ grpcurl -plaintext localhost:8080 api.v1.Greeter.Greeter
ERROR:
Code: InvalidArgument
Message: Invalid argument, triggered manually
Details:
1) {"@type":"type.googleapis.com/rk.api.v1.ErrorDetail","code":3,"message":"[from-grpc] Invalid argument, triggered manually","status":"InvalidArgument"}
2) {"@type":"type.googleapis.com/rk.api.v1.ErrorDetail","code":2,"message":"this is detail","status":"Unknown"}
捕获 Panic(系统崩溃)
我们还是以 demo 代码为例子。
在 RPC 实现中,我们试着让系统崩溃,看看 rk-boot 会如何自动捕获,并且返回何种信息给用户。
注意,这里,在 boot.yaml 中需要开启 enableRkGwOption,才可以把 GRPC 错误映射成 grpc-gateway 错误。
- boot.yaml
---
grpc:
- name: greeter # Name of grpc entry
port: 8080 # Port of grpc entry
enabled: true # Enable grpc entry
enableReflection: true
enableRkGwOption: true
- main.go
// Copyright (c) 2021 rookie-ninja
//
// Use of this source code is governed by an Apache-style
// license that can be found in the LICENSE file.
package main
import (
"context"
"fmt"
"github.com/rookie-ninja/rk-boot"
"github.com/rookie-ninja/rk-demo/api/gen/v1"
"google.golang.org/grpc"
)
// Application entrance.
func main() {
// Create a new boot instance.
boot := rkboot.NewBoot()
// Get grpc entry with name
grpcEntry := boot.GetGrpcEntry("greeter")
grpcEntry.AddRegFuncGrpc(registerGreeter)
grpcEntry.AddRegFuncGw(greeter.RegisterGreeterHandlerFromEndpoint)
// Bootstrap
boot.Bootstrap(context.Background())
// Wait for shutdown sig
boot.WaitForShutdownSig(context.Background())
}
func registerGreeter(server *grpc.Server) {
greeter.RegisterGreeterServer(server, &GreeterServer{})
}
type GreeterServer struct{}
func (server *GreeterServer) Greeter(ctx context.Context, request *greeter.GreeterRequest) (*greeter.GreeterResponse, error) {
panic("Panic manually!")
return &greeter.GreeterResponse{
Message: fmt.Sprintf("Hello %s!", request.Name),
}, nil
}
代码语言:txt复制$ go run main.go
- 给 grpc-gateway 发送请求
代码语言:txt复制我们可以看到,针对于 grpc-gateway 的请求,rk-boot 把 GRPC 的错误,映射成了 500 错误。
curl "localhost:8080/v1/greeter?name=rk-dev"
{
"error":{
"code":500,
"status":"Internal Server Error",
"message":"Panic manually!",
"details":[]
}
}
- 给 GRPC 发送请求
$ grpcurl -plaintext localhost:8080 api.v1.Greeter.Greeter
ERROR:
Code: Internal
Message: Panic manually!
GRPC 对应 grpc-gateway 错误映射
可参考:https://github.com/googleapis/googleapis/blob/master/google/rpc/code.proto
源代码
rk-boot 里对于错误的处理,实现在了 rk-common/error 中。
更多例子
请参考:https://github.com/rookie-ninja/rk-grpc/tree/master/example/interceptor/panic/client 获取更多例子。