From d246bb3214a2aeee214959a607aa476f1e39cca1 Mon Sep 17 00:00:00 2001 From: Adphi Date: Sun, 21 Nov 2021 16:13:43 +0100 Subject: [PATCH] options: fix interceptors, add defaulter and recovery --- README.md | 16 ++++-- example/example.go | 6 +- example/example.pb.defaults.go | 3 + example/example.pb.go | 55 +++++++++--------- example/example.pb.validate.go | 11 +++- example/example.proto | 3 +- interceptors/defaulter/interceptors.go | 77 ++++++++++++++++++++++++++ interceptors/recovery/interceptors.go | 29 ++++++++++ interceptors/validation/validation.go | 2 - service/options.go | 1 - service/service.go | 2 +- 11 files changed, 165 insertions(+), 40 deletions(-) create mode 100644 interceptors/defaulter/interceptors.go create mode 100644 interceptors/recovery/interceptors.go diff --git a/README.md b/README.md index feb66b3..b7cff08 100644 --- a/README.md +++ b/README.md @@ -9,28 +9,30 @@ Principles: Features: - [x] simple configuration with options -- [x] embeded gorm database with options (branch db) +- [x] embedded gorm database with options (branch db) - [x] simple TLS configuration - [ ] TLS auth - [ ] client connection pool - [ ] registry / resolver resolution - [ ] mdns - [ ] kubernetes -- [ ] default interceptors implementation: - - [ ] default - - [ ] validation +- [ ] default interceptors implementation: + - [ ] context request id + - [x] defaulter + - [x] validation - [ ] health - [ ] context logger - [ ] sentry - [ ] rate-limiting + - [ ] ban - [ ] auth claim in context - - [ ] recovery + - [x] recovery (server side only) - [x] tracing (open-tracing) - [x] metrics (prometheus) - [ ] retries - [ ] context DB / transaction - ... -- [ ] api gateway with middleware: +- [ ] grpc web / api gateway with middleware: - [ ] auth - [ ] cors - [ ] logging @@ -43,3 +45,5 @@ Features: - https://github.com/grpc-ecosystem/grpc-opentracing - https://github.com/grpc-ecosystem/go-grpc-prometheus - https://github.com/grpc-ecosystem/grpc-gateway +- https://github.com/jaredfolkins/badactor +- github.com/improbable-eng/grpc-web diff --git a/example/example.go b/example/example.go index 87ab274..af1c614 100644 --- a/example/example.go +++ b/example/example.go @@ -14,6 +14,7 @@ import ( "github.com/sirupsen/logrus" "go.linka.cloud/grpc/client" + "go.linka.cloud/grpc/interceptors/defaulter" metrics2 "go.linka.cloud/grpc/interceptors/metrics" validation2 "go.linka.cloud/grpc/interceptors/validation" "go.linka.cloud/grpc/logger" @@ -69,6 +70,7 @@ func main() { var err error metrics := metrics2.NewInterceptors() validation := validation2.NewInterceptors(true) + defaulter := defaulter.NewInterceptors() address := "0.0.0.0:9991" svc, err = service.New( service.WithContext(ctx), @@ -93,7 +95,7 @@ func main() { service.WithGRPCWeb(true), service.WithGRPCWebPrefix("/grpc"), service.WithMiddlewares(httpLogger), - service.WithInterceptors(validation, metrics), + service.WithInterceptors(metrics, defaulter, validation), ) if err != nil { panic(err) @@ -125,7 +127,7 @@ func main() { if err == nil { logrus.Fatal("expected validation error") } - stream, err := g.SayHelloStream(context.Background(), &HelloStreamRequest{Name: "test", Count: 10}) + stream, err := g.SayHelloStream(context.Background(), &HelloStreamRequest{Name: "test"}) if err != nil { logrus.Fatal(err) } diff --git a/example/example.pb.defaults.go b/example/example.pb.defaults.go index 37e6317..5e7044f 100644 --- a/example/example.pb.defaults.go +++ b/example/example.pb.defaults.go @@ -21,4 +21,7 @@ func (x *HelloReply) Default() { } func (x *HelloStreamRequest) Default() { + if x.Count == 0 { + x.Count = 10 + } } diff --git a/example/example.pb.go b/example/example.pb.go index 70a797f..b9c3d73 100644 --- a/example/example.pb.go +++ b/example/example.pb.go @@ -11,6 +11,7 @@ import ( sync "sync" _ "github.com/envoyproxy/protoc-gen-validate/validate" + _ "go.linka.cloud/protoc-gen-defaults/defaults" _ "google.golang.org/genproto/googleapis/api/annotations" protoreflect "google.golang.org/protobuf/reflect/protoreflect" protoimpl "google.golang.org/protobuf/runtime/protoimpl" @@ -180,32 +181,34 @@ var file_example_example_proto_rawDesc = []byte{ 0x72, 0x6c, 0x64, 0x1a, 0x1c, 0x67, 0x6f, 0x6f, 0x67, 0x6c, 0x65, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x61, 0x6e, 0x6e, 0x6f, 0x74, 0x61, 0x74, 0x69, 0x6f, 0x6e, 0x73, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x17, 0x76, 0x61, 0x6c, 0x69, 0x64, 0x61, 0x74, 0x65, 0x2f, 0x76, 0x61, 0x6c, 0x69, - 0x64, 0x61, 0x74, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x22, 0x2d, 0x0a, 0x0c, 0x48, 0x65, - 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x04, 0x6e, 0x61, - 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x09, 0xfa, 0x42, 0x06, 0x72, 0x04, 0x10, - 0x02, 0x18, 0x28, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x22, 0x26, 0x0a, 0x0a, 0x48, 0x65, 0x6c, - 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, - 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, - 0x65, 0x22, 0x49, 0x0a, 0x12, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, - 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, - 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x09, 0xfa, 0x42, 0x06, 0x72, 0x04, 0x10, 0x02, 0x18, 0x28, - 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, 0x14, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, - 0x02, 0x20, 0x01, 0x28, 0x03, 0x52, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x32, 0xb7, 0x01, 0x0a, - 0x07, 0x47, 0x72, 0x65, 0x65, 0x74, 0x65, 0x72, 0x12, 0x5e, 0x0a, 0x08, 0x53, 0x61, 0x79, 0x48, - 0x65, 0x6c, 0x6c, 0x6f, 0x12, 0x18, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, - 0x64, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, - 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x48, 0x65, 0x6c, 0x6c, - 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x22, 0x15, - 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, 0x2f, 0x67, 0x72, 0x65, 0x65, 0x74, 0x65, 0x72, 0x2f, - 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x3a, 0x01, 0x2a, 0x12, 0x4c, 0x0a, 0x0e, 0x53, 0x61, 0x79, 0x48, - 0x65, 0x6c, 0x6c, 0x6f, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x12, 0x1e, 0x2e, 0x68, 0x65, 0x6c, - 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x53, 0x74, 0x72, - 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x68, 0x65, 0x6c, - 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, - 0x6c, 0x79, 0x22, 0x00, 0x30, 0x01, 0x42, 0x22, 0x5a, 0x20, 0x67, 0x6f, 0x2e, 0x6c, 0x69, 0x6e, - 0x6b, 0x61, 0x2e, 0x63, 0x6c, 0x6f, 0x75, 0x64, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2f, 0x65, 0x78, - 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x3b, 0x6d, 0x61, 0x69, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, - 0x6f, 0x33, + 0x64, 0x61, 0x74, 0x65, 0x2e, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x1a, 0x17, 0x64, 0x65, 0x66, 0x61, + 0x75, 0x6c, 0x74, 0x73, 0x2f, 0x64, 0x65, 0x66, 0x61, 0x75, 0x6c, 0x74, 0x73, 0x2e, 0x70, 0x72, + 0x6f, 0x74, 0x6f, 0x22, 0x2d, 0x0a, 0x0c, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x12, 0x1d, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x42, 0x09, 0xfa, 0x42, 0x06, 0x72, 0x04, 0x10, 0x02, 0x18, 0x28, 0x52, 0x04, 0x6e, 0x61, + 0x6d, 0x65, 0x22, 0x26, 0x0a, 0x0a, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, + 0x12, 0x18, 0x0a, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, + 0x09, 0x52, 0x07, 0x6d, 0x65, 0x73, 0x73, 0x61, 0x67, 0x65, 0x22, 0x59, 0x0a, 0x12, 0x48, 0x65, + 0x6c, 0x6c, 0x6f, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, + 0x12, 0x1d, 0x0a, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x18, 0x01, 0x20, 0x01, 0x28, 0x09, 0x42, 0x09, + 0xfa, 0x42, 0x06, 0x72, 0x04, 0x10, 0x02, 0x18, 0x28, 0x52, 0x04, 0x6e, 0x61, 0x6d, 0x65, 0x12, + 0x24, 0x0a, 0x05, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x03, 0x42, 0x0e, + 0xfa, 0x42, 0x06, 0x22, 0x04, 0x18, 0x0a, 0x28, 0x01, 0x9a, 0x49, 0x02, 0x20, 0x0a, 0x52, 0x05, + 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x32, 0xb7, 0x01, 0x0a, 0x07, 0x47, 0x72, 0x65, 0x65, 0x74, 0x65, + 0x72, 0x12, 0x5e, 0x0a, 0x08, 0x53, 0x61, 0x79, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x12, 0x18, 0x2e, + 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, + 0x52, 0x65, 0x71, 0x75, 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, + 0x6f, 0x72, 0x6c, 0x64, 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, + 0x20, 0x82, 0xd3, 0xe4, 0x93, 0x02, 0x1a, 0x22, 0x15, 0x2f, 0x61, 0x70, 0x69, 0x2f, 0x76, 0x31, + 0x2f, 0x67, 0x72, 0x65, 0x65, 0x74, 0x65, 0x72, 0x2f, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x3a, 0x01, + 0x2a, 0x12, 0x4c, 0x0a, 0x0e, 0x53, 0x61, 0x79, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x53, 0x74, 0x72, + 0x65, 0x61, 0x6d, 0x12, 0x1e, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, + 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x53, 0x74, 0x72, 0x65, 0x61, 0x6d, 0x52, 0x65, 0x71, 0x75, + 0x65, 0x73, 0x74, 0x1a, 0x16, 0x2e, 0x68, 0x65, 0x6c, 0x6c, 0x6f, 0x77, 0x6f, 0x72, 0x6c, 0x64, + 0x2e, 0x48, 0x65, 0x6c, 0x6c, 0x6f, 0x52, 0x65, 0x70, 0x6c, 0x79, 0x22, 0x00, 0x30, 0x01, 0x42, + 0x22, 0x5a, 0x20, 0x67, 0x6f, 0x2e, 0x6c, 0x69, 0x6e, 0x6b, 0x61, 0x2e, 0x63, 0x6c, 0x6f, 0x75, + 0x64, 0x2f, 0x67, 0x72, 0x70, 0x63, 0x2f, 0x65, 0x78, 0x61, 0x6d, 0x70, 0x6c, 0x65, 0x3b, 0x6d, + 0x61, 0x69, 0x6e, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33, } var ( diff --git a/example/example.pb.validate.go b/example/example.pb.validate.go index 980331c..1f6a5ef 100644 --- a/example/example.pb.validate.go +++ b/example/example.pb.validate.go @@ -249,7 +249,16 @@ func (m *HelloStreamRequest) Validate(all bool) error { errors = append(errors, err) } - // no validation rules for Count + if val := m.GetCount(); val < 1 || val > 10 { + err := HelloStreamRequestValidationError{ + field: "Count", + reason: "value must be inside range [1, 10]", + } + if !all { + return err + } + errors = append(errors, err) + } if len(errors) > 0 { return HelloStreamRequestMultiError(errors) diff --git a/example/example.proto b/example/example.proto index 9f860d6..f7eadf2 100644 --- a/example/example.proto +++ b/example/example.proto @@ -6,6 +6,7 @@ option go_package = "go.linka.cloud/grpc/example;main"; import "google/api/annotations.proto"; import "validate/validate.proto"; +import "defaults/defaults.proto"; service Greeter { rpc SayHello (HelloRequest) returns (HelloReply) { @@ -27,5 +28,5 @@ message HelloReply { message HelloStreamRequest { string name = 1 [(validate.rules).string = {min_len: 2, max_len: 40}]; - int64 count = 2; + int64 count = 2 [(validate.rules).int64 = {gte: 1, lte: 10}, (defaults.value).int64 = 10]; } diff --git a/interceptors/defaulter/interceptors.go b/interceptors/defaulter/interceptors.go new file mode 100644 index 0000000..5098592 --- /dev/null +++ b/interceptors/defaulter/interceptors.go @@ -0,0 +1,77 @@ +package defaulter + +import ( + "context" + + "google.golang.org/grpc" + + "go.linka.cloud/grpc/interceptors" +) + +type interceptor struct{} + +func NewInterceptors() interceptors.Interceptors { + return &interceptor{} +} + +func defaults(v interface{}) { + if d, ok := v.(interface{ Default() }); v != nil && ok { + d.Default() + } +} + +func (i interceptor) UnaryServerInterceptor() grpc.UnaryServerInterceptor { + return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) { + defaults(req) + return handler(ctx, req) + } +} + +func (i interceptor) UnaryClientInterceptor() grpc.UnaryClientInterceptor { + return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error { + defaults(req) + return invoker(ctx, method, req, reply, cc, opts...) + } +} + +func (i interceptor) StreamServerInterceptor() grpc.StreamServerInterceptor { + return func(srv interface{}, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error { + wrapper := &recvWrapper{ServerStream: stream} + return handler(srv, wrapper) + } +} + +func (i interceptor) StreamClientInterceptor() grpc.StreamClientInterceptor { + return func(ctx context.Context, desc *grpc.StreamDesc, cc *grpc.ClientConn, method string, streamer grpc.Streamer, opts ...grpc.CallOption) (grpc.ClientStream, error) { + desc.Handler = (&sendWrapper{handler: desc.Handler}).Handler() + return streamer(ctx, desc, cc, method) + } +} + +type recvWrapper struct { + grpc.ServerStream +} + +func (s *recvWrapper) RecvMsg(m interface{}) error { + if err := s.ServerStream.RecvMsg(m); err != nil { + return err + } + defaults(m) + return nil +} + +type sendWrapper struct { + grpc.ServerStream + handler grpc.StreamHandler +} + +func (s *sendWrapper) Handler() grpc.StreamHandler { + return func(srv interface{}, stream grpc.ServerStream) error { + return s.handler(srv, s) + } +} + +func (s *sendWrapper) SendMsg(m interface{}) error { + defaults(m) + return s.ServerStream.SendMsg(m) +} diff --git a/interceptors/recovery/interceptors.go b/interceptors/recovery/interceptors.go new file mode 100644 index 0000000..2a5ea86 --- /dev/null +++ b/interceptors/recovery/interceptors.go @@ -0,0 +1,29 @@ +package recovery + +import ( + grpc_recovery "github.com/grpc-ecosystem/go-grpc-middleware/recovery" + "google.golang.org/grpc" +) + +type interceptors struct { + opts grpc_recovery.Option +} + +func (i *interceptors) UnaryServerInterceptor() grpc.UnaryServerInterceptor { + return grpc_recovery.UnaryServerInterceptor(i.opts) +} + +func (i *interceptors) StreamServerInterceptor() grpc.StreamServerInterceptor { + return grpc_recovery.StreamServerInterceptor(i.opts) +} + +func (i *interceptors) UnaryClientInterceptor() grpc.UnaryClientInterceptor { + panic("not implemented") +} + +func (i *interceptors) StreamClientInterceptor() grpc.StreamClientInterceptor { + panic("not implemented") +} + + + diff --git a/interceptors/validation/validation.go b/interceptors/validation/validation.go index 24196da..7ec87d5 100644 --- a/interceptors/validation/validation.go +++ b/interceptors/validation/validation.go @@ -133,11 +133,9 @@ func (s *recvWrapper) RecvMsg(m interface{}) error { if err := s.ServerStream.RecvMsg(m); err != nil { return err } - if err := s.i.validate(m); err != nil { return err } - return nil } diff --git a/service/options.go b/service/options.go index 43cb3ad..72d2bf7 100644 --- a/service/options.go +++ b/service/options.go @@ -370,7 +370,6 @@ type options struct { serverOpts []grpc.ServerOption - unaryServerInterceptors []grpc.UnaryServerInterceptor streamServerInterceptors []grpc.StreamServerInterceptor diff --git a/service/service.go b/service/service.go index fe939bc..aaf378e 100644 --- a/service/service.go +++ b/service/service.go @@ -110,7 +110,7 @@ func newService(opts ...Option) (*service, error) { ui := grpcmiddleware.ChainUnaryServer(s.opts.unaryServerInterceptors...) s.inproc = s.inproc.WithServerUnaryInterceptor(ui) - si := grpcmiddleware.ChainStreamServer( /*TODO(adphi): add to options*/ ) + si := grpcmiddleware.ChainStreamServer(s.opts.streamServerInterceptors... ) s.inproc = s.inproc.WithServerStreamInterceptor(si) gopts := []grpc.ServerOption{