Skip to content

Commit

Permalink
Consolidate error handling configuration
Browse files Browse the repository at this point in the history
All unary responses are now handled with a single function,
and are configured in a single place.
  • Loading branch information
johanbrandhorst committed May 3, 2020
1 parent 53da419 commit 245b80d
Show file tree
Hide file tree
Showing 11 changed files with 121 additions and 644 deletions.
88 changes: 8 additions & 80 deletions docs/_docs/customizingyourgateway.md
Original file line number Diff line number Diff line change
Expand Up @@ -234,91 +234,19 @@ if err := pb.RegisterMyServiceHandlerFromEndpoint(ctx, mux, serviceEndpoint, opt
```

## 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

To override all error handling for a `*runtime.ServeMux`, use the
`runtime.WithProtoErrorHandler` serve option.

Alternatively, you can override the global default `HTTPError` handling by
setting `runtime.GlobalHTTPErrorHandler` to a custom function, and override
the global default `OtherErrorHandler` by setting `runtime.OtherErrorHandler`
to a custom function.

You should not set `runtime.HTTPError` directly, because that might break
any `ServeMux` set up with the `WithProtoErrorHandler` option.
To override error handling for a `*runtime.ServeMux`, use the
`runtime.WithErrorHandler` option. This will configure all unary error
responses to pass through this error handler.

See https://mycodesmells.com/post/grpc-gateway-error-handler for an example
of writing a custom error handler function.
of writing a custom error handler function. Note that this post targets
the v1 release of the gateway, and you no longer assign to `HTTPError` to
configure an error handler.

## Stream Error Handler
The error handler described in the previous section applies only
to RPC methods that have a unary response.

When the method has a streaming response, grpc-gateway handles
that by emitting a newline-separated stream of "chunks". Each
chunk is an envelope that can container either a response message
or an error. Only the last chunk will include an error, and only
when the RPC handler ends abnormally (i.e. with an error code).

Because of the way the errors are included in the response body,
the other error handler signature is insufficient. So for server
streams, you must install a _different_ error handler:

```go
mux := runtime.NewServeMux(
runtime.WithStreamErrorHandler(handleStreamError))
```

The signature of the handler is much more rigid because we need
to know the structure of the error payload in order to properly
encode the "chunk" schema into a Swagger/OpenAPI spec.

So the function must return a `*runtime.StreamError`. The handler
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.
//
// It uses a default "502 Bad Gateway" HTTP code; only emits "safe"
// 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,
}
}
```

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. 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).
to RPC methods that have a unary response. It is currently
not possible to configure the stream error handler.

## 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.
Expand Down
3 changes: 0 additions & 3 deletions examples/internal/integration/BUILD.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ go_test(
"fieldmask_test.go",
"integration_test.go",
"main_test.go",
"proto_error_test.go",
],
deps = [
"//examples/internal/clients/abe:go_default_library",
Expand All @@ -23,9 +22,7 @@ go_test(
"@com_github_golang_protobuf//descriptor:go_default_library_gen",
"@com_github_golang_protobuf//jsonpb:go_default_library_gen",
"@com_github_golang_protobuf//proto:go_default_library",
"@com_github_golang_protobuf//ptypes:go_default_library_gen",
"@com_github_google_go_cmp//cmp:go_default_library",
"@go_googleapis//google/rpc:errdetails_go_proto",
"@go_googleapis//google/rpc:status_go_proto",
"@io_bazel_rules_go//proto/wkt:empty_go_proto",
"@io_bazel_rules_go//proto/wkt:field_mask_go_proto",
Expand Down
6 changes: 3 additions & 3 deletions examples/internal/integration/integration_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1345,13 +1345,13 @@ func TestUnknownPath(t *testing.T) {
return
}

if got, want := resp.StatusCode, http.StatusNotFound; got != want {
if got, want := resp.StatusCode, http.StatusNotImplemented; got != want {
t.Errorf("resp.StatusCode = %d; want %d", got, want)
t.Logf("%s", buf)
}
}

func TestMethodNotAllowed(t *testing.T) {
func TestIncorrectMethod(t *testing.T) {
if testing.Short() {
t.Skip()
return
Expand All @@ -1370,7 +1370,7 @@ func TestMethodNotAllowed(t *testing.T) {
return
}

if got, want := resp.StatusCode, http.StatusMethodNotAllowed; got != want {
if got, want := resp.StatusCode, http.StatusNotImplemented; got != want {
t.Errorf("resp.StatusCode = %d; want %d", got, want)
t.Logf("%s", buf)
}
Expand Down
Loading

0 comments on commit 245b80d

Please sign in to comment.