From 75b67e3e6a6c58eb29cdbb93c5324c0b92445436 Mon Sep 17 00:00:00 2001 From: Johan Brandhorst Date: Sun, 24 May 2020 13:27:53 +0100 Subject: [PATCH 1/2] Tidy up docs --- docs/_docs/customizingyourgateway.md | 228 ++++++++++++--------------- docs/_docs/examples.md | 30 +++- docs/_docs/faq.md | 33 ++-- docs/_docs/grpcapiconfiguration.md | 20 +-- docs/_docs/httpbody.md | 16 +- docs/_docs/patch.md | 102 +++++++----- docs/_docs/season_of_docs.md | 1 + docs/_docs/usegotemplates.md | 47 +++--- 8 files changed, 252 insertions(+), 225 deletions(-) diff --git a/docs/_docs/customizingyourgateway.md b/docs/_docs/customizingyourgateway.md index e1df0aa3209..5830e74f377 100644 --- a/docs/_docs/customizingyourgateway.md +++ b/docs/_docs/customizingyourgateway.md @@ -13,27 +13,29 @@ You might want to serialize request/response messages in MessagePack instead of 1. Write a custom implementation of [`Marshaler`](https://pkg.go.dev/github.com/grpc-ecosystem/grpc-gateway/runtime?tab=doc#Marshaler) 2. Register your marshaler with [`WithMarshalerOption`](https://pkg.go.dev/github.com/grpc-ecosystem/grpc-gateway/runtime?tab=doc#WithMarshalerOption) - e.g. - ```go - var m your.MsgPackMarshaler - mux := runtime.NewServeMux(runtime.WithMarshalerOption("application/x-msgpack", m)) - ``` + e.g. + ```go + var m your.MsgPackMarshaler + mux := runtime.NewServeMux( + runtime.WithMarshalerOption("application/x-msgpack", m), + ) + ``` You can see [the default implementation for JSON](https://github.com/grpc-ecosystem/grpc-gateway/blob/master/runtime/marshal_jsonpb.go) for reference. ### 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 You can have Elasticsearch-style `?pretty` support in your gateway's endpoints as follows: 1. Wrap the ServeMux using a stdlib [`http.HandlerFunc`](https://golang.org/pkg/net/http/#HandlerFunc) - that translates the provided query parameter into a custom `Accept` header, and + that translates the provided query parameter into a custom `Accept` header, and 2. Register a pretty-printing marshaler for that MIME code. For example: @@ -112,11 +114,10 @@ default marshaler options: ```go mux := runtime.NewServeMux( - runtime.WithMarshalerOption("application/json+strict", - &m{ - JSONPb: &runtime.JSONPb{EmitDefaults: true}, - unmarshaler: &jsonpb.Unmarshaler{AllowUnknownFields: false}, // explicit "false", &jsonpb.Unmarshaler{} would have the same effect - }), + runtime.WithMarshalerOption("application/json+strict", &m{ + JSONPb: &runtime.JSONPb{EmitDefaults: true}, + unmarshaler: &jsonpb.Unmarshaler{AllowUnknownFields: false}, // explicit "false", &jsonpb.Unmarshaler{} would have the same effect + }), ) ``` @@ -126,70 +127,66 @@ You might not like [the default mapping rule](https://pkg.go.dev/github.com/grpc 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 - } - } - ... - - mux := runtime.NewServeMux(runtime.WithIncomingHeaderMatcher(CustomMatcher)) - ``` + 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), + ) + ``` + 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 func CustomMatcher(key string) (string, bool) { - switch key { - case "X-User-Id": - return key, true - default: - return runtime.DefaultHeaderMatcher(key) - } + switch key { + case "X-User-Id": + return key, true + default: + return runtime.DefaultHeaderMatcher(key) + } } ``` It will work with both: -```bash -curl --header "x-user-id: 100d9f38-2777-4ee2-ac3b-b3a108f81a30" ... +```shell +$ curl --header "x-user-id: 100d9f38-2777-4ee2-ac3b-b3a108f81a30" ... ``` and: -```bash -curl --header "X-USER-ID: 100d9f38-2777-4ee2-ac3b-b3a108f81a30" ... +```shell +$ curl --header "X-USER-ID: 100d9f38-2777-4ee2-ac3b-b3a108f81a30" ... ``` To access this header on gRPC server side use: ```go -... userID := "" if md, ok := metadata.FromIncomingContext(ctx); ok { - if uID, ok := md["x-user-id"]; ok { - userID = strings.Join(uID, ",") - } + if uID, ok := md["x-user-id"]; ok { + userID = strings.Join(uID, ",") + } } -... ``` ## 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. - - ```go - ... - if appendCustomHeader { - grpc.SendHeader(ctx, metadata.New(map[string]string{ - "x-custom-header1": "value", - })) - } - ``` +for more info on sending / receiving gRPC metadata, e.g. +```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; @@ -197,24 +194,25 @@ 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 = "" + ```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)) - ``` + e.g. + ```go + mux := runtime.NewServeMux( + runtime.WithForwardResponseOption(myFilter), + ) + ``` ## OpenTracing Support @@ -222,35 +220,34 @@ If your project uses [OpenTracing](https://github.com/opentracing/opentracing-go ```go import ( - ... - "github.com/opentracing/opentracing-go" - "github.com/opentracing/opentracing-go/ext" + "github.com/opentracing/opentracing-go" + "github.com/opentracing/opentracing-go/ext" ) var grpcGatewayTag = opentracing.Tag{Key: string(ext.Component), Value: "grpc-gateway"} func tracingWrapper(h http.Handler) http.Handler { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - parentSpanContext, err := opentracing.GlobalTracer().Extract( - opentracing.HTTPHeaders, - opentracing.HTTPHeadersCarrier(r.Header)) - if err == nil || err == opentracing.ErrSpanContextNotFound { - serverSpan := opentracing.GlobalTracer().StartSpan( - "ServeHTTP", - // this is magical, it attaches the new span to the parent parentSpanContext, and creates an unparented one if empty. - ext.RPCServerOption(parentSpanContext), - grpcGatewayTag, - ) - r = r.WithContext(opentracing.ContextWithSpan(r.Context(), serverSpan)) - defer serverSpan.Finish() - } - h.ServeHTTP(w, r) - }) + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + parentSpanContext, err := opentracing.GlobalTracer().Extract( + opentracing.HTTPHeaders, + opentracing.HTTPHeadersCarrier(r.Header)) + if err == nil || err == opentracing.ErrSpanContextNotFound { + serverSpan := opentracing.GlobalTracer().StartSpan( + "ServeHTTP", + // this is magical, it attaches the new span to the parent parentSpanContext, and creates an unparented one if empty. + ext.RPCServerOption(parentSpanContext), + grpcGatewayTag, + ) + r = r.WithContext(opentracing.ContextWithSpan(r.Context(), serverSpan)) + defer serverSpan.Finish() + } + h.ServeHTTP(w, r) + }) } // Then just wrap the mux returned by runtime.NewServeMux() like this if err := http.ListenAndServe(":8080", tracingWrapper(mux)); err != nil { - log.Fatalf("failed to start gateway server on 8080: %v", err) + log.Fatalf("failed to start gateway server on 8080: %v", err) } ``` @@ -259,17 +256,16 @@ the services. E.g. ```go import ( - ... - "google.golang.org/grpc" - "github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing" + "google.golang.org/grpc" + "github.com/grpc-ecosystem/go-grpc-middleware/tracing/opentracing" ) opts := []grpc.DialOption{ - grpc.WithUnaryInterceptor( - grpc_opentracing.UnaryClientInterceptor( - grpc_opentracing.WithTracer(opentracing.GlobalTracer()), - ), - ), + grpc.WithUnaryInterceptor( + grpc_opentracing.UnaryClientInterceptor( + grpc_opentracing.WithTracer(opentracing.GlobalTracer()), + ), + ), } if err := pb.RegisterMyServiceHandlerFromEndpoint(ctx, mux, serviceEndpoint, opts); err != nil { log.Fatalf("could not register HTTP service: %v", err) @@ -302,7 +298,8 @@ 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 @@ -345,34 +342,3 @@ If no custom handler is provided, the default stream error handler will include any gRPC error attributes (code, message, detail messages), if the error being reported includes them. If the error does not have these attributes, a gRPC code of `Unknown` (2) is reported. - -## 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" - - "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...) - } - - func init() { - forward_MyService_Checkout_0 = forwardCheckoutResp - } - ``` diff --git a/docs/_docs/examples.md b/docs/_docs/examples.md index 81003500600..cf22cc3d7ec 100644 --- a/docs/_docs/examples.md +++ b/docs/_docs/examples.md @@ -5,13 +5,29 @@ category: documentation # Examples Examples are available under `examples/internal` directory. -* `proto/examplepb/echo_service.proto`, `proto/examplepb/a_bit_of_everything.proto`, `proto/examplepb/unannotated_echo_service.proto`: service definition - * `proto/examplepb/echo_service.pb.go`, `proto/examplepb/a_bit_of_everything.pb.go`, `proto/examplepb/unannotated_echo_service.pb.go`: [generated] stub of the service - * `proto/examplepb/echo_service.pb.gw.go`, `proto/examplepb/a_bit_of_everything.pb.gw.go`, `proto/examplepb/uannotated_echo_service.pb.gw.go`: [generated] reverse proxy for the service - * `proto/examplepb/unannotated_echo_service.yaml`: gRPC API Configuration for ```unannotated_echo_service.proto``` -* `server/main.go`: service implementation -* `main.go`: entrypoint of the generated reverse proxy +* [`proto/examplepb/echo_service.proto`](https://github.com/grpc-ecosystem/grpc-gateway/tree/master/examples/internal/proto/examplepb/echo_service.proto), + [`proto/examplepb/a_bit_of_everything.proto`](https://github.com/grpc-ecosystem/grpc-gateway/tree/master/examples/internal/proto/examplepb/a_bit_of_everything.proto), + [`proto/examplepb/unannotated_echo_service.proto`](https://github.com/grpc-ecosystem/grpc-gateway/tree/master/examples/internal/proto/examplepb/unannotated_echo_service.proto): + protobuf service definitions. +* [`proto/examplepb/echo_service.pb.go`](https://github.com/grpc-ecosystem/grpc-gateway/tree/master/examples/internal/proto/examplepb/echo_service.pb.go), + [`proto/examplepb/a_bit_of_everything.pb.go`](https://github.com/grpc-ecosystem/grpc-gateway/tree/master/examples/internal/proto/examplepb/a_bit_of_everything.pb.go), + [`proto/examplepb/unannotated_echo_service.pb.go`](https://github.com/grpc-ecosystem/grpc-gateway/tree/master/examples/internal/proto/examplepb/unannotated_echo_service.pb.go): + generated Go service stubs and types. +* [`proto/examplepb/echo_service.pb.gw.go`](https://github.com/grpc-ecosystem/grpc-gateway/tree/master/examples/internal/proto/examplepb/echo_service.pb.gw.go), + [`proto/examplepb/a_bit_of_everything.pb.gw.go`](https://github.com/grpc-ecosystem/grpc-gateway/tree/master/examples/internal/proto/examplepb/a_bit_of_everything.pb.gw.go), + [`proto/examplepb/uannotated_echo_service.pb.gw.go`](https://github.com/grpc-ecosystem/grpc-gateway/tree/master/examples/internal/proto/examplepb/uannotated_echo_service.pb.gw.go): + generated gRPC-gateway clients. + * [`proto/examplepb/unannotated_echo_service.yaml`](https://github.com/grpc-ecosystem/grpc-gateway/tree/master/examples/internal/proto/examplepb/uannotated_echo_service.yaml): + gRPC API Configuration for `unannotated_echo_service.proto`. +* [`server/main.go`](https://github.com/grpc-ecosystem/grpc-gateway/tree/master/examples/internal/server/main.go): + service implementation. +* [`main.go`](https://github.com/grpc-ecosystem/grpc-gateway/tree/master/examples/internal/gateway/main.go): + entrypoint of the generated reverse proxy. -To use the same port for custom HTTP handlers (e.g. serving `swagger.json`), gRPC-gateway, and a gRPC server, see [this code example by CoreOS](https://github.com/philips/grpc-gateway-example/blob/master/cmd/serve.go) (and its accompanying [blog post](https://coreos.com/blog/grpc-protobufs-swagger.html)) +To use the same port for custom HTTP handlers (e.g. serving `swagger.json`), +gRPC-gateway, and a gRPC server, see +[this code example by CoreOS](https://github.com/philips/grpc-gateway-example/blob/master/cmd/serve.go) +(and its accompanying +[blog post](https://coreos.com/blog/grpc-protobufs-swagger.html)) diff --git a/docs/_docs/faq.md b/docs/_docs/faq.md index bb62fabf446..348f77387b7 100644 --- a/docs/_docs/faq.md +++ b/docs/_docs/faq.md @@ -6,27 +6,42 @@ title: FAQ # FAQ ## How can I write the annotations which grpc-gateway requires? -Grpc-gateway follows the spec of [`google.api.HttpRule`](https://github.com/googleapis/googleapis/blob/master/google/api/http.proto). -So first check out the documentation if it is feasible in the spec. +The gRPC-Gateway follows the spec of +[`google.api.HttpRule`](https://github.com/googleapis/googleapis/blob/master/google/api/http.proto), +so first check out the documentation if it is feasible in the spec. -See also [a past discussion](https://groups.google.com/d/msg/grpc-io/Xqx80hG0D44/VNCDHjeE6pUJ) in grpc-io mailing list. +See also [a past discussion](https://groups.google.com/d/msg/grpc-io/Xqx80hG0D44/VNCDHjeE6pUJ) +in the grpc-io mailing list. ## I want to support a certain style of HTTP request but the code generated by grpc-gateway does not. How can I support this style? See the question above at first. -Grpc-gateway is intended to cover 80% of use cases without forcing you to write comprehensive but complicated annotations. So grpc-gateway itself does not always cover all the use cases you have by design. In other words, grpc-gateway automates typical boring boilerplate mapping between gRPC and HTTP/1 communication, but it does not do arbitrarily complex custom mappings for you. +The gRPC-Gateway is intended to cover 80% of use cases without forcing you to write comprehensive but +complicated annotations. So the gateway itself does not always cover all the use cases you +have by design. In other words, the gateway automates typical boring boilerplate mapping +between gRPC and HTTP/1 communication, but it does not do arbitrarily complex custom mappings for you. -On the other hand, you can still add whatever you want as a middleware which wraps [`runtime.ServeMux`](https://pkg.go.dev/github.com/grpc-ecosystem/grpc-gateway/runtime?tab=doc#ServeMux). Since `runtime.ServeMux` is just a standard [`http.Handler`](http://golang.org/pkg/http#Handler), you can easily write a custom wrapper of `runtime.ServeMux`, leveraged with existing third-party libraries in Go. -e.g. https://github.com/grpc-ecosystem/grpc-gateway/blob/master/examples/internal/main.go +On the other hand, you can still add whatever you want as a middleware which wraps +[`runtime.ServeMux`](https://pkg.go.dev/github.com/grpc-ecosystem/grpc-gateway/runtime?tab=doc#ServeMux). +Since `runtime.ServeMux` is just a standard [`http.Handler`](http://golang.org/pkg/http#Handler), +you can easily write a custom wrapper of `runtime.ServeMux`, leveraged with existing third-party +libraries in Go. +e.g. https://github.com/grpc-ecosystem/grpc-gateway/blob/master/examples/internal/gateway/main.go ## My gRPC server is written in (Scala|C++|Ruby|Haskell|....). Is there a (Scala|C++|Ruby|Haskell|....) version of grpc-gateway? -AFAIK, no. But it should not be a big issue because the reverse-proxy which grpc-gateway generates usually works as an independent process and communicates with your gRPC server over TCP or a unix-domain socket. +AFAIK, no. But it should not be a big issue because the reverse-proxy which grpc-gateway generates +usually works as an independent process and communicates with your gRPC server over TCP or a unix-domain socket. ## Why are the models in the swagger specification prefixed with the last part of the proto package name? -The reason to generate the prefixes is that we don't have a guaranteed unique namespace. If two packages produce different Foo messages then we will have trouble. +The reason to generate the prefixes is that we don't have a guaranteed unique namespace. +If two packages produce different Foo messages then we will have trouble. ## Why not strip the prefix? -When a message is added which happens to conflict with another message (e.g. by importing a message with the same name from a different package) it will break code that is very far away from the code that changed. This is in an effort to adhere to the [principle of least astonishment](https://en.wikipedia.org/wiki/Principle_of_least_astonishment). +When a message is added which happens to conflict with another message +(e.g. by importing a message with the same name from a different package) +it will break code that is very far away from the code that changed. +This is in an effort to adhere to the +[principle of least astonishment](https://en.wikipedia.org/wiki/Principle_of_least_astonishment). diff --git a/docs/_docs/grpcapiconfiguration.md b/docs/_docs/grpcapiconfiguration.md index aa1b8408c0a..0c0846aa777 100644 --- a/docs/_docs/grpcapiconfiguration.md +++ b/docs/_docs/grpcapiconfiguration.md @@ -16,8 +16,8 @@ The following is equivalent to the basic [usage example](usage.html) but without 1. Define your service in gRPC as usual - your_service.proto: - ```protobuf + your_service.proto: + ```protobuf syntax = "proto3"; package your.service.v1; option go_package = "github.com/yourorg/yourprotos/gen/go/your/service/v1"; @@ -28,9 +28,10 @@ The following is equivalent to the basic [usage example](usage.html) but without service YourService { rpc Echo(StringMessage) returns (StringMessage) {} } - ``` + ``` -2. Instead of annotating the .proto file in this step leave it untouched and create a `your_service.yaml` with the following content: +2. Instead of annotating the .proto file in this step leave it untouched + and create a `your_service.yaml` with the following content: ```yaml type: google.api.Service config_version: 3 @@ -53,12 +54,13 @@ The following is equivalent to the basic [usage example](usage.html) but without 4. Implement your service in gRPC as usual -5. Generate the reverse-proxy. Here we have to pass the path to the `your_service.yaml` in addition to the .proto file: +5. Generate the reverse-proxy. Here we have to pass the path to + the `your_service.yaml` in addition to the .proto file: - ```sh - protoc -I. --grpc-gateway_out=logtostderr=true,paths=source_relative,grpc_api_configuration=path/to/your_service.yaml:./gen/go \ - your/service/v1/your_service.proto - ``` + ```sh + protoc -I. --grpc-gateway_out=logtostderr=true,paths=source_relative,grpc_api_configuration=path/to/your_service.yaml:./gen/go \ + your/service/v1/your_service.proto + ``` This will generate a reverse proxy `gen/go/your/service/v1/your_service.pb.gw.go` that is identical to the one produced for the annotated proto. diff --git a/docs/_docs/httpbody.md b/docs/_docs/httpbody.md index 5ca58c30c0a..196034680fb 100644 --- a/docs/_docs/httpbody.md +++ b/docs/_docs/httpbody.md @@ -2,44 +2,41 @@ category: documentation --- -# HttpBody message Feature +# HttpBody messages The [HTTP Body](https://github.com/googleapis/googleapis/blob/master/google/api/httpbody.proto) messages allows a response message to be specified with custom data content and a custom content type header. The values included in the HTTPBody response will be used verbatim in the returned message from the gateway. Make sure you format your response carefully! ## Example Usage 1. Create a mux and configure it to use the `HTTPBodyMarshaler`. -```golang +```protobuf mux := runtime.NewServeMux() runtime.SetHTTPBodyMarshaler(mux) ``` 2. Define your service in gRPC with an httpbody response message -```golang +```protobuf import "google/api/httpbody.proto"; import "google/api/annotations.proto"; import "google/protobuf/empty.proto"; service HttpBodyExampleService { - - rpc HelloWorld(google.protobuf.Empty) returns (google.api.HttpBody) { + rpc HelloWorld(google.protobuf.Empty) returns (google.api.HttpBody) { option (google.api.http) = { get: "/helloworld" }; } - rpc Download(google.protobuf.Empty) returns (stream google.api.HttpBody) { option (google.api.http) = { get: "/download" }; } - } ``` 3. Generate gRPC and reverse-proxy stubs and implement your service. ## Example service implementation -```golang +```go func (*HttpBodyExampleService) Helloworld(ctx context.Context, in *empty.Empty) (*httpbody.HttpBody, error) { return &httpbody.HttpBody{ ContentType: "text/html", @@ -64,5 +61,4 @@ func (HttpBodyExampleService) Download(_ *empty.Empty, stream HttpBodyExampleSer return nil } - -``` \ No newline at end of file +``` diff --git a/docs/_docs/patch.md b/docs/_docs/patch.md index d1543eb8350..f31ce9a865c 100644 --- a/docs/_docs/patch.md +++ b/docs/_docs/patch.md @@ -5,60 +5,86 @@ category: documentation # Patch Feature The HTTP PATCH method allows a resource to be partially updated. -The idea, If a binding is mapped to patch and the request message has exactly one FieldMask message in it, additional code is rendered for the gateway handler that will populate the FieldMask based on the request body. +If a binding is mapped to patch and the request message has exactly one +FieldMask message in it, additional code is rendered for the gateway +handler that will populate the FieldMask based on the request body. + There are two scenarios: -- The FieldMask is hidden from the REST request as per the [Google API design guide](https://cloud.google.com/apis/design/standard_methods#update) (as in the first additional binding in the [UpdateV2](https://github.com/grpc-ecosystem/grpc-gateway/blob/master/examples/internal/proto/examplepb/a_bit_of_everything.proto#L366) example). In this case, the FieldMask is updated from the request body and set in the gRPC request message. -- The FieldMask is exposed to the REST request (as in the second additional binding in the [UpdateV2](https://github.com/grpc-ecosystem/grpc-gateway/blob/master/examples/internal/proto/examplepb/a_bit_of_everything.proto#L370) example). For this case, the field mask is left untouched by the gateway. +- The FieldMask is hidden from the REST request as per the + [Google API design guide](https://cloud.google.com/apis/design/standard_methods#update) + (as in the first additional binding in the + [UpdateV2](https://github.com/grpc-ecosystem/grpc-gateway/blob/master/examples/internal/proto/examplepb/a_bit_of_everything.proto#L366) + example). + In this case, the FieldMask is updated from the request body and + set in the gRPC request message. +- The FieldMask is exposed to the REST request (as in the second + additional binding in the + [UpdateV2](https://github.com/grpc-ecosystem/grpc-gateway/blob/master/examples/internal/proto/examplepb/a_bit_of_everything.proto#L370) + example). + For this case, the field mask is left untouched by the gateway. ## Example Usage 1. Create PATCH request. - The PATCH request needs to include the message and the update mask. -```golang -// UpdateV2Request request for update includes the message and the update mask -message UpdateV2Request { - ABitOfEverything abe = 1; - google.protobuf.FieldMask update_mask = 2; -} -``` + The PATCH request needs to include the message and the update mask. + ```protobuf + // UpdateV2Request request for update includes the message and the update mask + message UpdateV2Request { + ABitOfEverything abe = 1; + google.protobuf.FieldMask update_mask = 2; + } + ``` 2. Define your service in gRPC -If you want to use PATCH with fieldmask hidden from REST request only include the request message in the body. + If you want to use PATCH with fieldmask hidden from REST request only include the request message in the body. -```golang -rpc UpdateV2(UpdateV2Request) returns (google.protobuf.Empty) { - option (google.api.http) = { - put: "/v2/example/a_bit_of_everything/{abe.uuid}" - body: "abe" - additional_bindings { - patch: "/v2/example/a_bit_of_everything/{abe.uuid}" + ```protobuf + rpc UpdateV2(UpdateV2Request) returns (google.protobuf.Empty) { + option (google.api.http) = { + put: "/v2/example/a_bit_of_everything/{abe.uuid}" body: "abe" - } - }; -} -``` + additional_bindings { + patch: "/v2/example/a_bit_of_everything/{abe.uuid}" + body: "abe" + } + }; + } + ``` -If you want to use PATCH with fieldmask exposed to the REST request then include the entire request message. + If you want to use PATCH with fieldmask exposed to the REST request then include the entire request message. -```golang -rpc UpdateV2(UpdateV2Request) returns (google.protobuf.Empty) { - option (google.api.http) = { - patch: "/v2a/example/a_bit_of_everything/{abe.uuid}" - body: "*" - }; -} -``` + ```protobuf + rpc UpdateV2(UpdateV2Request) returns (google.protobuf.Empty) { + option (google.api.http) = { + patch: "/v2a/example/a_bit_of_everything/{abe.uuid}" + body: "*" + }; + } + ``` 3. Generate gRPC and reverse-proxy stubs and implement your service. ## Curl examples -In the example below we will partially update our ABitOfEverything resource by passing only the field we want to change. Since we are using the endpoint with field mask hidden we only need to pass the field we want to change ("string_value") and it will keep everything else in our resource the same. -``` -curl --data '{"string_value": "strprefix/foo"}' -X PATCH http://address:port/v2/example/a_bit_of_everything/1 +In the example below we will partially update our ABitOfEverything +resource by passing only the field we want to change. Since we are +using the endpoint with field mask hidden we only need to pass the +field we want to change ("string_value") and it will keep everything +else in our resource the same. +```shell +$ curl \ + --data '{"string_value": "strprefix/foo"}' \ + -X PATCH \ + http://address:port/v2/example/a_bit_of_everything/1 ``` -If we know what fields we want to update then we can use PATCH with field mask approach. For this we need to pass the resource and the update_mask. Below only the "single_nested" will get updated because that is what we specify in the field_mask. -``` -curl --data '{"abe":{"single_nested":{"amount":457},"string_value":"some value that won't get updated because not in the field mask"},"update_mask":{"paths":["single_nested"]}}' -X PATCH http://address:port/v2a/example/a_bit_of_everything/1 +If we know what fields we want to update then we can use PATCH with +field mask approach. For this we need to pass the resource and the +update_mask. Below only the "single_nested" will get updated because +that is what we specify in the field_mask. +```shell +$ curl \ + --data '{"abe":{"single_nested":{"amount":457},"string_value":"some value that won't get updated because not in the field mask"},"update_mask":{"paths":["single_nested"]}}' \ + -X PATCH \ + http://address:port/v2a/example/a_bit_of_everything/1 ``` diff --git a/docs/_docs/season_of_docs.md b/docs/_docs/season_of_docs.md index 79262ce2698..ce273c2aa96 100644 --- a/docs/_docs/season_of_docs.md +++ b/docs/_docs/season_of_docs.md @@ -1,5 +1,6 @@ --- category: documentation +name: 2020 Season of Docs --- # 2020 Season of Docs diff --git a/docs/_docs/usegotemplates.md b/docs/_docs/usegotemplates.md index f2ae6c9ce5c..68649b2f21a 100644 --- a/docs/_docs/usegotemplates.md +++ b/docs/_docs/usegotemplates.md @@ -1,17 +1,24 @@ -| Title | Category | -| -------------------------------------- | ------------- | -| Use go templates in protofile comments | Documentation | +--- +category: documentation +name: Use go templates in protofile comments +--- # Use go templates in protofile comments -Use [Go templates](https://golang.org/pkg/text/template/ "Package template") in your protofile comments to allow more advanced documentation such as: +Use [Go templates](https://golang.org/pkg/text/template/) +in your protofile comments to allow more advanced documentation such +as: * Documentation about fields in the proto objects. -* Import the content of external files (such as [Markdown](https://en.wikipedia.org/wiki/Markdown "Markdown Github")). +* Import the content of external files (such as + [Markdown](https://en.wikipedia.org/wiki/Markdown)). ## How to use it -By default this function is turned off, so if you want to use it you have to set the `use_go_templates` flag to true inside of the `swagger_out` flag. -```bash +By default this function is turned off, so if you want to use it you +have to set the `use_go_templates` flag to true inside of the +`swagger_out` flag. + +```shell --swagger_out=use_go_templates=true:. ``` @@ -19,15 +26,12 @@ By default this function is turned off, so if you want to use it you have to set Example of a bash script with the `use_go_templates` flag set to true: -```bash -protoc -I/usr/local/include -I. \ - -I$GOPATH/src \ - -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway/third_party/googleapis \ - -I$GOPATH/src/github.com/grpc-ecosystem/grpc-gateway \ - --go_out=plugins=grpc:. \ - --grpc-gateway_out=logtostderr=true:. \ - --swagger_out=logtostderr=true,use_go_templates=true:. \ - *.proto +```shell +$ protoc -I. \ + --go_out=plugins=grpc:. \ + --grpc-gateway_out=logtostderr=true:. \ + --swagger_out=logtostderr=true,use_go_templates=true:. \ + path/to/my/proto/v1/myproto.proto ``` ### Example proto file @@ -80,14 +84,15 @@ The content of `tables.md`: ### SwaggerUI -This is how the swagger file would be rendered in [SwaggerUI](https://swagger.io/tools/swagger-ui/ "SwaggerUI site") +This is how the swagger file would be rendered in [SwaggerUI](https://swagger.io/tools/swagger-ui/) -![Screenshot swaggerfile in SwaggerUI](../_imgs/gotemplates/swaggerui.png "SwaggerUI") +![Screenshot swaggerfile in SwaggerUI](../_imgs/gotemplates/swaggerui.png) ### Postman -This is how the swagger file would be rendered in [Postman](https://www.getpostman.com/ "Postman site") +This is how the swagger file would be rendered in [Postman](https://www.getpostman.com/) -![Screenshot swaggerfile in Postman](../_imgs/gotemplates/postman.png "Postman") +![Screenshot swaggerfile in Postman](../_imgs/gotemplates/postman.png) -For a more detailed example of a protofile that has Go templates enabled, [click here](https://github.com/grpc-ecosystem/grpc-gateway/blob/master/examples/internal/proto/examplepb/use_go_template.proto "Example protofile with Go template"). +For a more detailed example of a protofile that has Go templates enabled, +[see the examples](https://github.com/grpc-ecosystem/grpc-gateway/blob/master/examples/internal/proto/examplepb/use_go_template.proto). From cdf28461e4f6fc632e180108030982707a8ac619 Mon Sep 17 00:00:00 2001 From: Johan Brandhorst Date: Sun, 24 May 2020 10:04:13 +0100 Subject: [PATCH 2/2] Update docs for v2 --- docs/_docs/customizingyourgateway.md | 81 +++++++------------ docs/_docs/httpbody.md | 8 +- docs/_docs/v2-migration.md | 115 +++++++++++++++++++++++++++ 3 files changed, 144 insertions(+), 60 deletions(-) create mode 100644 docs/_docs/v2-migration.md diff --git a/docs/_docs/customizingyourgateway.md b/docs/_docs/customizingyourgateway.md index 5830e74f377..8528a4a4fc5 100644 --- a/docs/_docs/customizingyourgateway.md +++ b/docs/_docs/customizingyourgateway.md @@ -23,11 +23,21 @@ You might want to serialize request/response messages in MessagePack instead of You can see [the default implementation for JSON](https://github.com/grpc-ecosystem/grpc-gateway/blob/master/runtime/marshal_jsonpb.go) for reference. -### Using camelCase for JSON +### Using proto names in 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, +The protocol buffer compiler generates camelCase JSON tags that are used by default. +If you want to use the exact case used in the proto files, set `UseProtoNames: true`: ```go -mux := runtime.NewServeMux(runtime.WithMarshalerOption(runtime.MIMEWildcard, &runtime.JSONPb{OrigName:false})) +mux := runtime.NewServeMux( + runtime.WithMarshalerOption(runtime.MIMEWildcard, &runtime.JSONPb{ + MarshalOptions: protojson.MarshalOptions{ + UseProtoNames: true, + }, + UnmarshalOptions: protojson.UnmarshalOptions{ + DiscardUnknown: true, + }, + }), +) ``` ### Pretty-print JSON responses when queried with ?pretty @@ -42,7 +52,15 @@ For example: ```go mux := runtime.NewServeMux( - runtime.WithMarshalerOption("application/json+pretty", &runtime.JSONPb{Indent: " "}), + runtime.WithMarshalerOption("application/json+pretty", &runtime.JSONPb{ + MarshalOptions: protojson.MarshalOptions{ + Indent: " ", + Multiline: true, // Optional, implied by presence of "Indent". + }, + UnmarshalOptions: protojson.UnmarshalOptions{ + DiscardUnknown: true, + }, + }), ) prettier := func(h http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { @@ -57,22 +75,6 @@ prettier := func(h http.Handler) http.Handler { http.ListenAndServe(":8080", prettier(mux)) ``` -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 to appear between fields and array - // elements. - Indent string - - // ... -} -``` - Now, either when passing the header `Accept: application/json+pretty` or appending `?pretty` to your HTTP endpoints, the response will be pretty-printed. @@ -81,42 +83,15 @@ 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 and passing that to `runtime.WithMarshalerOption`: - -```go -type m struct { - *runtime.JSONPb - unmarshaler *jsonpb.Unmarshaler -} - -type decoderWrapper struct { - *json.Decoder - *jsonpb.Unmarshaler -} - -func (n *m) NewDecoder(r io.Reader) runtime.Decoder { - 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 -} -``` - -This scaffolding allows us to pass a custom unmarshal options. In this example, we configure the -unmarshaler to disallow unknown fields. For demonstration purposes, we'll also change some of the -default marshaler options: +Having different unmarshaling options per Content-Type is as easy as +configuring a custom marshaler: ```go mux := runtime.NewServeMux( - runtime.WithMarshalerOption("application/json+strict", &m{ - JSONPb: &runtime.JSONPb{EmitDefaults: true}, - unmarshaler: &jsonpb.Unmarshaler{AllowUnknownFields: false}, // explicit "false", &jsonpb.Unmarshaler{} would have the same effect + runtime.WithMarshalerOption("application/json+strict", &runtime.JSONPb{ + UnmarshalOptions: &protojson.UnmarshalOptions{ + DiscardUnknown: false, // explicit "false", &protojson.UnmarshalOptions{} would have the same effect + }, }), ) ``` diff --git a/docs/_docs/httpbody.md b/docs/_docs/httpbody.md index 196034680fb..afca9554dfd 100644 --- a/docs/_docs/httpbody.md +++ b/docs/_docs/httpbody.md @@ -6,13 +6,7 @@ category: documentation The [HTTP Body](https://github.com/googleapis/googleapis/blob/master/google/api/httpbody.proto) messages allows a response message to be specified with custom data content and a custom content type header. The values included in the HTTPBody response will be used verbatim in the returned message from the gateway. Make sure you format your response carefully! ## Example Usage -1. Create a mux and configure it to use the `HTTPBodyMarshaler`. - -```protobuf - mux := runtime.NewServeMux() - runtime.SetHTTPBodyMarshaler(mux) -``` -2. Define your service in gRPC with an httpbody response message +1. Define your service in gRPC with an httpbody response message ```protobuf import "google/api/httpbody.proto"; diff --git a/docs/_docs/v2-migration.md b/docs/_docs/v2-migration.md new file mode 100644 index 00000000000..8e1b749f7d1 --- /dev/null +++ b/docs/_docs/v2-migration.md @@ -0,0 +1,115 @@ +--- +title: v2 migration guide +category: documentation +--- + +# gRPC-Gateway v2 migration guide + +This guide is supposed to help users of the gateway migrate from v1 to v2. +See https://github.com/grpc-ecosystem/grpc-gateway/issues/1223 for detailed +information on all changes that were made specifically to v2. + +The following behavioural defaults have been changed: + +## We now use the camelCase JSON names by default +See +[the original issue](https://github.com/grpc-ecosystem/grpc-gateway/issues/375) +and +[original pull request](https://github.com/grpc-ecosystem/grpc-gateway/issues/375) +for more information. + +If you want to revert to the old behaviour, configure a custom marshaler with +`UseProtoNames: true`: +```go +mux := runtime.NewServeMux( + runtime.WithMarshalerOption(runtime.MIMEWildcard, &runtime.JSONPb{ + MarshalOptions: protojson.MarshalOptions{ + UseProtoNames: true, + }, + UnmarshalOptions: protojson.UnmarshalOptions{ + DiscardUnknown: true, + }, + }), +) +``` + +To change the swagger generator behaviour to match, set `json_names_for_fields=false` when generating: + +```shell +--swagger_out=json_names_for_fields=false:./gen/swagger path/to/my/proto/v1/myproto.proto +``` + +## We now emit default vaules for all fields + +See [the original issue](https://github.com/grpc-ecosystem/grpc-gateway/issues/233) +for more information. + +If you want to revert to the old behaviour, configure a custom marshaler with +`EmitUnpopulated: false`: +```go +mux := runtime.NewServeMux( + runtime.WithMarshalerOption(runtime.MIMEWildcard, &runtime.JSONPb{ + MarshalOptions: protojson.MarshalOptions{ + EmitUnpopulated: false, + }, + UnmarshalOptions: protojson.UnmarshalOptions{ + DiscardUnknown: true, + }, + }), +) +``` + +## We now support google.api.HttpBody message types by default + +The `runtime.SetHTTPBodyMarshaler` function has disappeared, and is now +enabled by default. If you for some reason don't want `HttpBody` messages to be +respected, you can disable it by overwriting the default marshaler: + +```go +mux := runtime.NewServeMux( + runtime.WithMarshalerOption(runtime.MIMEWildcard, &runtime.JSONPb{ + MarshalOptions: protojson.MarshalOptions{ + EmitUnpopulated: true, + }, + UnmarshalOptions: protojson.UnmarshalOptions{ + DiscardUnknown: true, + }, + }), +) +``` + +## runtime.DisallowUnknownFields has been removed + +All marshalling settings are now inherited from the configured marshaler. If you wish +to disallow unknown fields, configure a custom marshaler: + +```go +mux := runtime.NewServeMux( + runtime.WithMarshalerOption(runtime.MIMEWildcard, &runtime.JSONPb{ + MarshalOptions: protojson.MarshalOptions{ + EmitUnpopulated: true, + }, + UnmarshalOptions: protojson.UnmarshalOptions{ + DiscardUnknown: false, + }, + }), +) +``` + +## WithLastMatchWins and allow_colon_final_segments=true is now default behaviour + +If you were previously specifying these, please remove them, as this is now +the default behaviour. See +[the original issue](https://github.com/grpc-ecosystem/grpc-gateway/issues/224) +for more information. + +There is no workaround for this, as we considered it a correct interpretation of the spec. +If this breaks your application, carefully consider the order in which you define your +services. + +## Error handling configuration has been overhauled + +`runtime.HTTPError`, `runtime.OtherErrorHandler`, `runtime.GlobalHTTPErrorHandler`, +`runtime.WithProtoErrorHandler` are all gone. Error handling is rewritten around the +use of gRPCs Status types. If you wish to configure how the gateway handles errors, +please use `runtime.WithErrorHandler` and `runtime.WithStreamErrorHandler`.