-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add WithForwardResponseRewriter to allow easier/more useful res…
…ponse control (#4622) ## Context/Background I am working with grpc-gateway to mimick an older REST API while implementing the core as a gRPC server. This REST API, by convention, emits a response envelope. After extensively researching grpc-gateway's code and options, I think that there is not a good enough way to address my use-case; for a few more nuanced reasons. The sum of my requirements are as follows: - 1) Allow a particular field of a response message to be used as the main response. ✅ This is handled with `response_body` annotation. - 2) Be able to run interface checks on the response to extract useful information, like `next_page_token` [0] and surface it in the final response envelope. (`npt, ok := resp.(interface { GetNextPageToken() string })`). - 3) Take the true result and place it into a response envelope along with other parts of the response by convention and let that be encoded and sent as the response instead. ### Implementing a response envelope with `Marshaler` My first attempt at getting my gRPC server's responses in an envelope led me to implement my own Marshaler, I have seen this approach discussed in #4483. This does satisfy requirements 1 and 3 just fine, since the HTTP annotations helpfully allow the code to only receive the true result, and the Marshal interface has enough capabilities to take that and wrap it in a response envelope. However, requirements 1 and 2 are not _both_ satisfiable with the current grpc-gateway code because of how the `XXX_ResponseBody()` is called _before_ passing to the `Marshal(v)` function. This strips out the other fields that I would normally be able to detect and place in the response envelope. I even tried creating my _own_ protobuf message extension that would let me define another way of defining the "true result" field. But the options for implementing that are either a _ton_ of protoreflect at runtime to detect and extract that, or I am writing another protobuf generator plugin (which I have done before [1]), but both of those options seem quite complex. ### Other non-starter options Just to get ahead of the discussion, `WithForwardResponseOption` clearly was not meant for this use-case. At best, it seems to only be a way to take information that might be in the response and add it as a header. [0]: https://google.aip.dev/158#:~:text=Response%20messages%20for%20collections%20should%20define%20a%20string%20next_page_token%20field [1]: https://github.com/nkcmr/protoc-gen-twirp_js ### In practice This change fulfills my requirements by allowing logic to be inserted right before the Marshal is called: ```go gatewayMux := runtime.NewServeMux( runtime.WithForwardResponseRewriter(func(ctx context.Context, response proto.Message) (interface{}, error) { if s, ok := response.(*statuspb.Status); ok { return rewriteStatusToErrorEnvelope(ctx, s) } return rewriteResultToEnvelope(ctx, response) }), ) ``` ## In this PR This PR introduces a new `ServeMuxOption` called `WithForwardResponseRewriter` that allows for a user-provided function to be supplied that can take a response `proto.Message` and return `any` during unary response forwarding, stream response forwarding, and error response forwarding. The code generation was also updated to make the `XXX_ResponseBody()` response wrappers embed the concrete type instead of just `proto.Message`. This allows any code in response rewriter functions to be able to have access to the original type, so that interface checks against it should pass as if it was the original message. Updated the "Customizing Your Gateway" documentation to use `WithForwardResponseRewriter` in the `Fully Overriding Custom HTTP Responses` sections. ## Testing Added some basic unit tests to ensure Unary/Stream and error handlers invoke `ForwardResponseRewriter` correctly.
- Loading branch information
Showing
8 changed files
with
156 additions
and
74 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
32 changes: 14 additions & 18 deletions
32
examples/internal/proto/examplepb/response_body_service.pb.gw.go
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.