", reply.Value)
}
我们可以看下status.FromError的返回结果:
- 如果 err 是由这个包产生的或者实现了方法
GRPCStatus() *Status
,返回相应的状态。
- 如果 err 为 nil,则返回带有代码的状态。OK 并且没有消息。
- 否则,err 是与此包不兼容的错误。 在这个情况下,返回一个 Status 结构是 code.Unknown 和 err 的 Error() 消息,并且ok为false。
我们重新执行下客户端代码:
go run helloclient/main.go
invoker request time duration: 1
2022/10/16 23:26:11 invalid arguments
exit status 1
可以看到,当服务端返回的是codes.InvalidArgument错误时,我们重新定义了错误。
3、获取grpc错误更详细的信息
当我们服务端返回grpc错误时,我们想带上一些自定义的详细错误信息,这个时候就可以像下面这样写:
func (h HelloService) Hello(ctx context.Context, args *String) (*String, error) {
time.Sleep(time.Second)
if args.GetValue() != "hello" {
errorStatus := status.New(codes.InvalidArgument, "请求参数错误")
details, err := errorStatus.WithDetails(&errdetails.BadRequest_FieldViolation{
Field: "string.value",
Description: fmt.Sprintf("expect hello, get %s", args.GetValue()),
})
if err != nil {
return nil, errorStatus.Err()
}
return nil, details.Err()
}
reply := &String{Value: "hello:" + args.GetValue()}
return reply, nil
}
我们重点看下WithDetails方法:
- 该方法传入一个proto.Message类型的数组,Message是一个protocol buffer的消息
- 返回一个新Status,并将提供的详细信息消息附加到Status
- 如果遇到任何错误,则返回 nil 和遇到的第一个错误
然后我们修改下客户端代码:
func unaryRpc(conn *grpc.ClientConn) {
client := helloservice.NewHelloServiceClient(conn)
ctx := context.Background()
md := metadata.Pairs("authorization", "mytoken")
ctx = metadata.NewOutgoingContext(ctx, md)
reply, err := client.Hello(ctx, &helloservice.String{Value: "f**k"})
if err != nil {
fromError, ok := status.FromError(err)
if !ok {
log.Fatal(err)
}
if fromError.Code() == codes.InvalidArgument {
// 获取错误的详细信息,因为详细信息返回的是数组,所以这里我们需要遍历
for _, detail := range fromError.Details() {
detail = detail.(*proto.Message)
log.Println(detail)
}
log.Fatal("invalid arguments")
}
}
log.Println("unaryRpc recv: ", reply.Value)
}
接着重启下服务端,运行下客户端代码:
go run helloclient/main.go
invoker request time duration: 1
2022/10/16 23:58:51 field:"string.value" description:"expect hello, get f**k"
2022/10/16 23:58:51 invalid arguments
exit status 1
可以看到详细信息打印出来了。
4、定义标准错误之外的错误
现实中我们可能会有这样的要求:
- 当grpc服务端是自定义错误时,客户端返回自定义错误
- 当grpc服务端返回的是标准错误时,客户端返回系统错误
我们可以创建一个自定义测错误类:
package xerr
import (
"fmt"
)
/**
常用通用固定错误
*/
type CodeError struct {
errCode uint32
errMsg string
}
//返回给前端的错误码
func (e *CodeError) GetErrCode() uint32 {
return e.errCode
}
//返回给前端显示端错误信息
func (e *CodeError) GetErrMsg() string {
return e.errMsg
}
func (e *CodeError) Error() string {
return fmt.Sprintf("ErrCode:%d,ErrMsg:%s", e.errCode, e.errMsg)
}
然后grpc服务端实现一个拦截器,目的是把自定义错误转换成grpc错误:
func LoggerInterceptor(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
resp, err = handler(ctx, req)
if err != nil {
causeErr := errors.Cause(err) // err类型
if e, ok := causeErr.(*xerr.CodeError); ok { //自定义错误类型
//转成grpc err
err = status.Error(codes.Code(e.GetErrCode()), e.GetErrMsg())
}
}
return resp, err
}
然后客户端处理错误代码的部分修改如下:
//错误返回
causeErr := errors.Cause(err) // err类型
if e, ok := caus