8

I use grpc-gateway to host an HTTP server out of my proto definitions. It works great overall.

However, for one special endpoint, instead of returning a value back, I want to do a 302 redirect to an image hosted in an s3.

If you want to return an error via grpc-gateway, you can return it like

nil, status.Error(codes.Unauthenticated, "Nope")

I wonder if there is something similar to do a 302 redirect?

As far as I get from this page it seems unlikely. I hope I overlooked something.

Umut Benzer
  • 3,476
  • 4
  • 36
  • 53

3 Answers3

7

you could also use WithForwardResponseOption method which allows you to modify your response and response headers.

here is what I did to set Location header in response.

  1. Set Location header in your GRPC method using metadata. this adds Grpc-Metadata-Location header to your response.
func (s *Server) CreatePayment(ctx context.Context, in *proto.Request) (*proto.Response, error) {
    header := metadata.Pairs("Location", url)
    grpc.SendHeader(ctx, header)
    
    return &proto.Response{}, nil
}
  1. If Grpc-Metadata-Location header exists in your GRPC response headers, Set HTTP Location header and status code as well.
func responseHeaderMatcher(ctx context.Context, w http.ResponseWriter, resp proto.Message) error {
    headers := w.Header()
    if location, ok := headers["Grpc-Metadata-Location"]; ok {
        w.Header().Set("Location", location[0])
        w.WriteHeader(http.StatusFound)
    }

    return nil
}
  1. Set this func as an Option in NewServeMux:
grpcGatewayMux := runtime.NewServeMux(
    runtime.WithForwardResponseOption(responseHeaderMatcher),
)
rezam
  • 627
  • 1
  • 12
  • 17
  • Your link is dead. I believe this is the new URL: https://grpc-ecosystem.github.io/grpc-gateway/docs/mapping/customizing_your_gateway/ – crunk1 Jun 03 '21 at 20:00
3

There's no straightforward way. But there's a workaround.

There's no conception in gRPC similar to 302. So simple error code mappings won't work fine. But you can overwrite a response forwarder per method so that it extracts redirectURL from the response and sets the HTTP status code and Location header.

Documentation link: https://grpc-ecosystem.github.io/grpc-gateway/docs/mapping/customizing_your_gateway/#mutate-response-messages-or-set-response-headers

ignite
  • 381
  • 5
  • 20
  • 1
    I am running into the same use-case but how can one extract `redirectURL` from the message? If I do `resp.redirectURL` the compiler tells me that proto.Message does not implement that. Do I have to do some casting first, or similar? – Markus Jul 06 '18 at 08:02
  • @Markus any solution to this? – mayankcpdixit Feb 07 '19 at 11:30
1

Looking at the code you mentioned, it appears to be simply mapping the grpc status codes directly to their closest http equivalents. It doesn't appear that there are any codes in the spec that really map to an http redirect. Am I correct in assuming you are using the gateway to connect a browser to a grpc service?

My suggestion would be to work redirects into the protocol somehow. What if the response to certain methods was something like:

message HelloResponse {
  string reply = 1;
  bool shouldRedirect = 2;
  string redirectURL = 3;
}

Then if the receiver could detect that from the response and redirect client-side. A little less magical, but still lets you do the redirect if needed.

captncraig
  • 22,118
  • 17
  • 108
  • 151
  • Hi, that's one option, however, my intention is to use this endpoint directly as a part of `` without processing on the frontend. – Umut Benzer Apr 18 '18 at 07:25
  • Then you are probably more limited. All I can think of is modifying the gateway with the feature you want. – captncraig Apr 18 '18 at 14:01
  • @captncraig can this be not done on grpc server side before forwarding response? – mayankcpdixit Feb 07 '19 at 05:30
  • @UmutBenzer What did you end up doing? – mayankcpdixit Feb 07 '19 at 05:31
  • I ended up implementing our logic on the client side. In one request I get the URL for the image and in the second request, we get the image itself by using the response of the first request. This was implemented before I learned that such a thing `WithOutgoingHeaderMatcher` existed. I don't know if that would've been worked for me or not. – Umut Benzer Feb 07 '19 at 11:22