gogrpcinterceptorgolandgrpc-go

how to pass metadata from gRPC function to interceptor


I'm a bit lost; help me figure out this. Im trying to add some extra data (like User ID) to a context that I will use later in the log interceptor.

here is how my gRPC call looks like

import (
    "context"
    "yourpbpackage/pb"
)

func (s *YourGRPCServer) SomeGRPCFunction(ctx context.Context, req *pb.YourRequest) (*pb.YourResponse, error) {

    // Add user ID to the context
    userID := "12345" // This would be dynamically determined, for example from the request
    ctx = context.WithValue(ctx, userIDKey, userID) // HERE IM ADDING METADATA

    // Process and return the response
    return &pb.YourResponse{}, nil
}

here in interceptor Im trying to catch user ID

func GRPCLogger(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp any, err error) {
    
// Invoke the handler with the modified context
    resp, err = handler(ctx, req)

    // Now retrieve user ID from the context after the handler has modified it
    if userID, ok := ctx.Value(userIDKey).(string); ok {
        log.Info().Str("userID", userID).Msg("User ID from handler context in interceptor")
    }

    return resp, err
}

here is code how I use interceptor

func runGrpcServer(conf util.Config, store db.Store, taskDistributor worker.TaskDistributor, authClient auth.AuthClient, logClient logsrv.LogClient) {
    server, err := gapi.NewServer(conf, store, taskDistributor, authClient)
    if err != nil {
        log.Fatal().Err(err).Msg("cannot create server")
    }

    grpLogger := grpc.UnaryInterceptor(gapi.GRPCLogger)
    grpcServer := grpc.NewServer(grpLogger)

    pb.RegisterStoneServer(grpcServer, server)
    reflection.Register(grpcServer)

    listener, err := net.Listen("tcp", conf.GRPCServerAddress)
    if err != nil {
        log.Fatal().Err(err).Msg("cannot create listener")
    }

    log.Info().Msgf("start gRPC server at %s", listener.Addr().String())
    err = grpcServer.Serve(listener)
    if err != nil {
        log.Fatal().Err(err).Msg("cannot start gRPC server:")
    }
}

Any suggestions what it could be ? where I'm wrong ?

I tried to modify context with values context.WithValue(ctx, userIDKey, userID) or set headers. If I set the header i can see it ctx.val (*transport.Stream) but I can't extract it from there


Solution

  • When you add a value to a context, you receive a new context containing that value and wrapping the old context. The old context does not know anything about that value.

    One way to achieve what you are trying to do is to prepare the context in the interceptor so that the handler can modify it:

    type HandlerData struct {
       UserID string
       ...
    }
    
    func GRPCLogger(ctx context.Context, req any, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp any, err error) {
      // Prepare the context with a writable struct
      handlerData:=&HandlerData{}
      newCtx:=context.WithValue(ctx,key, handlerData)
    
        
      // Invoke the handler with the modified context
      resp, err = handler(newCtx, req)
    
      // Now retrieve user ID from
      if handlerData.UserID!="" {
        // Work with it
      }
    
      return resp, err
    }
    

    In your grpc handler:

    func (s *YourGRPCServer) SomeGRPCFunction(ctx context.Context, req *pb.YourRequest) (*pb.YourResponse, error) {
       handlerData,_:=ctx.Value(key).(*HandlerData)
       if handlerData!=nil {
           // Add user ID to the context
           handlerData.UserID := "12345" METADATA
        }
        // Process and return the response
        return &pb.YourResponse{}, nil
    }