Graphql: header access

Created on 10 Aug 2018  路  14Comments  路  Source: graphql-go/graphql

shall we add the header access from the request and the response, the change will pass along the call stack to the resolver function, but it will be useful

Most helpful comment

You can provide a RootObjectFn to the handler.Config struct of github.com/graphql-go/handler package. Its function signature is type RootObjectFn func(ctx context.Context, r *http.Request) map[string]interface{}. The value you return from that function is equal to the value of p.Info.RootValue where p is a graphql.ResolveParams object.

Reference pull request: https://github.com/graphql-go/handler/pull/42

Example:

// setup the graphql handler
graphqlHandler := handler.New(&handler.Config{
    Schema: &schema,
    Pretty:     true,
    GraphiQL:   false,
    Playground: true,
    RootObjectFn: func(ctx context.Context, r *http.Request) map[string]interface{} {
        return map[string]interface{}{
            "hello": "world",
        }
    },
})
// setup the root query
rootQuery := graphql.ObjectConfig{
    Name: "Query",
    Fields: graphql.Fields{
        "hello": &graphql.Field{
            Type: graphql.String,
            Resolve: func(p graphql.ResolveParams) (interface{}, error) {
                fmt.Println(p.Info.RootValue) // prints: map[hello:world]
                return "woot!", nil
            },
        },
    },
}

All 14 comments

Is it possible currently to access headers?

Is it possible currently to access headers?

Hi @kamushadenes, thanks for using the lib and reaching us for a question, you can access request headers as you would normally do via the net/http.Header.Get:

(graphql)-> go doc http.Header.Get
func (h Header) Get(key string) string
    Get gets the first value associated with the given key. It is case
    insensitive; textproto.CanonicalMIMEHeaderKey is used to canonicalize the
    provided key. If there are no values associated with the key, Get returns
    "". To access multiple values of a key, or to use non-canonical keys, access
    the map directly.

Hello @chris-ramon, thank you for your response.

It's not clear to me how to access the http request though the Resolve method (p ResolveParams). Can you please give me an example?

An alternative I thought of is using a middleware that takes the relevant headers and passes them by using the context.

If both ways (direct acessing the http request X using a middleware) are possible, which do you think is more elegant?

@chris-ramon can you also point me to how I can access the original request body?

I need to make a passthrough to another system after collecting some metrics, so I need the original body to replay the query.

@chris-ramon I ended up coding a middleware that fetches the relevant headers and request body and puts them into the context (as seem in #269).

I also made a custom writer that delays WriteHeader() calls so that I can add some late headers that are only available after everything is processed by the handler.

I would love a better way but I couldn't come with one.

func StargateMiddleware(next http.Handler) http.Handler {
    ctx := context.WithValue(context.Background(), "requestUUID", utils.GetUUID().String())

    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {

        b, _ := ioutil.ReadAll(r.Body)
        r.Body = ioutil.NopCloser(bytes.NewReader(b))

        ctx = context.WithValue(ctx, "originalRequest", string(b))

        ip := r.Header.Get("X-Forwarded-For")
        if ip == "" {
            ip = strings.Split(r.RemoteAddr, ":")[0]
        }

        ctx = context.WithValue(ctx, "remoteAddress", ip)

        ctx = context.WithValue(ctx, "partnerUUID", "TODO")

        writer := qhttp.NewRewritableResponseWriter(w)

        next.ServeHTTP(writer, r.WithContext(ctx))

        writer.Header()["RequestUUID"] = []string{ctx.Value("requestUUID").(string)}

        writer.Commit()

    })
}

The other way is to create a dictionary of headers then pass it to context using context.WithValue(context.Background(),...

I also have the same doubts. I need to get the request header in the Resolve method. Is there a way?

Of course. Check what I said, you need to pass the parameters using context.withValue

Where do you write this Middleware? Where is _context_ variable available to middleware function?

I am using graphql-go within Gin-Gonic.

On _graphql.go_ code:

...
import (
    "github.com/gin-gonic/gin"
    "github.com/graphql-go/graphql"
    "github.com/graphql-go/handler"
)
...
// Handler initializes the prometheus middleware.
func GraphQLHandler() gin.HandlerFunc {
    // Creates a GraphQL-go HTTP handler with the defined schema
    h := handler.New(&handler.Config{
        Schema: &schema,
        Pretty: true,
        GraphiQL: false,
        Playground: false,
    })

    return func(c *gin.Context) {
        h.ServeHTTP(c.Writer, c.Request)
    }
}

On _main.go_ :

...
func main() {
    router = gin.Default()
    router.Use(...)
    ...
    graphql := router.Group("/graphql")
    graphql.POST("", handlers.GraphQLHandler())
}

So where should I write the middleware in this case?

@harshithjv what i ended up doing is the following

Create a middleware function to extract the headers:

func httpHeaderMiddleware(next *handler.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        ctx := context.WithValue(r.Context(), "header", r.Header)

        next.ContextHandler(ctx, w, r)
    })
}

wrap my handler in this function:

func GQLHandler(schema graphql.Schema) *handler.Handler {
    handler := handler.New(
        &handler.Config{
            Schema:     &schema,
            Pretty:     true,
            GraphiQL:   false,
            Playground: true,
        })

    return  httpHeaderMiddleware(handler)
}

this is what I ended up doing:

func graphqlMiddleware() gin.HandlerFunc {
    return func(c *gin.Context) {
        // before request
        // set auth
        c.Request = c.Request.WithContext(context.WithValue(c.Request.Context(), tokenKey, getAuthToken(c.Request)))
        c.Next()
        // after request
    }
}

then:

accountdata, err := validateLoggedIn(params.Context.Value(tokenKey).(string))
if err != nil {
    return nil, err
}

You can provide a RootObjectFn to the handler.Config struct of github.com/graphql-go/handler package. Its function signature is type RootObjectFn func(ctx context.Context, r *http.Request) map[string]interface{}. The value you return from that function is equal to the value of p.Info.RootValue where p is a graphql.ResolveParams object.

Reference pull request: https://github.com/graphql-go/handler/pull/42

Example:

// setup the graphql handler
graphqlHandler := handler.New(&handler.Config{
    Schema: &schema,
    Pretty:     true,
    GraphiQL:   false,
    Playground: true,
    RootObjectFn: func(ctx context.Context, r *http.Request) map[string]interface{} {
        return map[string]interface{}{
            "hello": "world",
        }
    },
})
// setup the root query
rootQuery := graphql.ObjectConfig{
    Name: "Query",
    Fields: graphql.Fields{
        "hello": &graphql.Field{
            Type: graphql.String,
            Resolve: func(p graphql.ResolveParams) (interface{}, error) {
                fmt.Println(p.Info.RootValue) // prints: map[hello:world]
                return "woot!", nil
            },
        },
    },
}

@david-castaneda @jschmidtnj @bmdelacruz

Thanks all for the suggestion and your time guys. I hadn't worked for much time on Go-lang or GraphQL and was working on a short time POC. But it was abruptly ended for another project requirement and therefore I hadn't seen your comments. So right now I can't test these suggestions. However thanks for your efforts and hope it help others who read this thread. :)

Was this page helpful?
0 / 5 - 0 ratings

Related issues

rustysys-dev picture rustysys-dev  路  6Comments

SDkie picture SDkie  路  6Comments

titanous picture titanous  路  3Comments

Fruchtgummi picture Fruchtgummi  路  4Comments

januszm picture januszm  路  6Comments