http header forwarded to grpc context
The grpc gateway can forward the request body content to the grpc corresponding message. So how to obtain the information in the http header. This article will introduce how to forward the http header to the grpc context and use an interceptor to obtain the content in the http header. Some built-in fields in the http header will be forwarded, such as Authorization, but many custom fields cannot be forwarded.
This article implements the forwarding of custom fields in the http header to the grpc context and uses interceptors for simple authentication.
For the code, please refer to the previous grpc-gateway blogs.
Getting started with grpc-gateway, environment + simple case
grpc-gateway proto defines http routing
grpc-gateway defines http routing
Gateway code modification
If you want to forward custom content in the http header, the generated gateway code needs to be modified and some gateway server options added.
- runtime.WithIncomingHeaderMatcher: Request http header settings to forward to the grpc context
- runtime.WithOutgoingHeaderMatcher: After the response, the grpc context is forwarded to the http header
gateway.go
package gateway import ( "context" "flag" "fmt" "net/http" "github.com/grpc-ecosystem/grpc-gateway/v2/runtime" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" _ "google.golang.org/grpc/grpclog" gw "user/proto" // Update ) var ( // command-line options: // gRPC server endpoint grpcServerEndpoint = flag.String("grpc-server-endpoint", "localhost:50051", "gRPC server endpoint") ) func Run() error {<!-- --> ctx := context.Background() ctx, cancel := context.WithCancel(ctx) defer cancel() // When requesting, forward certain fields in the http header to the grpc context inComingOpt := runtime.WithIncomingHeaderMatcher(func(s string) (string, bool) {<!-- --> fmt.Println("header:" + s) switch s {<!-- --> case "Service-Authorization": fmt.Println("Service-Authorization hit") return "Service-Authorization", true default: return "", false } }) // After the response, the grpc context is forwarded to the http header outGoingOpt := runtime.WithOutgoingHeaderMatcher(func(s string) (string, bool) {<!-- --> return "", false }) // Register gRPC server endpoint // Note: Make sure the gRPC server is running properly and accessible mux := runtime.NewServeMux(inComingOpt, outGoingOpt) //Add file upload processing function mux.HandlePath("POST", "/upload", uploadHandler) opts := []grpc.DialOption{<!-- -->grpc.WithTransportCredentials(insecure.NewCredentials())} err := gw.RegisterUserHandlerFromEndpoint(ctx, mux, *grpcServerEndpoint, opts) if err != nil {<!-- --> return err } // Start HTTP server (and proxy calls to gRPC server endpoint) return http.ListenAndServe(":8081", mux) }
The file upload interface is modified because this is a custom gateway routing interface and you need to forward the fields in the Header to grpc yourself.
upload.go
package gateway import ( "context" "fmt" "github.com/golang/protobuf/jsonpb" "google.golang.org/grpc" "google.golang.org/grpc/credentials/insecure" "google.golang.org/grpc/metadata" "io" "net/http" "user/proto" ) func uploadHandler(w http.ResponseWriter, r *http.Request, pathParams map[string]string) {<!-- --> // Parse the file from request first err := r.ParseForm() if err != nil {<!-- --> http.Error(w, fmt.Sprintf("Upload failed: %s", err.Error()), http.StatusInternalServerError) } f, header, err :=r.FormFile("attachment") if err != nil {<!-- --> http.Error(w, fmt.Sprintf("Upload failed: %s", err.Error()), http.StatusInternalServerError) } defer f.Close() //Access grpc server side, connection pool for actual production conn, err := grpc.Dial(*grpcServerEndpoint, grpc.WithTransportCredentials(insecure.NewCredentials())) if err != nil {<!-- --> http.Error(w, fmt.Sprintf("Upload failed: %s", err.Error()), http.StatusInternalServerError) } defer conn.Close() c := proto.NewUserClient(conn) ctx := context.Background() ctx = metadata.NewOutgoingContext(ctx, metadata.New(map[string]string{<!-- --> "file_name":header.Filename, "service-authorization":r.Header.Get("Service-Authorization"), })) stream, err := c.Upload(ctx) if err != nil {<!-- --> http.Error(w, fmt.Sprintf("Upload failed: %s", err.Error()), http.StatusInternalServerError) } // Read the file stream and forward it to grpc buf := make([]byte, 512) for {<!-- --> n, err := f.Read(buf) if err != nil & amp; & amp; err != io.EOF{<!-- --> http.Error(w, fmt.Sprintf("Upload failed: %s", err.Error()), http.StatusInternalServerError) } if n == 0 {<!-- --> break } stream.Send( & amp;proto.UploadRequest{<!-- --> Content: buf[:n], Size: int64(n), }) } res, err := stream.CloseAndRecv() if err != nil {<!-- --> http.Error(w, fmt.Sprintf("Upload failed: %s", err.Error()), http.StatusInternalServerError) } m := jsonpb.Marshaler{<!-- -->} str, _ := m.MarshalToString(res) if err != nil {<!-- --> http.Error(w, fmt.Sprintf("Upload failed: %s", err.Error()), http.StatusInternalServerError) } w.Header().Add("Content-Type", "application/json") fmt.Fprintf(w, str) }
grpc service code modification
Interceptor, just get metadata from the context for business operations
interceptor.go
package server import ( "context" "errors" "fmt" "google.golang.org/grpc" "google.golang.org/grpc/metadata" "strings" ) func UnaryInterceptor(ctx context.Context, req interface{<!-- -->}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{<!-- -->}, err error) {<! -- --> err = auth(ctx) if err != nil {<!-- --> return nil, err } return handler(ctx, req) } func StreamInterceptor(srv interface{<!-- -->}, ss grpc.ServerStream, info *grpc.StreamServerInfo, handler grpc.StreamHandler) error {<!-- --> err := auth(ss.Context()) if err != nil {<!-- --> return err } return handler(srv, ss) } func auth(ctx context.Context) error {<!-- --> md, ok := metadata.FromIncomingContext(ctx) fmt.Println("meta:", md) //In actual application, the return prompt needs to be blurred, and detailed errors can be printed in the log. if !ok {<!-- --> return errors.New("Failed to obtain metadata, identity verification failed") } // All forwarded messages are in lowercase authorization := md["service-authorization"] if len(authorization) < 1 {<!-- --> return errors.New("Failed to obtain identity token, identity verification failed") } token := strings.TrimPrefix(authorization[0], "Bearer ") if token != bearerToken {<!-- --> return errors.New("Identity token comparison failed, identity verification failed") } return nil } // for testing var bearerToken = "sdfdlsdhgeiasdxzasqqqy2ybfhhu2gyvb"
And register the interceptor into the grpc service
s := grpc.NewServer(grpc.UnaryInterceptor(server.UnaryInterceptor), grpc.StreamInterceptor(server.StreamInterceptor))
The key point is to modify the gateway code and add the logic of forwarding headers.