diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 00000000000..507fa464415 Binary files /dev/null and b/.DS_Store differ diff --git a/docs/.DS_Store b/docs/.DS_Store new file mode 100644 index 00000000000..b0fd0721874 Binary files /dev/null and b/docs/.DS_Store differ diff --git a/docs/_docs/.DS_Store b/docs/_docs/.DS_Store new file mode 100644 index 00000000000..31a19f46b93 Binary files /dev/null and b/docs/_docs/.DS_Store differ diff --git a/docs/_docs/customizingyourgateway.md b/docs/_docs/customizingyourgateway.md index a90a8d75d7b..92e93223bc0 100644 --- a/docs/_docs/customizingyourgateway.md +++ b/docs/_docs/customizingyourgateway.md @@ -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. @@ -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 @@ -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 - // ... + // ... } ``` @@ -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 } ``` @@ -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 @@ -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 := "" @@ -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)) ``` @@ -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. @@ -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. @@ -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 @@ -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. @@ -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, + } } ``` @@ -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 } ```