Skip to content

Commit

Permalink
Tidy up docs
Browse files Browse the repository at this point in the history
  • Loading branch information
johanbrandhorst committed May 24, 2020
1 parent 9e8664e commit 5f308a6
Show file tree
Hide file tree
Showing 8 changed files with 270 additions and 326 deletions.
228 changes: 97 additions & 131 deletions docs/_docs/customizingyourgateway.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down Expand Up @@ -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
}),
)
```

Expand All @@ -126,131 +127,127 @@ 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;
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

If 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.

```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)
}
```

Expand All @@ -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)
Expand Down Expand Up @@ -312,7 +308,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
Expand Down Expand Up @@ -362,34 +359,3 @@ these attributes, a gRPC code of `Unknown` (2) is reported. The default
handler will also include an HTTP code and status, which is derived
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"
"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
}
```
30 changes: 23 additions & 7 deletions docs/_docs/examples.md
Original file line number Diff line number Diff line change
Expand Up @@ -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))


Loading

0 comments on commit 5f308a6

Please sign in to comment.