Skip to content

Commit 5ca0c41

Browse files
authored
Adjustments to README and consistency of callback options. (#555)
Signed-off-by: bwplotka <[email protected]>
1 parent 0e1142d commit 5ca0c41

File tree

9 files changed

+33
-116
lines changed

9 files changed

+33
-116
lines changed

README.md

+5-3
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,8 @@ This list covers known interceptors that users use for their Go microservices (b
4444
All paths should work with `go get <path>`.
4545

4646
#### Auth
47-
* [`github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/auth`](interceptors/auth) - a customizable (via `AuthFunc`) piece of auth middleware.
47+
* [`github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/auth`](interceptors/auth) - a customizable via `AuthFunc` piece of auth middleware.
48+
* (external) [`google.golang.org/grpc/authz`](https://github.com/grpc/grpc-go/blob/master/authz/grpc_authz_server_interceptors.go) - more complex, customizable via auth polices (RBAC like), piece of auth middleware.
4849

4950
#### Observability
5051
* Metrics with [`github.com/grpc-ecosystem/go-grpc-middleware/providers/prometheus`](providers/prometheus) - Prometheus client-side and server-side monitoring middleware. Supports exemplars. Moved from deprecated now [`go-grpc-prometheus`](https://github.com/grpc-ecosystem/go-grpc-prometheus). It's a separate module, so core module has limited number of dependencies.
@@ -54,8 +55,9 @@ All paths should work with `go get <path>`.
5455
* (external) [`github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing`](https://pkg.go.dev/github.com/grpc-ecosystem/[email protected]/tracing/opentracing) - deprecated [OpenTracing](http://opentracing.io/) client-side and server-side interceptors if you still need it!
5556

5657
#### Client
57-
* [`github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/retry`](interceptors/retry) - a generic gRPC response code retry mechanism, client-side middleware
58-
* [`github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/timeout`](interceptors/timeout) - a generic gRPC request timeout, client-side middleware
58+
* [`github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/retry`](interceptors/retry) - a generic gRPC response code retry mechanism, client-side middleware.
59+
* NOTE: grpc-go has native retries too with advanced policies (https://github.com/grpc/grpc-go/blob/v1.54.0/examples/features/retry/client/main.go)
60+
* [`github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/timeout`](interceptors/timeout) - a generic gRPC request timeout, client-side middleware.
5961

6062
#### Server
6163
* [`github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/validator`](interceptors/validator) - codegen inbound message validation from `.proto` options.

interceptors/auth/auth.go

+2
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ type ServiceAuthFuncOverride interface {
3232
}
3333

3434
// UnaryServerInterceptor returns a new unary server interceptors that performs per-request auth.
35+
// NOTE(bwplotka): For more complex auth interceptor see https://github.com/grpc/grpc-go/blob/master/authz/grpc_authz_server_interceptors.go.
3536
func UnaryServerInterceptor(authFunc AuthFunc) grpc.UnaryServerInterceptor {
3637
return func(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (any, error) {
3738
var newCtx context.Context
@@ -49,6 +50,7 @@ func UnaryServerInterceptor(authFunc AuthFunc) grpc.UnaryServerInterceptor {
4950
}
5051

5152
// StreamServerInterceptor returns a new unary server interceptors that performs per-request auth.
53+
// NOTE(bwplotka): For more complex auth interceptor see https://github.com/grpc/grpc-go/blob/master/authz/grpc_authz_server_interceptors.go.
5254
func StreamServerInterceptor(authFunc AuthFunc) grpc.StreamServerInterceptor {
5355
return func(srv any, stream grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {
5456
var newCtx context.Context

interceptors/retry/backoff.go

+5-4
Original file line numberDiff line numberDiff line change
@@ -4,13 +4,14 @@
44
package retry
55

66
import (
7+
"context"
78
"math/rand"
89
"time"
910
)
1011

1112
// BackoffLinear is very simple: it waits for a fixed period of time between calls.
1213
func BackoffLinear(waitBetween time.Duration) BackoffFunc {
13-
return func(attempt uint) time.Duration {
14+
return func(ctx context.Context, attempt uint) time.Duration {
1415
return waitBetween
1516
}
1617
}
@@ -31,7 +32,7 @@ func exponentBase2(a uint) uint {
3132
// BackoffLinearWithJitter waits a set period of time, allowing for jitter (fractional adjustment).
3233
// For example waitBetween=1s and jitter=0.10 can generate waits between 900ms and 1100ms.
3334
func BackoffLinearWithJitter(waitBetween time.Duration, jitterFraction float64) BackoffFunc {
34-
return func(attempt uint) time.Duration {
35+
return func(ctx context.Context, attempt uint) time.Duration {
3536
return jitterUp(waitBetween, jitterFraction)
3637
}
3738
}
@@ -40,15 +41,15 @@ func BackoffLinearWithJitter(waitBetween time.Duration, jitterFraction float64)
4041
// The scalar is multiplied times 2 raised to the current attempt. So the first
4142
// retry with a scalar of 100ms is 100ms, while the 5th attempt would be 1.6s.
4243
func BackoffExponential(scalar time.Duration) BackoffFunc {
43-
return func(attempt uint) time.Duration {
44+
return func(ctx context.Context, attempt uint) time.Duration {
4445
return scalar * time.Duration(exponentBase2(attempt))
4546
}
4647
}
4748

4849
// BackoffExponentialWithJitter creates an exponential backoff like
4950
// BackoffExponential does, but adds jitter.
5051
func BackoffExponentialWithJitter(scalar time.Duration, jitterFraction float64) BackoffFunc {
51-
return func(attempt uint) time.Duration {
52+
return func(ctx context.Context, attempt uint) time.Duration {
5253
return jitterUp(scalar*time.Duration(exponentBase2(attempt)), jitterFraction)
5354
}
5455
}

interceptors/retry/options.go

+3-22
Original file line numberDiff line numberDiff line change
@@ -23,9 +23,7 @@ var (
2323
perCallTimeout: 0, // disabled
2424
includeHeader: true,
2525
codes: DefaultRetriableCodes,
26-
backoffFunc: BackoffFuncContext(func(ctx context.Context, attempt uint) time.Duration {
27-
return BackoffLinearWithJitter(50*time.Millisecond /*jitter*/, 0.10)(attempt)
28-
}),
26+
backoffFunc: BackoffLinearWithJitter(50*time.Millisecond /*jitter*/, 0.10),
2927
onRetryCallback: OnRetryCallback(func(ctx context.Context, attempt uint, err error) {
3028
logTrace(ctx, "grpc_retry attempt: %d, backoff for %v", attempt, err)
3129
}),
@@ -37,16 +35,8 @@ var (
3735
// They are called with an identifier of the attempt, and should return a time the system client should
3836
// hold off for. If the time returned is longer than the `context.Context.Deadline` of the request
3937
// the deadline of the request takes precedence and the wait will be interrupted before proceeding
40-
// with the next iteration.
41-
type BackoffFunc func(attempt uint) time.Duration
42-
43-
// BackoffFuncContext denotes a family of functions that control the backoff duration between call retries.
44-
//
45-
// They are called with an identifier of the attempt, and should return a time the system client should
46-
// hold off for. If the time returned is longer than the `context.Context.Deadline` of the request
47-
// the deadline of the request takes precedence and the wait will be interrupted before proceeding
4838
// with the next iteration. The context can be used to extract request scoped metadata and context values.
49-
type BackoffFuncContext func(ctx context.Context, attempt uint) time.Duration
39+
type BackoffFunc func(ctx context.Context, attempt uint) time.Duration
5040

5141
// OnRetryCallback is the type of function called when a retry occurs.
5242
type OnRetryCallback func(ctx context.Context, attempt uint, err error)
@@ -67,15 +57,6 @@ func WithMax(maxRetries uint) CallOption {
6757

6858
// WithBackoff sets the `BackoffFunc` used to control time between retries.
6959
func WithBackoff(bf BackoffFunc) CallOption {
70-
return CallOption{applyFunc: func(o *options) {
71-
o.backoffFunc = BackoffFuncContext(func(ctx context.Context, attempt uint) time.Duration {
72-
return bf(attempt)
73-
})
74-
}}
75-
}
76-
77-
// WithBackoffContext sets the `BackoffFuncContext` used to control time between retries.
78-
func WithBackoffContext(bf BackoffFuncContext) CallOption {
7960
return CallOption{applyFunc: func(o *options) {
8061
o.backoffFunc = bf
8162
}}
@@ -124,7 +105,7 @@ type options struct {
124105
perCallTimeout time.Duration
125106
includeHeader bool
126107
codes []codes.Code
127-
backoffFunc BackoffFuncContext
108+
backoffFunc BackoffFunc
128109
onRetryCallback OnRetryCallback
129110
}
130111

interceptors/retry/retry_test.go

-69
Original file line numberDiff line numberDiff line change
@@ -178,40 +178,6 @@ func (s *RetrySuite) TestUnary_OverrideFromDialOpts() {
178178
require.EqualValues(s.T(), 5, s.srv.requestCount(), "five requests should have been made")
179179
}
180180

181-
func (s *RetrySuite) TestUnary_PerCallDeadline_Succeeds() {
182-
s.T().Skip("TODO(bwplotka): Mock time & unskip, this is too flaky on GH Actions.")
183-
184-
// This tests 5 requests, with first 4 sleeping for 10 millisecond, and the retry logic firing
185-
// a retry call with a 5 millisecond deadline. The 5th one doesn't sleep and succeeds.
186-
deadlinePerCall := 5 * time.Millisecond
187-
s.srv.resetFailingConfiguration(5, codes.NotFound, 2*deadlinePerCall)
188-
out, err := s.Client.Ping(s.SimpleCtx(), testpb.GoodPing, WithPerRetryTimeout(deadlinePerCall),
189-
WithMax(5))
190-
require.NoError(s.T(), err, "the fifth invocation should succeed")
191-
require.NotNil(s.T(), out, "Pong must be not nil")
192-
require.EqualValues(s.T(), 5, s.srv.requestCount(), "five requests should have been made")
193-
}
194-
195-
func (s *RetrySuite) TestUnary_PerCallDeadline_FailsOnParent() {
196-
s.T().Skip("TODO(bwplotka): Mock time & unskip, this is too flaky on GH Actions.")
197-
198-
// This tests that the parent context (passed to the invocation) takes precedence over retries.
199-
// The parent context has 150 milliseconds of deadline.
200-
// Each failed call sleeps for 100milliseconds, and there is 5 milliseconds between each one.
201-
// This means that unlike in TestUnary_PerCallDeadline_Succeeds, the fifth successful call won't
202-
// be made.
203-
parentDeadline := 150 * time.Millisecond
204-
deadlinePerCall := 50 * time.Millisecond
205-
// All 0-4 requests should have 10 millisecond sleeps and deadline, while the last one works.
206-
s.srv.resetFailingConfiguration(5, codes.NotFound, 2*deadlinePerCall)
207-
ctx, cancel := context.WithTimeout(context.TODO(), parentDeadline)
208-
defer cancel()
209-
_, err := s.Client.Ping(ctx, testpb.GoodPing, WithPerRetryTimeout(deadlinePerCall),
210-
WithMax(5))
211-
require.Error(s.T(), err, "the retries must fail due to context deadline exceeded")
212-
require.Equal(s.T(), codes.DeadlineExceeded, status.Code(err), "failre code must be a gRPC error of Deadline class")
213-
}
214-
215181
func (s *RetrySuite) TestUnary_OnRetryCallbackCalled() {
216182
retryCallbackCount := 0
217183

@@ -243,41 +209,6 @@ func (s *RetrySuite) TestServerStream_OverrideFromContext() {
243209
require.EqualValues(s.T(), 5, s.srv.requestCount(), "three requests should have been made")
244210
}
245211

246-
func (s *RetrySuite) TestServerStream_PerCallDeadline_Succeeds() {
247-
s.T().Skip("TODO(bwplotka): Mock time & unskip, this is too flaky on GH Actions.")
248-
249-
// This tests 5 requests, with first 4 sleeping for 100 millisecond, and the retry logic firing
250-
// a retry call with a 50 millisecond deadline. The 5th one doesn't sleep and succeeds.
251-
deadlinePerCall := 100 * time.Millisecond
252-
s.srv.resetFailingConfiguration(5, codes.NotFound, 2*deadlinePerCall)
253-
stream, err := s.Client.PingList(s.SimpleCtx(), testpb.GoodPingList, WithPerRetryTimeout(deadlinePerCall),
254-
WithMax(5))
255-
require.NoError(s.T(), err, "establishing the connection must always succeed")
256-
s.assertPingListWasCorrect(stream)
257-
require.EqualValues(s.T(), 5, s.srv.requestCount(), "three requests should have been made")
258-
}
259-
260-
func (s *RetrySuite) TestServerStream_PerCallDeadline_FailsOnParent() {
261-
s.T().Skip("TODO(bwplotka): Mock time & unskip, this is too flaky on GH Actions.")
262-
263-
// This tests that the parent context (passed to the invocation) takes precedence over retries.
264-
// The parent context has 150 milliseconds of deadline.
265-
// Each failed call sleeps for 50milliseconds, and there is 25 milliseconds between each one.
266-
// This means that unlike in TestServerStream_PerCallDeadline_Succeeds, the fifth successful call won't
267-
// be made.
268-
parentDeadline := 150 * time.Millisecond
269-
deadlinePerCall := 50 * time.Millisecond
270-
// All 0-4 requests should have 10 millisecond sleeps and deadline, while the last one works.
271-
s.srv.resetFailingConfiguration(5, codes.NotFound, 2*deadlinePerCall)
272-
parentCtx, cancel := context.WithTimeout(context.TODO(), parentDeadline)
273-
defer cancel()
274-
stream, err := s.Client.PingList(parentCtx, testpb.GoodPingList, WithPerRetryTimeout(deadlinePerCall),
275-
WithMax(5))
276-
require.NoError(s.T(), err, "establishing the connection must always succeed")
277-
_, err = stream.Recv()
278-
require.Equal(s.T(), codes.DeadlineExceeded, status.Code(err), "failre code must be a gRPC error of Deadline class")
279-
}
280-
281212
func (s *RetrySuite) TestServerStream_OnRetryCallbackCalled() {
282213
retryCallbackCount := 0
283214

interceptors/validator/interceptors.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ import (
1717
func UnaryServerInterceptor(opts ...Option) grpc.UnaryServerInterceptor {
1818
o := evaluateOpts(opts)
1919
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
20-
if err := validate(ctx, req, o.shouldFailFast, o.onValidationErrFunc); err != nil {
20+
if err := validate(ctx, req, o.shouldFailFast, o.onValidationErrCallback); err != nil {
2121
return nil, err
2222
}
2323
return handler(ctx, req)
@@ -32,7 +32,7 @@ func UnaryServerInterceptor(opts ...Option) grpc.UnaryServerInterceptor {
3232
func UnaryClientInterceptor(opts ...Option) grpc.UnaryClientInterceptor {
3333
o := evaluateOpts(opts)
3434
return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
35-
if err := validate(ctx, req, o.shouldFailFast, o.onValidationErrFunc); err != nil {
35+
if err := validate(ctx, req, o.shouldFailFast, o.onValidationErrCallback); err != nil {
3636
return err
3737
}
3838
return invoker(ctx, method, req, reply, cc, opts...)
@@ -68,7 +68,7 @@ func (s *recvWrapper) RecvMsg(m any) error {
6868
if err := s.ServerStream.RecvMsg(m); err != nil {
6969
return err
7070
}
71-
if err := validate(s.Context(), m, s.shouldFailFast, s.onValidationErrFunc); err != nil {
71+
if err := validate(s.Context(), m, s.shouldFailFast, s.onValidationErrCallback); err != nil {
7272
return err
7373
}
7474
return nil

interceptors/validator/interceptors_test.go

+6-6
Original file line numberDiff line numberDiff line change
@@ -116,8 +116,8 @@ func TestValidatorTestSuite(t *testing.T) {
116116
sWithOnErrFuncArgs := &ValidatorTestSuite{
117117
InterceptorTestSuite: &testpb.InterceptorTestSuite{
118118
ServerOpts: []grpc.ServerOption{
119-
grpc.StreamInterceptor(validator.StreamServerInterceptor(validator.WithOnValidationErrFunc(onErr))),
120-
grpc.UnaryInterceptor(validator.UnaryServerInterceptor(validator.WithOnValidationErrFunc(onErr))),
119+
grpc.StreamInterceptor(validator.StreamServerInterceptor(validator.WithOnValidationErrCallback(onErr))),
120+
grpc.UnaryInterceptor(validator.UnaryServerInterceptor(validator.WithOnValidationErrCallback(onErr))),
121121
},
122122
},
123123
}
@@ -128,8 +128,8 @@ func TestValidatorTestSuite(t *testing.T) {
128128
sAll := &ValidatorTestSuite{
129129
InterceptorTestSuite: &testpb.InterceptorTestSuite{
130130
ServerOpts: []grpc.ServerOption{
131-
grpc.StreamInterceptor(validator.StreamServerInterceptor(validator.WithFailFast(), validator.WithOnValidationErrFunc(onErr))),
132-
grpc.UnaryInterceptor(validator.UnaryServerInterceptor(validator.WithFailFast(), validator.WithOnValidationErrFunc(onErr))),
131+
grpc.StreamInterceptor(validator.StreamServerInterceptor(validator.WithFailFast(), validator.WithOnValidationErrCallback(onErr))),
132+
grpc.UnaryInterceptor(validator.UnaryServerInterceptor(validator.WithFailFast(), validator.WithOnValidationErrCallback(onErr))),
133133
},
134134
},
135135
}
@@ -158,7 +158,7 @@ func TestValidatorTestSuite(t *testing.T) {
158158
csWithOnErrFuncArgs := &ClientValidatorTestSuite{
159159
InterceptorTestSuite: &testpb.InterceptorTestSuite{
160160
ServerOpts: []grpc.ServerOption{
161-
grpc.UnaryInterceptor(validator.UnaryServerInterceptor(validator.WithOnValidationErrFunc(onErr))),
161+
grpc.UnaryInterceptor(validator.UnaryServerInterceptor(validator.WithOnValidationErrCallback(onErr))),
162162
},
163163
},
164164
}
@@ -169,7 +169,7 @@ func TestValidatorTestSuite(t *testing.T) {
169169
csAll := &ClientValidatorTestSuite{
170170
InterceptorTestSuite: &testpb.InterceptorTestSuite{
171171
ClientOpts: []grpc.DialOption{
172-
grpc.WithUnaryInterceptor(validator.UnaryClientInterceptor(validator.WithFailFast(), validator.WithOnValidationErrFunc(onErr))),
172+
grpc.WithUnaryInterceptor(validator.UnaryClientInterceptor(validator.WithFailFast(), validator.WithOnValidationErrCallback(onErr))),
173173
},
174174
},
175175
}

interceptors/validator/options.go

+6-6
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ import (
88
)
99

1010
type options struct {
11-
shouldFailFast bool
12-
onValidationErrFunc OnValidationErr
11+
shouldFailFast bool
12+
onValidationErrCallback OnValidationErrCallback
1313
}
1414
type Option func(*options)
1515

@@ -21,12 +21,12 @@ func evaluateOpts(opts []Option) *options {
2121
return optCopy
2222
}
2323

24-
type OnValidationErr func(ctx context.Context, err error)
24+
type OnValidationErrCallback func(ctx context.Context, err error)
2525

26-
// WithOnValidationErrFunc registers function that will be invoked on validation error(s).
27-
func WithOnValidationErrFunc(onValidationErrFunc OnValidationErr) Option {
26+
// WithOnValidationErrCallback registers function that will be invoked on validation error(s).
27+
func WithOnValidationErrCallback(onValidationErrCallback OnValidationErrCallback) Option {
2828
return func(o *options) {
29-
o.onValidationErrFunc = onValidationErrFunc
29+
o.onValidationErrCallback = onValidationErrCallback
3030
}
3131
}
3232

interceptors/validator/validator.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ type validatorLegacy interface {
2727
Validate() error
2828
}
2929

30-
func validate(ctx context.Context, reqOrRes interface{}, shouldFailFast bool, onValidationErrFunc OnValidationErr) (err error) {
30+
func validate(ctx context.Context, reqOrRes interface{}, shouldFailFast bool, onValidationErrCallback OnValidationErrCallback) (err error) {
3131
if shouldFailFast {
3232
switch v := reqOrRes.(type) {
3333
case validatorLegacy:
@@ -50,8 +50,8 @@ func validate(ctx context.Context, reqOrRes interface{}, shouldFailFast bool, on
5050
return nil
5151
}
5252

53-
if onValidationErrFunc != nil {
54-
onValidationErrFunc(ctx, err)
53+
if onValidationErrCallback != nil {
54+
onValidationErrCallback(ctx, err)
5555
}
5656
return status.Error(codes.InvalidArgument, err.Error())
5757
}

0 commit comments

Comments
 (0)