In my golang project I am using Fiber (https://gofiber.io/) as a router and somewhere in code I am setting some values in fibers context. Now I am adding graphql (https://github.com/99designs/gqlgen) endpoints and in resolvers context I need to access values set in fibers context. In router I added the following:
import (
"github.com/gofiber/adaptor/v2"
"github.com/gofiber/fiber/v2"
gql_handler "github.com/99designs/gqlgen/graphql/handler"
)
server := gql_handler.NewDefaultServer(generated.NewExecutableSchema(
generated.Config{
Resolvers: h.createResolvers(),
Directives: h.createDirectives(),
Complexity: h.createComplexity(),
},
))
route := r.Group("/graph")
route.Get("/playground", adaptor.HTTPHandlerFunc(playground.Handler("Graphql Playground", "/graph")))
route.All("", adaptor.HTTPHandler(server))
so somewhere in my code I do:
func (h *Handler) MyFunc(c *fiber.Ctx) error {
//setting value in fibers context
c.Locals("MyTestValue", "some_value")
return nil
}
and in my graphql resolver I want to access it:
func (r *queryResolver) MyQuery(ctx context.Context) (bool, error) {
fmt.Println(ctx.Value("MyTestValue"))//returns nil
return true, nil
}
I tried to create a middleware to pass fiber context into regular context (as it is made for gin router, for example) but it did not work:
func FiberContextToContext() fiber.Handler {
return func(c *fiber.Ctx) error {
ctx := context.WithValue(c.Context(), "FiberContextKey", c)
//c.Context() = c.WithContext(ctx)//errors here
return c.Next()
}
}
Any ideas how to fix it would be welcome. Thank you.
First note: if you want to change the value behind a pointer, you’ld need to apply a construction like:
requestCtx := c.Context()
*requestCtx = myNewConxt
Second note: this won’t work because in you case ctx
is not of type fasthttp.RequestCtx
(fasthttp.RequestCtx
implements context.Context
, not the other way around.)
So, in general you’re right, with fiber
you set Locals
, which basically just sets a fasthttp.UserValue
. And there’s where the problems begin. UserValues
get “lost” after the conversion with adaptor
. There’s an open issue for that, but it’s not an easy one to solve.
Effectively, this is where your precious local variables end up:
So they are hidden in an object behind the key "__local_user_context__"
.
My workaround is this:
// FindValue enables support for values residing either directly in the given context or in the wrapped fiber UserValues hidden by the fiber Adaptor conversion.
func FindValue(ctx context.Context, key any) (value any) {
if value = ctx.Value(key); value == nil {
value = getUserCtxFromFiberRequestCtx(ctx).Value(key)
}
return value
}
// getUserCtxFromFiberRequestCtx allows to access util-server specific context values for contexts which come from the fiber Adaptor.
// This method returns either the hidden user context in case it's a proper Adaptor, context or otherwise just the incoming adaptor.
func getUserCtxFromFiberRequestCtx(ctx context.Context) (extracted context.Context) {
if localUserCtx := ctx.Value("__local_user_context__"); localUserCtx != nil {
if localUsrCtxCast, ok := localUserCtx.(context.Context); ok {
return localUsrCtxCast
}
}
return ctx
}
It’s not great and might break when this key is rename – or even worse, becomes a private type
instead of a string
. Maybe other users can come up with better ideas.
In general, it’s to just rely on fiber
/fasthttp
handlers. But sadly for gqlgen
that’s not possible – my screenshot stemps from exactly the same scenario. Still you should keep the following considerations in mind:
- Does the middleware you want to apply to modify the “context” has to be
fiber
middleware? Otherwise, you could just set regularhttp
middleware and move it into theadaptor
. - Is it worth to go with
fiber
here at all? With the usage of theadaptor
, I expect that most of the gain in performance to be lost, b/cfiber
(resp. openfaas) is based on reusing objects, and this gets dropped after the conversion.