-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Allow setting headers in HTTP transports (#2590)
Currently gqlgen sets Content-Type header to 'application/json'. There's no easy way to change it or add additional headers. This commit adds struct variable ResponseHeaders that can hold any headers you want to be returned with response. It is standard `map[string][]string` variable. If user does not set this map, we default to the Content-Type header with 'application/json' value - nothing will be changed for existing users. Usage: as simple as: ``` headers := map[string][]string{ "Content-Type": {"application/json; charset: utf8"}, "Other-Header": {"dummy-post-header","another-value"}, } h.AddTransport(transport.POST{ResponseHeaders: headers}) ``` Added tests in transport/headers_test.go.
- Loading branch information
Showing
5 changed files
with
197 additions
and
5 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,17 @@ | ||
package transport | ||
|
||
import "net/http" | ||
|
||
func writeHeaders(w http.ResponseWriter, headers map[string][]string) { | ||
if len(headers) == 0 { | ||
headers = map[string][]string{ | ||
"Content-Type": {"application/json"}, | ||
} | ||
} | ||
|
||
for key, values := range headers { | ||
for _, value := range values { | ||
w.Header().Add(key, value) | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,163 @@ | ||
package transport_test | ||
|
||
import ( | ||
"context" | ||
"net/http" | ||
"net/http/httptest" | ||
"testing" | ||
|
||
"github.com/99designs/gqlgen/graphql" | ||
"github.com/99designs/gqlgen/graphql/handler" | ||
"github.com/99designs/gqlgen/graphql/handler/testserver" | ||
"github.com/99designs/gqlgen/graphql/handler/transport" | ||
"github.com/stretchr/testify/assert" | ||
"github.com/stretchr/testify/require" | ||
"github.com/vektah/gqlparser/v2" | ||
"github.com/vektah/gqlparser/v2/ast" | ||
) | ||
|
||
func TestHeadersWithPOST(t *testing.T) { | ||
t.Run("Headers not set", func(t *testing.T) { | ||
h := testserver.New() | ||
h.AddTransport(transport.POST{}) | ||
|
||
resp := doRequest(h, "POST", "/graphql", `{"query":"{ name }"}`) | ||
assert.Equal(t, http.StatusOK, resp.Code) | ||
assert.Equal(t, 1, len(resp.Header())) | ||
assert.Equal(t, "application/json", resp.Header().Get("Content-Type")) | ||
}) | ||
|
||
t.Run("Headers set", func(t *testing.T) { | ||
headers := map[string][]string{ | ||
"Content-Type": {"application/json; charset: utf8"}, | ||
"Other-Header": {"dummy-post", "another-one"}, | ||
} | ||
|
||
h := testserver.New() | ||
h.AddTransport(transport.POST{ResponseHeaders: headers}) | ||
|
||
resp := doRequest(h, "POST", "/graphql", `{"query":"{ name }"}`) | ||
assert.Equal(t, http.StatusOK, resp.Code) | ||
assert.Equal(t, 2, len(resp.Header())) | ||
assert.Equal(t, "application/json; charset: utf8", resp.Header().Get("Content-Type")) | ||
assert.Equal(t, "dummy-post", resp.Header().Get("Other-Header")) | ||
assert.Equal(t, "another-one", resp.Header().Values("Other-Header")[1]) | ||
}) | ||
} | ||
|
||
func TestHeadersWithGET(t *testing.T) { | ||
t.Run("Headers not set", func(t *testing.T) { | ||
h := testserver.New() | ||
h.AddTransport(transport.GET{}) | ||
|
||
resp := doRequest(h, "GET", "/graphql?query={name}", "") | ||
assert.Equal(t, http.StatusOK, resp.Code) | ||
assert.Equal(t, 1, len(resp.Header())) | ||
assert.Equal(t, "application/json", resp.Header().Get("Content-Type")) | ||
}) | ||
|
||
t.Run("Headers set", func(t *testing.T) { | ||
headers := map[string][]string{ | ||
"Content-Type": {"application/json; charset: utf8"}, | ||
"Other-Header": {"dummy-get"}, | ||
} | ||
|
||
h := testserver.New() | ||
h.AddTransport(transport.GET{ResponseHeaders: headers}) | ||
|
||
resp := doRequest(h, "GET", "/graphql?query={name}", "") | ||
assert.Equal(t, http.StatusOK, resp.Code) | ||
assert.Equal(t, 2, len(resp.Header())) | ||
assert.Equal(t, "application/json; charset: utf8", resp.Header().Get("Content-Type")) | ||
assert.Equal(t, "dummy-get", resp.Header().Get("Other-Header")) | ||
}) | ||
} | ||
|
||
func TestHeadersWithMULTIPART(t *testing.T) { | ||
t.Run("Headers not set", func(t *testing.T) { | ||
es := &graphql.ExecutableSchemaMock{ | ||
ExecFunc: func(ctx context.Context) graphql.ResponseHandler { | ||
return graphql.OneShot(graphql.ErrorResponse(ctx, "not implemented")) | ||
}, | ||
SchemaFunc: func() *ast.Schema { | ||
return gqlparser.MustLoadSchema(&ast.Source{Input: ` | ||
type Mutation { | ||
singleUpload(file: Upload!): String! | ||
} | ||
scalar Upload | ||
`}) | ||
}, | ||
} | ||
|
||
h := handler.New(es) | ||
h.AddTransport(transport.MultipartForm{}) | ||
|
||
es.ExecFunc = func(ctx context.Context) graphql.ResponseHandler { | ||
return graphql.OneShot(&graphql.Response{Data: []byte(`{"singleUpload":"test"}`)}) | ||
} | ||
|
||
operations := `{ "query": "mutation ($file: Upload!) { singleUpload(file: $file) }", "variables": { "file": null } }` | ||
mapData := `{ "0": ["variables.file"] }` | ||
files := []file{ | ||
{ | ||
mapKey: "0", | ||
name: "a.txt", | ||
content: "test1", | ||
contentType: "text/plain", | ||
}, | ||
} | ||
req := createUploadRequest(t, operations, mapData, files) | ||
|
||
resp := httptest.NewRecorder() | ||
h.ServeHTTP(resp, req) | ||
require.Equal(t, http.StatusOK, resp.Code, resp.Body.String()) | ||
assert.Equal(t, 1, len(resp.Header())) | ||
assert.Equal(t, "application/json", resp.Header().Get("Content-Type")) | ||
}) | ||
|
||
t.Run("Headers set", func(t *testing.T) { | ||
es := &graphql.ExecutableSchemaMock{ | ||
ExecFunc: func(ctx context.Context) graphql.ResponseHandler { | ||
return graphql.OneShot(graphql.ErrorResponse(ctx, "not implemented")) | ||
}, | ||
SchemaFunc: func() *ast.Schema { | ||
return gqlparser.MustLoadSchema(&ast.Source{Input: ` | ||
type Mutation { | ||
singleUpload(file: Upload!): String! | ||
} | ||
scalar Upload | ||
`}) | ||
}, | ||
} | ||
|
||
h := handler.New(es) | ||
headers := map[string][]string{ | ||
"Content-Type": {"application/json; charset: utf8"}, | ||
"Other-Header": {"dummy-multipart"}, | ||
} | ||
h.AddTransport(transport.MultipartForm{ResponseHeaders: headers}) | ||
|
||
es.ExecFunc = func(ctx context.Context) graphql.ResponseHandler { | ||
return graphql.OneShot(&graphql.Response{Data: []byte(`{"singleUpload":"test"}`)}) | ||
} | ||
|
||
operations := `{ "query": "mutation ($file: Upload!) { singleUpload(file: $file) }", "variables": { "file": null } }` | ||
mapData := `{ "0": ["variables.file"] }` | ||
files := []file{ | ||
{ | ||
mapKey: "0", | ||
name: "a.txt", | ||
content: "test1", | ||
contentType: "text/plain", | ||
}, | ||
} | ||
req := createUploadRequest(t, operations, mapData, files) | ||
|
||
resp := httptest.NewRecorder() | ||
h.ServeHTTP(resp, req) | ||
require.Equal(t, http.StatusOK, resp.Code, resp.Body.String()) | ||
assert.Equal(t, 2, len(resp.Header())) | ||
assert.Equal(t, "application/json; charset: utf8", resp.Header().Get("Content-Type")) | ||
assert.Equal(t, "dummy-multipart", resp.Header().Get("Other-Header")) | ||
}) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters