...

Text file src/github.com/grpc-ecosystem/grpc-gateway/docs/_docs/customizingyourgateway.md

Documentation: github.com/grpc-ecosystem/grpc-gateway/docs/_docs

     1---
     2title: Customizing your gateway
     3category: documentation
     4order: 101
     5---
     6
     7# Customizing your gateway
     8
     9## Message serialization
    10### Custom serializer
    11
    12You might want to serialize request/response messages in MessagePack instead of JSON, for example.
    13
    141. Write a custom implementation of [`Marshaler`](https://pkg.go.dev/github.com/grpc-ecosystem/grpc-gateway/runtime?tab=doc#Marshaler)
    152. Register your marshaler with [`WithMarshalerOption`](https://pkg.go.dev/github.com/grpc-ecosystem/grpc-gateway/runtime?tab=doc#WithMarshalerOption)
    16	e.g.
    17	```go
    18	var m your.MsgPackMarshaler
    19	mux := runtime.NewServeMux(
    20		runtime.WithMarshalerOption("application/x-msgpack", m),
    21	)
    22	```
    23
    24You can see [the default implementation for JSON](https://github.com/grpc-ecosystem/grpc-gateway/blob/master/runtime/marshal_jsonpb.go) for reference.
    25
    26### Using camelCase for JSON
    27
    28The 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,
    29```go
    30mux := runtime.NewServeMux(runtime.WithMarshalerOption(runtime.MIMEWildcard, &runtime.JSONPb{OrigName:false}))
    31```
    32
    33### Pretty-print JSON responses when queried with ?pretty
    34
    35You can have Elasticsearch-style `?pretty` support in your gateway's endpoints as follows:
    36
    371. Wrap the ServeMux using a stdlib [`http.HandlerFunc`](https://golang.org/pkg/net/http/#HandlerFunc)
    38	that translates the provided query parameter into a custom `Accept` header, and
    392. Register a pretty-printing marshaler for that MIME code.
    40
    41For example:
    42
    43```go
    44mux := runtime.NewServeMux(
    45	runtime.WithMarshalerOption("application/json+pretty", &runtime.JSONPb{Indent: "  "}),
    46)
    47prettier := func(h http.Handler) http.Handler {
    48	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
    49		// checking Values as map[string][]string also catches ?pretty and ?pretty=
    50		// r.URL.Query().Get("pretty") would not.
    51		if _, ok := r.URL.Query()["pretty"]; ok {
    52			r.Header.Set("Accept", "application/json+pretty")
    53		}
    54		h.ServeHTTP(w, r)
    55	})
    56}
    57http.ListenAndServe(":8080", prettier(mux))
    58```
    59
    60Note that  `runtime.JSONPb{Indent: "  "}` will do the trick for pretty-printing: it wraps
    61`jsonpb.Marshaler`:
    62```go
    63type Marshaler struct {
    64	// ...
    65
    66	// A string to indent each level by. The presence of this field will
    67	// also cause a space to appear between the field separator and
    68	// value, and for newlines to appear between fields and array
    69	// elements.
    70	Indent string
    71
    72	// ...
    73}
    74```
    75
    76Now, either when passing the header `Accept: application/json+pretty` or appending `?pretty` to
    77your HTTP endpoints, the response will be pretty-printed.
    78
    79Note that this will conflict with any methods having input messages with fields named `pretty`;
    80also, this example code does not remove the query parameter `pretty` from further processing.
    81
    82## Customize unmarshaling per Content-Type
    83
    84Having different unmarshaling options per Content-Type is possible by wrapping the decoder and passing that to `runtime.WithMarshalerOption`:
    85
    86```go
    87type m struct {
    88	*runtime.JSONPb
    89	unmarshaler *jsonpb.Unmarshaler
    90}
    91
    92type decoderWrapper struct {
    93	*json.Decoder
    94	*jsonpb.Unmarshaler
    95}
    96
    97func (n *m) NewDecoder(r io.Reader) runtime.Decoder {
    98	d := json.NewDecoder(r)
    99	return &decoderWrapper{Decoder: d, Unmarshaler: n.unmarshaler}
   100}
   101
   102func (d *decoderWrapper) Decode(v interface{}) error {
   103	p, ok := v.(proto.Message)
   104	if !ok { // if it's not decoding into a proto.Message, there's no notion of unknown fields
   105		return d.Decoder.Decode(v)
   106	}
   107	return d.UnmarshalNext(d.Decoder, p) // uses m's jsonpb.Unmarshaler configuration
   108}
   109```
   110
   111This scaffolding allows us to pass a custom unmarshal options. In this example, we configure the
   112unmarshaler to disallow unknown fields. For demonstration purposes, we'll also change some of the
   113default marshaler options:
   114
   115```go
   116mux := runtime.NewServeMux(
   117	runtime.WithMarshalerOption("application/json+strict", &m{
   118		JSONPb: &runtime.JSONPb{EmitDefaults: true},
   119		unmarshaler: &jsonpb.Unmarshaler{AllowUnknownFields: false}, // explicit "false", &jsonpb.Unmarshaler{} would have the same effect
   120	}),
   121)
   122```
   123
   124## Mapping from HTTP request headers to gRPC client metadata
   125You 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.
   126
   1271. Write a [`HeaderMatcherFunc`](https://pkg.go.dev/github.com/grpc-ecosystem/grpc-gateway/runtime?tab=doc#HeaderMatcherFunc).
   1282. Register the function with [`WithIncomingHeaderMatcher`](https://pkg.go.dev/github.com/grpc-ecosystem/grpc-gateway/runtime?tab=doc#WithIncomingHeaderMatcher)
   129
   130	e.g.
   131	```go
   132	func CustomMatcher(key string) (string, bool) {
   133		switch key {
   134		case "X-Custom-Header1":
   135			return key, true
   136		case "X-Custom-Header2":
   137			return "custom-header2", true
   138		default:
   139			return key, false
   140		}
   141	}
   142
   143	mux := runtime.NewServeMux(
   144		runtime.WithIncomingHeaderMatcher(CustomMatcher),
   145	)
   146	```
   147
   148To 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:
   149
   150```go
   151func CustomMatcher(key string) (string, bool) {
   152	switch key {
   153	case "X-User-Id":
   154		return key, true
   155	default:
   156		return runtime.DefaultHeaderMatcher(key)
   157	}
   158}
   159```
   160It will work with both:
   161
   162```shell
   163$ curl --header "x-user-id: 100d9f38-2777-4ee2-ac3b-b3a108f81a30" ...
   164```
   165and:
   166```shell
   167$ curl --header "X-USER-ID: 100d9f38-2777-4ee2-ac3b-b3a108f81a30" ...
   168```
   169To access this header on gRPC server side use:
   170```go
   171userID := ""
   172if md, ok := metadata.FromIncomingContext(ctx); ok {
   173	if uID, ok := md["x-user-id"]; ok {
   174		userID = strings.Join(uID, ",")
   175	}
   176}
   177```
   178
   179## Mapping from gRPC server metadata to HTTP response headers
   180ditto. Use [`WithOutgoingHeaderMatcher`](https://pkg.go.dev/github.com/grpc-ecosystem/grpc-gateway/runtime?tab=doc#WithOutgoingHeaderMatcher).
   181See [gRPC metadata docs](https://github.com/grpc/grpc-go/blob/master/Documentation/grpc-metadata.md)
   182for more info on sending / receiving gRPC metadata, e.g.
   183```go
   184if appendCustomHeader {
   185	grpc.SendHeader(ctx, metadata.New(map[string]string{
   186		"x-custom-header1": "value",
   187	}))
   188}
   189```
   190
   191## Mutate response messages or set response headers
   192### Set HTTP headers
   193You might want to return a subset of response fields as HTTP response headers;
   194You might want to simply set an application-specific token in a header.
   195Or you might want to mutate the response messages to be returned.
   196
   1971. Write a filter function.
   198
   199```go
   200func myFilter(ctx context.Context, w http.ResponseWriter, resp proto.Message) error {
   201	t, ok := resp.(*externalpb.Tokenizer)
   202	if ok {
   203		w.Header().Set("X-My-Tracking-Token", t.Token)
   204		t.Token = ""
   205	}
   206	return nil
   207}
   208```
   2092. Register the filter with [`WithForwardResponseOption`](https://pkg.go.dev/github.com/grpc-ecosystem/grpc-gateway/runtime?tab=doc#WithForwardResponseOption)
   210
   211e.g.
   212```go
   213mux := runtime.NewServeMux(
   214	runtime.WithForwardResponseOption(myFilter),
   215)
   216```
   217### Controlling HTTP response status codes
   218To have the most control over the HTTP response status codes, you can use custom metadata.
   219
   220While handling the rpc, set the intended status code:
   221
   222```go
   223grpc.SetHeader(ctx, metadata.Pairs("x-http-code", "401"))
   224```
   225
   226Now, before sending the HTTP response, we need to check for this metadata pair and explicitly set the status code for the response if found. 
   227To do so, create a function and hook it into the grpc-gateway as a Forward Response Option.
   228
   229The function looks like this:
   230```go
   231func httpResponseModifier(ctx context.Context, w http.ResponseWriter, p proto.Message) error {
   232	md, ok := runtime.ServerMetadataFromContext(ctx)
   233	if !ok {
   234		return nil
   235	}
   236
   237	// set http status code
   238	if vals := md.HeaderMD.Get("x-http-code"); len(vals) > 0 {
   239		code, err := strconv.Atoi(vals[0])
   240		if err != nil {
   241			return err
   242		}
   243		w.WriteHeader(code)
   244	// delete the headers to not expose any grpc-metadata in http response
   245		delete(md.HeaderMD, "x-http-code")
   246		delete(w.Header(), "Grpc-Metadata-X-Http-Code")
   247	}
   248
   249	return nil
   250}
   251```
   252
   253And it gets hooked into the grpc-gateway with:
   254
   255```go
   256gwMux := runtime.NewServeMux(
   257	runtime.WithForwardResponseOption(httpResponseModifier),
   258)
   259```
   260
   261## OpenTracing Support
   262
   263If your project uses [OpenTracing](https://github.com/opentracing/opentracing-go) and you'd like spans to propagate through the gateway, you can add some middleware which parses the incoming HTTP headers to create a new span correctly.
   264
   265```go
   266import (
   267	"github.com/opentracing/opentracing-go"
   268	"github.com/opentracing/opentracing-go/ext"
   269)
   270
   271var grpcGatewayTag = opentracing.Tag{Key: string(ext.Component), Value: "grpc-gateway"}
   272
   273func tracingWrapper(h http.Handler) http.Handler {
   274	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
   275		parentSpanContext, err := opentracing.GlobalTracer().Extract(
   276			opentracing.HTTPHeaders,
   277			opentracing.HTTPHeadersCarrier(r.Header))
   278		if err == nil || err == opentracing.ErrSpanContextNotFound {
   279			serverSpan := opentracing.GlobalTracer().StartSpan(
   280				"ServeHTTP",
   281				// this is magical, it attaches the new span to the parent parentSpanContext, and creates an unparented one if empty.
   282				ext.RPCServerOption(parentSpanContext),
   283				grpcGatewayTag,
   284			)
   285			r = r.WithContext(opentracing.ContextWithSpan(r.Context(), serverSpan))
   286			defer serverSpan.Finish()
   287		}
   288		h.ServeHTTP(w, r)
   289	})
   290}
   291
   292// Then just wrap the mux returned by runtime.NewServeMux() like this
   293if err := http.ListenAndServe(":8080", tracingWrapper(mux)); err != nil {
   294	log.Fatalf("failed to start gateway server on 8080: %v", err)
   295}
   296```
   297
   298Finally, don't forget to add a tracing interceptor when registering
   299the services. E.g.
   300
   301```go
   302import (
   303	"google.golang.org/grpc"
   304	"github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing"
   305)
   306
   307opts := []grpc.DialOption{
   308	grpc.WithUnaryInterceptor(
   309		grpc_opentracing.UnaryClientInterceptor(
   310			grpc_opentracing.WithTracer(opentracing.GlobalTracer()),
   311		),
   312	),
   313}
   314if err := pb.RegisterMyServiceHandlerFromEndpoint(ctx, mux, serviceEndpoint, opts); err != nil {
   315	log.Fatalf("could not register HTTP service: %v", err)
   316}
   317```
   318
   319## Error handler
   320The gateway uses two different error handlers for non-streaming requests:
   321
   322 * `runtime.HTTPError` is called for errors from backend calls
   323 * `runtime.OtherErrorHandler` is called for errors from parsing and routing client requests
   324
   325To override all error handling for a `*runtime.ServeMux`, use the
   326`runtime.WithProtoErrorHandler` serve option.
   327
   328Alternatively, you can override the global default `HTTPError` handling by
   329setting `runtime.GlobalHTTPErrorHandler` to a custom function, and override
   330the global default `OtherErrorHandler` by setting `runtime.OtherErrorHandler`
   331to a custom function.
   332
   333You should not set `runtime.HTTPError` directly, because that might break
   334any `ServeMux` set up with the `WithProtoErrorHandler` option.
   335
   336See https://mycodesmells.com/post/grpc-gateway-error-handler for an example
   337of writing a custom error handler function.
   338
   339## Stream Error Handler
   340The error handler described in the previous section applies only
   341to RPC methods that have a unary response.
   342
   343When the method has a streaming response, grpc-gateway handles
   344that by emitting a newline-separated stream of "chunks". Each
   345chunk is an envelope that can contain either a response message
   346or an error. Only the last chunk will include an error, and only
   347when the RPC handler ends abnormally (i.e. with an error code).
   348
   349Because of the way the errors are included in the response body,
   350the other error handler signature is insufficient. So for server
   351streams, you must install a _different_ error handler:
   352
   353```go
   354mux := runtime.NewServeMux(
   355	runtime.WithStreamErrorHandler(handleStreamError),
   356)
   357```
   358
   359The signature of the handler is much more rigid because we need
   360to know the structure of the error payload to properly
   361encode the "chunk" schema into a Swagger/OpenAPI spec.
   362
   363So the function must return a `*runtime.StreamError`. The handler
   364can choose to omit some fields and can filter/transform the original
   365error, such as stripping stack traces from error messages.
   366
   367Here's an example custom handler:
   368```go
   369// handleStreamError overrides default behavior for computing an error
   370// message for a server stream.
   371//
   372// It uses a default "502 Bad Gateway" HTTP code; only emits "safe"
   373// messages; and does not set gRPC code or details fields (so they will
   374// be omitted from the resulting JSON object that is sent to client).
   375func handleStreamError(ctx context.Context, err error) *runtime.StreamError {
   376	code := http.StatusBadGateway
   377	msg := "unexpected error"
   378	if s, ok := status.FromError(err); ok {
   379		code = runtime.HTTPStatusFromCode(s.Code())
   380		// default message, based on the name of the gRPC code
   381		msg = code.String()
   382		// see if error details include "safe" message to send
   383		// to external callers
   384		for _, msg := s.Details() {
   385			if safe, ok := msg.(*SafeMessage); ok {
   386				msg = safe.Text
   387				break
   388			}
   389		}
   390	}
   391	return &runtime.StreamError{
   392	    HttpCode:   int32(code),
   393	    HttpStatus: http.StatusText(code),
   394	    Message:    msg,
   395	}
   396}
   397```
   398
   399If no custom handler is provided, the default stream error handler
   400will include any gRPC error attributes (code, message, detail messages),
   401if the error being reported includes them. If the error does not have
   402these attributes, a gRPC code of `Unknown` (2) is reported. The default
   403handler will also include an HTTP code and status, which is derived
   404from the gRPC code (or set to `"500 Internal Server Error"` when
   405the source error has no gRPC attributes).

View as plain text