定制框架错误处理
部分用户会问如何让调用端收到服务端对应的错误类型,这里解释一下,RPC 是通过协议进行通信,错误的处理也是基于协议,通常服务端返回 Error,RPC 框架统一进行错误编码返回回调用端,如果要让调用端返回和服务端一样的错误,需要定义一套错误码进行处理。但考虑到 RPC 并没有统一的错误码规范且内部的错误码不一定适用于外部用户,所以 Kitex 的开源部分没有暴露错误码定义,用户可以通过提供 ErrorHandler 来定制自己的错误处理。
建议使用方式
ErrorHandler 通过 client/server 的 Option 配置,但通常一个微服务体系会有统一的异常处理规范,如果是企业用户建议通过 Suite 封装定制 Option,服务开发者就不用具体关注异常处理的配置。
服务端配置
// server option
server.WithErrorHandler(yourServerErrorHandler)
该函数会在服务端 handler 执行后,中间件执行前被执行,可以用于给调用端返回自定义的错误码和信息。注意,虽然对此提供了支持,但业务层面自定义的错误码依然不建议通过 ErrorHandler 处理,因为我们希望将 RPC 错误和业务的错误能够区分开,RPC 错误表示一次RPC 请求失败,比如超时、熔断、限流,从 RPC 层面是失败的请求,但业务错误属于业务逻辑层面,在 RPC 层面其实是请求成功。Kitex 会制定一个业务自定义异常规范用于区分业务错误和 RPC 层面错误。
-
ErrorHandler 示例:
Kitex 对 server handler 返回的 error 统一封装为 kerrors.ErrBiz,如果要获取原始的 error 需要先进行 Unwrap。
// convert errors that can be serialized
func ServerErrorHandler(ctx context.Context, err error) error {
// if you want get other rpc info, you can get rpcinfo first, like `ri := rpcinfo.GetRPCInfo(ctx)`
// for example, get remote address: `remoteAddr := rpcinfo.GetRPCInfo(ctx).From().Address()`
if errors.Is(err, kerrors.ErrBiz) {
err = errors.Unwrap(err)
}
if errCode, ok := GetErrorCode(err); ok {
// for Thrift、KitexProtobuf
return remote.NewTransError(errCode, err)
}
return err
}
// convert errors that can be serialized
func ServerErrorHandler(ctx context.Context, err error) error {
if errors.Is(err, kerrors.ErrBiz) {
err = errors.Unwrap(err)
}
if errCode, ok := GetErrorCode(err); ok {
// for gRPC
// status use github.com/cloudwego/kitex/pkg/remote/trans/nphttp2/status
return status.Errorf(errCode, err.Error())
}
return err
}
调用端配置
// client option
client.WithErrorHandler(yourClientErrorHandler)
该 handler 在远程调用结束,中间件执行前被执行。框架有默认的 ClientErrorHandler,如果未配置将使用默认的,默认 Handler 的行为是:接收到服务端的错误返回或者调用端在传输层出现了异常,统一返回 ErrRemoteOrNetwork。另外,对于 Thrift 和 KitexProtobuf,error msg 会包含 ‘[remote]’ 信息用来标识这是对端的错误;对于 gRPC 如果对端通过 status.Error
构造的错误返回,本端使用 status.FromError(err)
可以获取 *status.Status
,注意 Status
需使用 Kitex 提供的,包路径是 github.com/cloudwego/kitex/pkg/remote/trans/nphttp2/status
。
- ErrorHandler 示例:
func ClientErrorHandler(ctx context.Context, err error) error {
// if you want get other rpc info, you can get rpcinfo first, like `ri := rpcinfo.GetRPCInfo(ctx)`
// for example, get remote address: `remoteAddr := rpcinfo.GetRPCInfo(ctx).To().Address()`
// for thrift、KitexProtobuf
if e, ok := err.(*remote.TransError); ok {
// TypeID is error code
return buildYourError(e.TypeID(), e)
}
// for gRPC
if s, ok := status.FromError(err); ok {
return buildYourErrorWithStatus(s.Code(), s)
}
return kerrors.ErrRemoteOrNetwork.WithCause(err)
}
错误码定义范围
因为部分错误码是框架内置的,所以使用者应当避开内置错误码,目前内置的错误码:
-
Thrift、KitexProtobuf:0 - 10。
-
gRPC:0 - 17。
ErrorHandler 执行机制
ErrorHandler 在 Middleware 中被执行,无论是调用端还是服务端 ErrorHandler 都作为最里层的 Middleware 被执行,如图所示: