Skip to content

Commit

Permalink
Review docs and comments on interceptors
Browse files Browse the repository at this point in the history
  • Loading branch information
marioizquierdo committed Sep 25, 2020
1 parent 403a70c commit f7b1717
Show file tree
Hide file tree
Showing 3 changed files with 51 additions and 33 deletions.
4 changes: 4 additions & 0 deletions docs/hooks.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,7 @@ Check out
for information on the specific callbacks. For an example hooks implementation,
[`github.com/twitchtv/twirp/hooks/statsd`](https://github.com/twitchtv/twirp/blob/master/hooks/statsd/)
is a good tutorial.

On the client side, use `*twirp.ClientHooks`.

For advanced cases, [interceptors](interceptors.md) can be used to plug in extra functionality.
44 changes: 27 additions & 17 deletions docs/interceptors.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,33 +4,43 @@ title: "Interceptors"
sidebar_label: "Interceptors"
---

The client and service constructors can use the options
`twirp.WithClientInterceptors(interceptors ...twirp.Interceptor)`
and `twirp.WithServerInterceptors(interceptors ...twirp.Interceptor)`
to plug in additional functionality:
Interceptors are like middleware, wrapping RPC calls with extra functionality.

```go
client := NewHaberdasherProtobufClient(url, &http.Client{}, twirp.WithClientInterceptors(NewLogInterceptor(logger.New(os.Stderr, "", 0))))
In most cases, it is better to use [Hooks](hooks.md) for observability at key points
during a request lifecycle. Hooks do not mutate the request and response structs,
which results in less problems when debugging issues.

server := NewHaberdasherServer(svcImpl, twirp.WithServerInterceptors(NewLogInterceptor(logger.New(os.Stderr, "", 0))))
Example:

// NewLogInterceptor logs various parts of a request using a standard Logger.
func NewLogInterceptor(l *log.Logger) twirp.Interceptor {
```go
// NewInterceptorMakeSmallHats builds an interceptor that modifies
// calls to MakeHat ignoring the request, and instead always making small hats.
func NewInterceptorMakeSmallHats() twirp.Interceptor {
return func(next twirp.Method) twirp.Method {
return func(ctx context.Context, req interface{}) (interface{}, error) {
l.Printf("request: %v", request)
resp, err := next(ctx, req)
if err != nil {
l.Printf("error: %v", err)
return nil, err
if twirp.MethodName(ctx) == "MakeHat" {
return next(ctx, &haberdasher.Size{Inches: 1})
}
l.Printf("response: %v", resp)
return resp, nil
return next(ctx, req)
}
}
}
```

To wrap all client requests with the interceptor:

```go
client := NewHaberdasherProtobufClient(url, &http.Client{},
twirp.WithClientInterceptors(NewInterceptorMakeSmallHats()))
```

To wrap all service requests with the interceptor:

```go
server := NewHaberdasherServer(svcImpl,
twirp.WithServerInterceptors(NewInterceptorMakeSmallHats())
```

Check out
[the godoc for `Interceptor`](http://godoc.org/github.com/twitchtv/twirp#Interceptor)
[the godoc for Interceptor](http://godoc.org/github.com/twitchtv/twirp#Interceptor)
for more information.
36 changes: 20 additions & 16 deletions interceptors.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,34 +16,38 @@ import (
"context"
)

// Method is a method that matches the generic form of a Twirp-generated RPC method.
// Interceptor is a form of middleware for Twirp requests, that can be installed on both
// clients and servers. To intercept RPC calls in the client, use the option
// `twirp.WithClientInterceptors` on the client constructor. To intercept RPC calls in the server,
// use the option `twirp.WithServerInterceptors` on the server constructor.
//
// This is used for Interceptors.
type Method func(ctx context.Context, request interface{}) (interface{}, error)

// Interceptor is an interceptor that can be installed on a client or server.
// Just like http middleware, interceptors can mutate requests and responses.
// This can enable some powerful integrations, but it should be used with much care
// becuase it may result in code that is very hard to debug.
//
// Users can use Interceptors to intercept any RPC.
// Example of an interceptor that logs every request and response:
//
// func LogInterceptor(l *log.Logger) twirp.Interceptor {
// return func(next twirp.Method) twirp.Method {
// return func(ctx context.Context, req interface{}) (interface{}, error) {
// l.Printf("request: %v", request)
// l.Printf("Service: %s, Method: %s, Request: %v",
// twirp.ServiceName(ctx), twirp.MethodName(ctx), req)
// resp, err := next(ctx, req)
// if err != nil {
// l.Printf("error: %v", err)
// return nil, err
// }
// l.Printf("response: %v", resp)
// return resp, nil
// l.Printf("Response: %v, Error: %v", resp)
// return resp, err
// }
// }
// }
//
type Interceptor func(Method) Method

// ChainInterceptors chains the Interceptors.
//
// Returns nil if interceptors is empty.
// Method is a generic representation of a Twirp-generated RPC method.
// It is used to define Interceptors.
type Method func(ctx context.Context, request interface{}) (interface{}, error)

// ChainInterceptors chains multiple Interceptors into a single Interceptor.
// The first interceptor wraps the second one, and so on.
// Returns nil if interceptors is empty. Nil interceptors are ignored.
func ChainInterceptors(interceptors ...Interceptor) Interceptor {
filtered := make([]Interceptor, 0, len(interceptors))
for _, interceptor := range interceptors {
Expand Down

0 comments on commit f7b1717

Please sign in to comment.