graphql-go / graphql

An implementation of GraphQL for Go / Golang
MIT License
9.86k stars 838 forks source link

Setting header values (Cache) #606

Open mimol91 opened 3 years ago

mimol91 commented 3 years ago

I am trying to find a way to set header value (Cache control) How can it be done? I've noticed that even if I wrote own middleware, it's not possible(it does not have any effect) to set header after grapql invoke writeHeader in handler. I am passing my header values in context, but I cant not use it

Aravind1411 commented 1 year ago

Same issue. Any updates from your side?

mimol91 commented 1 year ago

@Aravind1411 The current solution that I am using - It allows to set header for specific query Please notice that it has some limitation as it is possible to join multiple queries in GQL.

{
  queryA{ //may have cache 2min
    bar
  }
  queryB {//may have cache 10min
    bar
  }
}
//schema.go
"fooQuery": headers(maxAge(600))(fooQuery)

var noStore = header.NoStore
var maxAge = header.MaxAge

func headers(headers ...http.Header) func(f *graphql.Field) *graphql.Field {
    headerCfg := http.Header{}
    for _, v := range headers {
        for k := range v {
            headerCfg.Set(k, v.Get(k))
        }
    }

    return func(f *graphql.Field) *graphql.Field {
        resolve := f.Resolve
        f.Resolve = func(p graphql.ResolveParams) (interface{}, error) {
            res, err := resolve(p)
            if err == nil {
                header.SetHeadersConfig(p.Context, headerCfg)
            }
            return res, err
        }
        return f
    }
}
//header package
var headersCfgKey = contextKey("headersCfg")

type contextKey string

func HeadersConfigFromCtx(ctx context.Context) http.Header {
    if c, ok := ctx.Value(headersCfgKey).(http.Header); ok {
        return c
    }
    return nil
}

func SetHeadersConfig(ctx context.Context, header http.Header) {
    cfg := HeadersConfigFromCtx(ctx)
    if cfg == nil {
        return
    }
    for k := range header {
        cfg.Set(k, header.Get(k))
    }
}

func Middleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        rec := httptest.NewRecorder()
        headers := http.Header{}

        next.ServeHTTP(rec, r.WithContext(context.WithValue(r.Context(), headersCfgKey, headers)))
        result := rec.Result()

        for k := range result.Header {
            w.Header().Set(k, result.Header.Get(k))
        }
        for k := range headers {
            w.Header().Set(k, headers.Get(k))
        }
        w.WriteHeader(result.StatusCode)
        if _, err := w.Write(rec.Body.Bytes()); err != nil {
            w.WriteHeader(http.StatusInternalServerError)
        }
    })
}

func NoStore() http.Header {
    h := http.Header{}
    h.Set("Cache-Control", "no-store, max-age=0")
    return h
}
func MaxAge(duration uint) http.Header {
    h := http.Header{}
    h.Set("Cache-Control", fmt.Sprintf("public, maxage=%d", duration))
    return h
}

and use middleware to set headers

    return server.Start(cancel, header.Middleware(r), host, port)