Skip to content

Commit

Permalink
Revert "Revert "formatted go snippets in customizingyourgateway.md""
Browse files Browse the repository at this point in the history
This reverts commit e3d2ad1.
  • Loading branch information
iamrajiv committed May 19, 2020
1 parent e3d2ad1 commit bab7045
Show file tree
Hide file tree
Showing 4 changed files with 115 additions and 94 deletions.
Binary file added .DS_Store
Binary file not shown.
Binary file added docs/.DS_Store
Binary file not shown.
Binary file added docs/_docs/.DS_Store
Binary file not shown.
209 changes: 115 additions & 94 deletions docs/_docs/customizingyourgateway.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ order: 101
# Customizing your gateway

## Message serialization

### Custom serializer

You might want to serialize request/response messages in MessagePack instead of JSON, for example.
Expand All @@ -24,9 +25,10 @@ You can see [the default implementation for JSON](https://github.com/grpc-ecosys
### Using camelCase for JSON

The protocol buffer compiler generates camelCase JSON tags that can be used with jsonpb package. By default jsonpb Marshaller uses `OrigName: true` which uses the exact case used in the proto files. To use camelCase for the JSON representation,
```go
mux := runtime.NewServeMux(runtime.WithMarshalerOption(runtime.MIMEWildcard, &runtime.JSONPb{OrigName:false}))
```

```go
mux := runtime.NewServeMux(runtime.WithMarshalerOption(runtime.MIMEWildcard, &runtime.JSONPb{OrigName:false}))
```

### Pretty-print JSON responses when queried with ?pretty

Expand All @@ -40,34 +42,35 @@ For example:
```go
mux := runtime.NewServeMux(
runtime.WithMarshalerOption("application/json+pretty", &runtime.JSONPb{Indent: " "}),
runtime.WithMarshalerOption("application/json+pretty", &runtime.JSONPb{Indent: " "}),
)
prettier := func(h http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// checking Values as map[string][]string also catches ?pretty and ?pretty=
// r.URL.Query().Get("pretty") would not.
if _, ok := r.URL.Query()["pretty"]; ok {
r.Header.Set("Accept", "application/json+pretty")
}
h.ServeHTTP(w, r)
})
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// checking Values as map[string][]string also catches ?pretty and ?pretty=
// r.URL.Query().Get("pretty") would not.
if _, ok := r.URL.Query()["pretty"]; ok {
r.Header.Set("Accept", "application/json+pretty")
}
h.ServeHTTP(w, r)
})
}
http.ListenAndServe(":8080", prettier(mux))
```
Note that `runtime.JSONPb{Indent: " "}` will do the trick for pretty-printing: it wraps
Note that `runtime.JSONPb{Indent: " "}` will do the trick for pretty-printing: it wraps
`jsonpb.Marshaler`:
```go
type Marshaler struct {
// ...
// ...
// A string to indent each level by. The presence of this field will
// also cause a space to appear between the field separator and
// value, and for newlines appear between fields and array
// elements.
Indent string
// A string to indent each level by. The presence of this field will
// also cause a space to appear between the field separator and
// value, and for newlines to appear between fields and array
// elements.
Indent string
// ...
// ...
}
```
Expand All @@ -79,30 +82,30 @@ also, this example code does not remove the query parameter `pretty` from furthe
## Customize unmarshaling per Content-Type
Having different unmarshaling options per Content-Type is possible by wrapping the decoder passing that to `runtime.WithMarshalerOption`:
Having different unmarshaling options per Content-Type is possible by wrapping the decoder and passing that to `runtime.WithMarshalerOption`:
```go
type m struct {
*runtime.JSONPb
unmarshaler *jsonpb.Unmarshaler
*runtime.JSONPb
unmarshaler *jsonpb.Unmarshaler
}
type decoderWrapper struct {
*json.Decoder
*jsonpb.Unmarshaler
*json.Decoder
*jsonpb.Unmarshaler
}
func (n *m) NewDecoder(r io.Reader) runtime.Decoder {
d := json.NewDecoder(r)
return &decoderWrapper{Decoder: d, Unmarshaler: n.unmarshaler}
d := json.NewDecoder(r)
return &decoderWrapper{Decoder: d, Unmarshaler: n.unmarshaler}
}
func (d *decoderWrapper) Decode(v interface{}) error {
p, ok := v.(proto.Message)
if !ok { // if it's not decoding into a proto.Message, there's no notion of unknown fields
return d.Decoder.Decode(v)
}
return d.UnmarshalNext(d.Decoder, p) // uses m's jsonpb.Unmarshaler configuration
p, ok := v.(proto.Message)
if !ok { // if it's not decoding into a proto.Message, there's no notion of unknown fields
return d.Decoder.Decode(v)
}
return d.UnmarshalNext(d.Decoder, p) // uses m's jsonpb.Unmarshaler configuration
}
```

Expand All @@ -121,27 +124,30 @@ mux := runtime.NewServeMux(
```

## Mapping from HTTP request headers to gRPC client metadata

You might not like [the default mapping rule](https://pkg.go.dev/github.com/grpc-ecosystem/grpc-gateway/runtime?tab=doc#DefaultHeaderMatcher) and might want to pass through all the HTTP headers, for example.

1. Write a [`HeaderMatcherFunc`](https://pkg.go.dev/github.com/grpc-ecosystem/grpc-gateway/runtime?tab=doc#HeaderMatcherFunc).
2. Register the function with [`WithIncomingHeaderMatcher`](https://pkg.go.dev/github.com/grpc-ecosystem/grpc-gateway/runtime?tab=doc#WithIncomingHeaderMatcher)

e.g.
```go
func CustomMatcher(key string) (string, bool) {
switch key {
case "X-Custom-Header1":
return key, true
case "X-Custom-Header2":
return "custom-header2", true
default:
return key, false
}
e.g.

```go
func CustomMatcher(key string) (string, bool) {
switch key {
case "X-Custom-Header1":
return key, true
case "X-Custom-Header2":
return "custom-header2", true
default:
return key, false
}
...
}
...

mux := runtime.NewServeMux(runtime.WithIncomingHeaderMatcher(CustomMatcher))
```

mux := runtime.NewServeMux(runtime.WithIncomingHeaderMatcher(CustomMatcher))
```
To keep the [the default mapping rule](https://pkg.go.dev/github.com/grpc-ecosystem/grpc-gateway/runtime?tab=doc#DefaultHeaderMatcher) alongside with your own rules write:

```go
Expand All @@ -154,16 +160,21 @@ func CustomMatcher(key string) (string, bool) {
}
}
```

It will work with both:

```bash
curl --header "x-user-id: 100d9f38-2777-4ee2-ac3b-b3a108f81a30" ...
```

and:

```bash
curl --header "X-USER-ID: 100d9f38-2777-4ee2-ac3b-b3a108f81a30" ...
```

To access this header on gRPC server side use:

```go
...
userID := ""
Expand All @@ -176,42 +187,47 @@ if md, ok := metadata.FromIncomingContext(ctx); ok {
```

## Mapping from gRPC server metadata to HTTP response headers

ditto. Use [`WithOutgoingHeaderMatcher`](https://pkg.go.dev/github.com/grpc-ecosystem/grpc-gateway/runtime?tab=doc#WithOutgoingHeaderMatcher).
See [gRPC metadata docs](https://github.com/grpc/grpc-go/blob/master/Documentation/grpc-metadata.md)
for more info on sending / receiving gRPC metadata.

e.g.
e.g.

```go
...
if appendCustomHeader {
grpc.SendHeader(ctx, metadata.New(map[string]string{
"x-custom-header1": "value",
}))
}
```
```go
...
if appendCustomHeader {
grpc.SendHeader(ctx, metadata.New(map[string]string{
"x-custom-header1": "value",
}))
}
```

## Mutate response messages or set response headers

You might want to return a subset of response fields as HTTP response headers;
You might want to simply set an application-specific token in a header.
Or you might want to mutate the response messages to be returned.

1. Write a filter function.

```go
func myFilter(ctx context.Context, w http.ResponseWriter, resp proto.Message) error {
t, ok := resp.(*externalpb.Tokenizer)

if ok {
w.Header().Set("X-My-Tracking-Token", t.Token)
t.Token = ""
}
{
)
"
}
return nil
return nil
}
```
2. Register the filter with [`WithForwardResponseOption`](https://pkg.go.dev/github.com/grpc-ecosystem/grpc-gateway/runtime?tab=doc#WithForwardResponseOption)
e.g.
```go
mux := runtime.NewServeMux(runtime.WithForwardResponseOption(myFilter))
```
Expand Down Expand Up @@ -272,15 +288,16 @@ opts := []grpc.DialOption{
),
}
if err := pb.RegisterMyServiceHandlerFromEndpoint(ctx, mux, serviceEndpoint, opts); err != nil {
log.Fatalf("could not register HTTP service: %v", err)
log.Fatalf("could not register HTTP service: %v", err)
}
```
## Error handler
The gateway uses two different error handlers for non-streaming requests:
* `runtime.HTTPError` is called for errors from backend calls
* `runtime.OtherErrorHandler` is called for errors from parsing and routing client requests
- `runtime.HTTPError` is called for errors from backend calls
- `runtime.OtherErrorHandler` is called for errors from parsing and routing client requests
To override all error handling for a `*runtime.ServeMux`, use the
`runtime.WithProtoErrorHandler` serve option.
Expand All @@ -297,6 +314,7 @@ See https://mycodesmells.com/post/grpc-gateway-error-handler for an example
of writing a custom error handler function.
## Stream Error Handler
The error handler described in the previous section applies only
to RPC methods that have a unary response.
Expand All @@ -312,7 +330,7 @@ streams, you must install a _different_ error handler:
```go
mux := runtime.NewServeMux(
runtime.WithStreamErrorHandler(handleStreamError))
runtime.WithStreamErrorHandler(handleStreamError))
```
The signature of the handler is much more rigid because we need
Expand All @@ -324,6 +342,7 @@ can choose to omit some fields and can filter/transform the original
error, such as stripping stack traces from error messages.
Here's an example custom handler:
```go
// handleStreamError overrides default behavior for computing an error
// message for a server stream.
Expand All @@ -332,26 +351,26 @@ Here's an example custom handler:
// messages; and does not set gRPC code or details fields (so they will
// be omitted from the resulting JSON object that is sent to client).
func handleStreamError(ctx context.Context, err error) *runtime.StreamError {
code := http.StatusBadGateway
msg := "unexpected error"
if s, ok := status.FromError(err); ok {
code = runtime.HTTPStatusFromCode(s.Code())
// default message, based on the name of the gRPC code
msg = code.String()
// see if error details include "safe" message to send
// to external callers
for _, msg := s.Details() {
if safe, ok := msg.(*SafeMessage); ok {
msg = safe.Text
break
}
}
}
return &runtime.StreamError{
HttpCode: int32(code),
HttpStatus: http.StatusText(code),
Message: msg,
}
code := http.StatusBadGateway
msg := "unexpected error"
if s, ok := status.FromError(err); ok {
code = runtime.HTTPStatusFromCode(s.Code())
// default message, based on the name of the gRPC code
msg = code.String()
// see if error details include "safe" message to send
// to external callers
for _, msg := s.Details() {
if safe, ok := msg.(*SafeMessage); ok {
msg = safe.Text
break
}
}
}
return &runtime.StreamError{
HttpCode: int32(code),
HttpStatus: http.StatusText(code),
Message: msg,
}
}
```
Expand All @@ -364,32 +383,34 @@ from the gRPC code (or set to `"500 Internal Server Error"` when
the source error has no gRPC attributes).
## Replace a response forwarder per method
You might want to keep the behavior of the current marshaler but change only a message forwarding of a certain API method.
1. write a custom forwarder which is compatible to [`ForwardResponseMessage`](https://pkg.go.dev/github.com/grpc-ecosystem/grpc-gateway/runtime?tab=doc#ForwardResponseMessage) or [`ForwardResponseStream`](https://pkg.go.dev/github.com/grpc-ecosystem/grpc-gateway/runtime?tab=doc#ForwardResponseStream).
2. replace the default forwarder of the method with your one.
e.g. add `forwarder_overwrite.go` into the go package of the generated code,
```go
package generated
import (
"net/http"
"net/http"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
"github.com/golang/protobuf/proto"
"golang.org/x/net/context"
"github.com/grpc-ecosystem/grpc-gateway/runtime"
"github.com/golang/protobuf/proto"
"golang.org/x/net/context"
)
func forwardCheckoutResp(ctx context.Context, mux *runtime.ServeMux, marshaler runtime.Marshaler, w http.ResponseWriter, req *http.Request, resp proto.Message, opts ...func(context.Context, http.ResponseWriter, proto.Message) error) {
if someCondition(resp) {
http.Error(w, "not enough credit", http. StatusPaymentRequired)
return
}
runtime.ForwardResponseMessage(ctx, mux, marshaler, w, req, resp, opts...)
if someCondition(resp) {
http.Error(w, "not enough credit", http. StatusPaymentRequired)
return
}
runtime.ForwardResponseMessage(ctx, mux, marshaler, w, req, resp, opts...)
}
func init() {
forward_MyService_Checkout_0 = forwardCheckoutResp
forward_MyService_Checkout_0 = forwardCheckoutResp
}
```

0 comments on commit bab7045

Please sign in to comment.