github.com/jimmyx0x/go-ethereum@v1.10.28/graphql/service.go (about) 1 // Copyright 2019 The go-ethereum Authors 2 // This file is part of the go-ethereum library. 3 // 4 // The go-ethereum library is free software: you can redistribute it and/or modify 5 // it under the terms of the GNU Lesser General Public License as published by 6 // the Free Software Foundation, either version 3 of the License, or 7 // (at your option) any later version. 8 // 9 // The go-ethereum library is distributed in the hope that it will be useful, 10 // but WITHOUT ANY WARRANTY; without even the implied warranty of 11 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 // GNU Lesser General Public License for more details. 13 // 14 // You should have received a copy of the GNU Lesser General Public License 15 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 16 17 package graphql 18 19 import ( 20 "context" 21 "encoding/json" 22 "net/http" 23 "strconv" 24 "sync" 25 "time" 26 27 "github.com/ethereum/go-ethereum/eth/filters" 28 "github.com/ethereum/go-ethereum/internal/ethapi" 29 "github.com/ethereum/go-ethereum/node" 30 "github.com/ethereum/go-ethereum/rpc" 31 "github.com/graph-gophers/graphql-go" 32 gqlErrors "github.com/graph-gophers/graphql-go/errors" 33 ) 34 35 type handler struct { 36 Schema *graphql.Schema 37 } 38 39 func (h handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 40 var params struct { 41 Query string `json:"query"` 42 OperationName string `json:"operationName"` 43 Variables map[string]interface{} `json:"variables"` 44 } 45 if err := json.NewDecoder(r.Body).Decode(¶ms); err != nil { 46 http.Error(w, err.Error(), http.StatusBadRequest) 47 return 48 } 49 50 var ( 51 ctx = r.Context() 52 responded sync.Once 53 timer *time.Timer 54 cancel context.CancelFunc 55 ) 56 ctx, cancel = context.WithCancel(ctx) 57 defer cancel() 58 59 if timeout, ok := rpc.ContextRequestTimeout(ctx); ok { 60 timer = time.AfterFunc(timeout, func() { 61 responded.Do(func() { 62 // Cancel request handling. 63 cancel() 64 65 // Create the timeout response. 66 response := &graphql.Response{ 67 Errors: []*gqlErrors.QueryError{{Message: "request timed out"}}, 68 } 69 responseJSON, err := json.Marshal(response) 70 if err != nil { 71 http.Error(w, err.Error(), http.StatusInternalServerError) 72 return 73 } 74 75 // Setting this disables gzip compression in package node. 76 w.Header().Set("transfer-encoding", "identity") 77 78 // Flush the response. Since we are writing close to the response timeout, 79 // chunked transfer encoding must be disabled by setting content-length. 80 w.Header().Set("content-type", "application/json") 81 w.Header().Set("content-length", strconv.Itoa(len(responseJSON))) 82 w.Write(responseJSON) 83 if flush, ok := w.(http.Flusher); ok { 84 flush.Flush() 85 } 86 }) 87 }) 88 } 89 90 response := h.Schema.Exec(ctx, params.Query, params.OperationName, params.Variables) 91 timer.Stop() 92 responded.Do(func() { 93 responseJSON, err := json.Marshal(response) 94 if err != nil { 95 http.Error(w, err.Error(), http.StatusInternalServerError) 96 return 97 } 98 if len(response.Errors) > 0 { 99 w.WriteHeader(http.StatusBadRequest) 100 } 101 w.Header().Set("Content-Type", "application/json") 102 w.Write(responseJSON) 103 }) 104 } 105 106 // New constructs a new GraphQL service instance. 107 func New(stack *node.Node, backend ethapi.Backend, filterSystem *filters.FilterSystem, cors, vhosts []string) error { 108 _, err := newHandler(stack, backend, filterSystem, cors, vhosts) 109 return err 110 } 111 112 // newHandler returns a new `http.Handler` that will answer GraphQL queries. 113 // It additionally exports an interactive query browser on the / endpoint. 114 func newHandler(stack *node.Node, backend ethapi.Backend, filterSystem *filters.FilterSystem, cors, vhosts []string) (*handler, error) { 115 q := Resolver{backend, filterSystem} 116 117 s, err := graphql.ParseSchema(schema, &q) 118 if err != nil { 119 return nil, err 120 } 121 h := handler{Schema: s} 122 handler := node.NewHTTPHandlerStack(h, cors, vhosts, nil) 123 124 stack.RegisterHandler("GraphQL UI", "/graphql/ui", GraphiQL{}) 125 stack.RegisterHandler("GraphQL", "/graphql", handler) 126 stack.RegisterHandler("GraphQL", "/graphql/", handler) 127 128 return &h, nil 129 }