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

How to retrieve data from a gRPC handler in HTTP middleware #3775

Open
LazarenkoA opened this issue Nov 30, 2023 · 2 comments
Open

How to retrieve data from a gRPC handler in HTTP middleware #3775

LazarenkoA opened this issue Nov 30, 2023 · 2 comments

Comments

@LazarenkoA
Copy link

LazarenkoA commented Nov 30, 2023

I'm facing a challenge where I need to extract specific data from a gRPC handler into my HTTP middleware. Despite thoroughly reviewing the documentation, I couldn't find a suitable answer. In my current implementation, I've utilized WithForwardResponseOption in runtime.NewServeMux to inject values into the HTTP response headers and subsequently retrieve them in the middleware. However, I find this solution somewhat inelegant, are there any other ways?

s := &http.Server{
   Addr:    addr,
   Handler: middleware.Metrics(mux),
}

s.ListenAndServe()
func Metrics(next http.Handler) http.Handler {
   return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
      timeStart := time.Now()
      next.ServeHTTP(w, r)

      // here it is supposed to record metrics and for them you need to get certain values from the grpc handler

   })

}

here's how I tried to solve this problem:

mux := runtime.NewServeMux(
   runtime.WithForwardResponseOption(func(ctx context.Context, writer http.ResponseWriter, message proto.Message) error {
      md, ok := runtime.ServerMetadataFromContext(ctx)
      if !ok {
         return nil
      }

      writer.Header().Add("action", firstOrDefault(md.HeaderMD.Get("action")))
      writer.Header().Add("providerName", firstOrDefault(md.HeaderMD.Get("providerName")))
      return nil
   }),

)
func Metrics(next http.Handler) http.Handler {
   return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
      timeStart := time.Now()
      next.ServeHTTP(w, r)

      action := popParam("action", w.Header())  // getting data from the header
      // ...... 
   })

}

func popParam(key string, header http.Header) string {
   result := header.Get(key)
   header.Del(key)
   return result
}

on the grpc handler side

grpc.SendHeader(ctx, metadata.New(map[string]string{
   "action":  "value",
   "...":  "...",
}))
@insan1k
Copy link

insan1k commented Dec 1, 2023

I'm trying to achieve something similar, I think it would be beneficial to have something like a functional interface for wrapping mux.Handle and mux.HandlePath, alternatively it would be also possible for the generated code to contain some capability of wrapping the call to mux.Handle there.

I'm trying to add support in go-http-metrics for the runtime mux, and although I can do this easily for custom methods, there are no viable alternatives of conciliating these two libraries. In my case if I went for a solution close to what @LazarenkoA has done here, it would be senseless to use go-http-metrics.

@johanbrandhorst
Copy link
Collaborator

What you're suggesting is the only way I know of to pass extra information from gRPC handlers into HTTP middleware. I don't think this is too rare of a use case (e.g., setting cookies from gRPC handlers) so I'd be interested in exploring the solution space, if you're interested in working on it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

3 participants