使用 gRPC 错误类型 服务端返回 codes.PermissionDenined
错误:
1 2 3 4 5 6 7 import ( ... "google.golang.org/grpc/codes" "google.golang.org/grpc/status" ) ... return nil , status.Error(codes.PermissionDenied, "PERMISSION_DENIED_TEXT" )
客户端使用 status
库的 FromError
函数解析错误,使用swicth
语句判断错误类型并进行对应操作:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 // client assignvar, err := s.MyFunctionCall(ctx, ...) if err != nil { if e, ok := status.FromError(err); ok { switch e.Code() { case codes.PermissionDenied: fmt.Println(e.Message()) // this will print PERMISSION_DENIED_TEST case codes.Internal: fmt.Println("Has Internal Error") case codes.Aborted: fmt.Println("gRPC Aborted the call") default: fmt.Println(e.Code(), e.Message()) } } else { fmt.Printf("not able to parse error returned %v", err) } }
参考:https://github.com/grpc/grpc-go/issues/106 ,通过添加拦截器的方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 grpc.Dial(target, grpc.WithInsecure(), grpc.WithPerRPCCredentials(&loginCreds{ Username: "admin" , Password: "admin123" , })) type loginCreds struct { Username, Password string } func (c *loginCreds) GetRequestMetadata (context.Context, ...string ) (map [string ]string , error) { return map [string ]string { "username" : c.Username, "password" : c.Password, }, nil } func (c *loginCreds) RequireTransportSecurity () bool { return true } grpc.NewServer( grpc.StreamInterceptor(streamInterceptor), grpc.UnaryInterceptor(unaryInterceptor) ) func streamInterceptor (srv interface {}, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { if err := authorize(stream.Context()); err != nil { return err } return handler(srv, stream) } func unaryInterceptor (ctx context.Context, req interface {}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface {}, error) { if err := authorize(ctx); err != nil { return err } return handler(ctx, req) } func authorize (ctx context.Context) error { if md, ok := metadata.FromContext(ctx); ok { if len (md["username" ]) > 0 && md["username" ][0 ] == "admin" && len (md["password" ]) > 0 && md["password" ][0 ] == "admin123" { return nil } return AccessDeniedErr } return EmptyMetadataErr }
grpc-gateway 实现 gRPC server 提供 RESTful 实现在同一服务端的同一端口同时提供 gRPC 和 RESTful 服务,用于向后兼容及技术栈的平滑迁移。其基本原理是创建一个 HTTP 反向代理服务,将客户端的 HTTP 请求转换为 gRPC 客户端请求并向 gRPC 服务端发起调用。辅助命令工具的部署安装参考:https://grpc-ecosystem.github.io/grpc-gateway/docs/usage.html 。 会使用到以下命令:
1 2 3 4 5 6 7 8 9 protoc -I/usr/local/include -I. -I$GOPATH/src -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis --go_out=plugins=grpc:. service.proto protoc service.proto --grpc-web_out=import_style=typescript,mode=grpcwebtext:./ --js_out=import_style=commonjs:. protoc -I/usr/local/include -I. -I$GOPATH/src -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis --grpc-gateway_out=logtostderr=true :. service.proto protoc -I/usr/local/include -I. -I$GOPATH/src -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis --swagger_out=logtostderr=true :. service.proto
使用 gRPC 错误类型的另一个好处是 grpc-gateway 会自动将其转换为对应的 HTTP 状态码而不是每次出错都返回 500,参见 grpc-gateway 源码 。 grpc-gateway 需要使用 google.api.http,参考 https://blog.csdn.net/xiaojia1100/article/details/79447283 可参考的实例:
使用 Empty 类型 在 gRPC 中要求每个函数调用都有返回值,如果确实不需要返回值,则为了统一规范与重用,我们可以使用 google 提供的 Empty 类型,Empty 类型的对象在序列化和反序列化时会被视为空对象,其 JSON 形式表示为 {}
,使用方法:
1 2 3 4 import "google/protobuf/empty.proto" ;service Foo { rpc Bar(google.protobuf.Empty) returns (google.protobuf.Empty) ; }
传输文件 https://ops.tips/blog/sending-files-via-grpc/