How can I pass the origin from gin gonic to a gRPC microservice

Issue

I’m writing a microservice that supports multiple origins (which then define what database to connect to). In order to do that I need for example the origin. The server with gin is responsible to validate the origin, the rpc microservice does not validate anything. The only point that I am missing is how to pass this metadata to the rpc service inside the context.

I simplified the idea as much as I could, the real case is way more complex, and there is more than 1 value to pass. Passing all those things to all functions is out of discussion, as it would generate way too much overhead

Gin server

func OriginMiddleware() gin.HandlerFunc {
    return func(context *gin.Context) {

        origin := context.GetHeader("origin")
        
        // add origin to the context

        context.Next()
    }
}

func main(){
    
    ...  // setup grpc etc

    
    r.Use(OriginMiddleware())

    r.POST("/login", User.Login)
    r.GET("/getUser", User.Get)

    ... // many other endpoints

    r.Run("localhost:8080")
}

Microservice


func OriginInterceptor() grpc.UnaryServerInterceptor {
    return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
        origin := ctx.Value("origin")
        fmt.Println(origin)
    
        ... // get or create db connection whatsoever
 
        return handler(ctx, req)
    }
}

func main() {
    ... // setup
    
    s := grpc.NewServer(grpc.UnaryInterceptor(MongoInterceptor()))
    
    UserPB.RegisterUserServiceServer(s, &server{})

    ... // serve
}

What I’ve been trying

  • Overwriting the gin context doesn’t seem possible
  • Using .Set works only as long as it’s inside the "gin" server, but all values are lost when sent to grpc

Solution

I found a solution that works, but it’s not the cleanest. Improvements and critics are well accepted!

func ToGrpcContext(ctx *gin.Context) context.Context {
    res := make(map[string]string)
    for key, value := range ctx.Keys {
        switch v := value.(type) {
        case string:
            res[key] = v
        }
    }
    return metadata.NewOutgoingContext(ctx, metadata.New(res))
}

Each time I call any grpc function, I wrap the context with this helper function, like:

user, err := UserPB.GetUser(ToGrpcContext(ctx), &UserPB.GetUserInput{...})

Answered By – Andrea

Answer Checked By – Dawn Plyler (GoLangFix Volunteer)

Leave a Reply

Your email address will not be published.