-
Notifications
You must be signed in to change notification settings - Fork 2.2k
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
[Feature Request] Provide a legit way to override HTTP Response. #4483
Comments
Hi, thanks for your well written issue. I'm not sure that this meets the threshold of utility for a new feature (though minor in cost). I usually like to hear from two separate users that are having the same issue before considering it. To rephrase your request (correct me if I'm wrong):
Now, as an alternative, have you tried just not returning an error from the response option? Since you've called |
Yup, you're absolutely right about my request. But it's not just me, our organization uses this approach as well. Additionally, two similar issues I mentioned earlier requested similar functionality but haven't reached a conclusion yet. Currently, the For example: message GetNameResponse {
string name = 1;
} Here's the relevant server code snippet: mux := runtime.NewServerMux(
runtime.WithHealthEndpointAt(grpc_health.NewHealthClient(conn), "/health"),
runtime.WithMarshalerOption(runtime.MIMEWildcard, &runtime.JSONBuiltin{}),
runtime.WithForwardResponseOption(forwardResponse),
)
func forwardResponse(ctx context.Context, w http.ResponseWriter, m protoreflect.ProtoMessage) error {
switch v := m.(type) {
case *pb.GetNameResponse:
resp := map[string]any{
"success": true,
"data": m,
}
return runtime.ErrResponseHandled // (1)
default:
return nil
}
} Issue with Returning Returning {"success":true,"data":{"name": "John Doe"}}{"name": "John Doe"} But if we return error at {"success":true,"data":{"name": "John Doe"}} |
Yep, I see the problem. I think the workflow of having ultimate control to override the HTTP response is something we do want to support. Let me propose some other options (not saying no to this feature yet, just exploring the problem space):
Let me know what you think! |
2.3. It’s okay to use these approaches, but my aim is to override HTTP responses. We want to maintain a structured response with gRPC calls. |
Do you think this is sufficient for now? If so, would you be willing to contribute an update to our docs that mentions this pattern for fully overriding responses? We already have a section for setting headers but it would be good to complement it with a custom marshaler example. Thanks! |
Yep, I agree, this is sufficient for now! I'll squeeze in some time to update the docs with that custom marshaler example. |
It took a while but I finally have time for this simple task. I've now added the section on fully overriding custom HTTP responses using both a Forward Response Option and a Custom Marshaller. Please review the changes #4564 and let me know if any further adjustments are needed. |
…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.
🚀 Feature Request
Issue with error logs for ForwardResponseOptions
PR #4245 changed how errors are logged, which is cool. But it also started logging a bunch of stuff we already aware.
My situation:
I need to override the response sometimes. To do that, I use
ForwardResponseOptions
with a custom function that might return an error. This tells grpc-gateway to chill and not write anything else. Works great, but then it fills the logs with these messages afterv2.20.0
:Tried a few things:
GRPC_GO_LOG_SEVERITY_LEVEL=none
would stop all logs, but I still need to see other errors.grpc.Logger
is also an option, but that's kinda overkill just to omit the simple log.Proposal:
Add a new variable like
ErrResponseHandled
. This would tellgrpc-gateway
to:Similar issues but have been closed:
Code examples (in case they help):
Here's what the change in
grpc-gateway/runtime/handler.go
would look like:And here's how I use it in my code:
Let me know what you think!
The text was updated successfully, but these errors were encountered: