From f59ed3565d18c1fa676fd34f4fd41ecccad707e8 Mon Sep 17 00:00:00 2001 From: Antoine Toulme Date: Wed, 25 Nov 2020 01:19:36 -0800 Subject: [PATCH] graphql: always return 400 if errors are present in the response (#21882) * Make sure to return 400 when errors are present in the response * graphql: use less memory in chainconfig for tests Co-authored-by: Martin Holst Swende --- graphql/graphql_test.go | 52 +++++++++++++++++++++++++++++++++++++++-- graphql/service.go | 36 ++++++++++++++++++++++++++-- 2 files changed, 84 insertions(+), 4 deletions(-) diff --git a/graphql/graphql_test.go b/graphql/graphql_test.go index 5ba9c9553..98c8622ee 100644 --- a/graphql/graphql_test.go +++ b/graphql/graphql_test.go @@ -22,8 +22,13 @@ import ( "net/http" "strings" "testing" + "time" + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/consensus/ethash" + "github.com/ethereum/go-ethereum/core" "github.com/ethereum/go-ethereum/eth" + "github.com/ethereum/go-ethereum/miner" "github.com/ethereum/go-ethereum/node" "github.com/stretchr/testify/assert" ) @@ -61,6 +66,7 @@ func TestGraphQLHTTPOnSamePort_GQLRequest_Successful(t *testing.T) { t.Fatalf("could not read from response body: %v", err) } expected := "{\"data\":{\"block\":{\"number\":\"0x0\"}}}" + assert.Equal(t, 200, resp.StatusCode) assert.Equal(t, expected, string(bodyBytes)) } @@ -90,6 +96,32 @@ func TestGraphQLHTTPOnSamePort_GQLRequest_Unsuccessful(t *testing.T) { assert.Equal(t, "404 page not found\n", string(bodyBytes)) } +// Tests that 400 is returned when an invalid RPC request is made. +func TestGraphQL_BadRequest(t *testing.T) { + stack := createNode(t, true) + defer stack.Close() + // start node + if err := stack.Start(); err != nil { + t.Fatalf("could not start node: %v", err) + } + // create http request + body := strings.NewReader("{\"query\": \"{bleh{number}}\",\"variables\": null}") + gqlReq, err := http.NewRequest(http.MethodGet, fmt.Sprintf("http://%s/graphql", "127.0.0.1:9393"), body) + if err != nil { + t.Error("could not issue new http request ", err) + } + gqlReq.Header.Set("Content-Type", "application/json") + // read from response + resp := doHTTPRequest(t, gqlReq) + bodyBytes, err := ioutil.ReadAll(resp.Body) + if err != nil { + t.Fatalf("could not read from response body: %v", err) + } + expected := "{\"errors\":[{\"message\":\"Cannot query field \\\"bleh\\\" on type \\\"Query\\\".\",\"locations\":[{\"line\":1,\"column\":2}]}]}" + assert.Equal(t, expected, string(bodyBytes)) + assert.Equal(t, 400, resp.StatusCode) +} + func createNode(t *testing.T, gqlEnabled bool) *node.Node { stack, err := node.New(&node.Config{ HTTPHost: "127.0.0.1", @@ -110,8 +142,24 @@ func createNode(t *testing.T, gqlEnabled bool) *node.Node { } func createGQLService(t *testing.T, stack *node.Node, endpoint string) { - // create backend - ethBackend, err := eth.New(stack, ð.DefaultConfig) + // create backend (use a config which is light on mem consumption) + ethConf := ð.Config{ + Genesis: core.DeveloperGenesisBlock(15, common.Address{}), + Miner: miner.Config{ + Etherbase: common.HexToAddress("0xaabb"), + }, + Ethash: ethash.Config{ + PowMode: ethash.ModeTest, + }, + NetworkId: 1337, + TrieCleanCache: 5, + TrieCleanCacheJournal: "triecache", + TrieCleanCacheRejournal: 60 * time.Minute, + TrieDirtyCache: 5, + TrieTimeout: 60 * time.Minute, + SnapshotCache: 5, + } + ethBackend, err := eth.New(stack, ethConf) if err != nil { t.Fatalf("could not create eth backend: %v", err) } diff --git a/graphql/service.go b/graphql/service.go index ae962e5b3..bcb0a4990 100644 --- a/graphql/service.go +++ b/graphql/service.go @@ -17,12 +17,44 @@ package graphql import ( + "encoding/json" + "net/http" + "github.com/ethereum/go-ethereum/internal/ethapi" "github.com/ethereum/go-ethereum/node" "github.com/graph-gophers/graphql-go" - "github.com/graph-gophers/graphql-go/relay" ) +type handler struct { + Schema *graphql.Schema +} + +func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + var params struct { + Query string `json:"query"` + OperationName string `json:"operationName"` + Variables map[string]interface{} `json:"variables"` + } + if err := json.NewDecoder(r.Body).Decode(¶ms); err != nil { + http.Error(w, err.Error(), http.StatusBadRequest) + return + } + + response := h.Schema.Exec(r.Context(), params.Query, params.OperationName, params.Variables) + responseJSON, err := json.Marshal(response) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + if len(response.Errors) > 0 { + w.WriteHeader(http.StatusBadRequest) + } + + w.Header().Set("Content-Type", "application/json") + w.Write(responseJSON) + +} + // New constructs a new GraphQL service instance. func New(stack *node.Node, backend ethapi.Backend, cors, vhosts []string) error { if backend == nil { @@ -41,7 +73,7 @@ func newHandler(stack *node.Node, backend ethapi.Backend, cors, vhosts []string) if err != nil { return err } - h := &relay.Handler{Schema: s} + h := handler{Schema: s} handler := node.NewHTTPHandlerStack(h, cors, vhosts) stack.RegisterHandler("GraphQL UI", "/graphql/ui", GraphiQL{}) -- GitLab