Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

docs: add example to fully override http responses #4564

Merged
merged 4 commits into from
Jul 30, 2024
Merged
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 81 additions & 1 deletion docs/docs/mapping/customizing_your_gateway.md
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,85 @@ service Greeter {
}
```

### Fully Overriding Custom HTTP Responses

To fully override custom HTTP responses, you can use both a Forward Response Option and a Custom Marshaller.
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: I think the spelling is Mashaler in the Go ecosystem

Suggested change
To fully override custom HTTP responses, you can use both a Forward Response Option and a Custom Marshaller.
To fully override custom HTTP responses, you can use both a Forward Response Option and a Custom Marshaler.


For example with proto response message as:

```proto
message CreateUserResponse {
string name = 1;
}
```

The desired HTTP response:

```json5
HTTP 200 OK
Content-Type: application/json

{"name":"John Doe"}
```

But you want to return a `201 Created` status code along with a custom response structure:

```json5
HTTP 201 Created
Content-Type: application/json

{"success":true,"data":{"name":"John Doe"}}
```

First, set up the gRPC-Gateway with the custom options:

```go
mux := runtime.NewServeMux(
runtime.WithMarshalerOption(runtime.MIMEWildcard, &ResponseWrapper{}),
runtime.WithForwardResponseOption(forwardResponse),
)
```

Define the `forwardResponse` function to handle specific response types:

```go
func forwardResponse(ctx context.Context, w http.ResponseWriter, m protoreflect.ProtoMessage) error {
switch v := m.(type) {
case *pb.CreateUserResponse:
w.WriteHeader(http.StatusCreated)
}
// keep default behavior
return nil
}
```

Create a custom marshaller to format the response data which utilizes the builtin JSON marshaller:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
Create a custom marshaller to format the response data which utilizes the builtin JSON marshaller:
Create a custom marshaler to format the response data which utilizes the builtin JSON marshaler as a fallback:


```go
type ResponseWrapper struct {
runtime.JSONBuiltin
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should embed runtime.JSONPb to make sure it supports protobuf messages in general (the behavior will differ around things like enums, oneofs, etc). See https://pkg.go.dev/github.com/grpc-ecosystem/grpc-gateway/v2/runtime#JSONPb.

}

func (c *ResponseWrapper) Marshal(data any) ([]byte, error) {
resp := data
switch v := data.(type) {
case *pb.CreateUserResponse:
// wrap the response in a custom structure
resp = map[string]any{
"success": true,
"data": data,
}
}
// otherwise, use the default JSON marshaller
return json.Marshal(resp)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you may want to use c.JSONPb.Marshal? It's important to use a protobuf-aware JSON marshaler with protobuf types.

Suggested change
return json.Marshal(resp)
return c.JSONPb.Marshal(resp)

}
```

In this setup:

- The `forwardResponse` function intercepts the response and formats it as needed.
- The `CustomPB` marshaller ensures that specific types of responses are wrapped in a custom structure before being sent to the client.

## Error handler

To override error handling for a `*runtime.ServeMux`, use the
Expand Down Expand Up @@ -385,7 +464,7 @@ This method is not used outside of the initial routing.

### Customizing Routing Errors

If you want to retain HTTP `405 Method Not Allowed` instead of allowing it to be converted to the equivalent of the gRPC `12 UNIMPLEMENTED`, which is HTTP `501 Not Implmented` you can use the following example:
If you want to retain HTTP `405 Method Not Allowed` instead of allowing it to be converted to the equivalent of the gRPC `12 UNIMPLEMENTED`, which is HTTP `501 Not Implmented` you can use the following example:

```go
func handleRoutingError(ctx context.Context, mux *runtime.ServeMux, marshaler runtime.Marshaler, w http.ResponseWriter, r *http.Request, httpStatus int) {
Expand All @@ -405,6 +484,7 @@ func handleRoutingError(ctx context.Context, mux *runtime.ServeMux, marshaler ru
```

To use this routing error handler, construct the mux as follows:

```go
mux := runtime.NewServeMux(
runtime.WithRoutingErrorHandler(handleRoutingError),
Expand Down