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