github.com/MetalBlockchain/subnet-evm@v0.4.9/rpc/http.go (about) 1 // (c) 2019-2020, Ava Labs, Inc. 2 // 3 // This file is a derived work, based on the go-ethereum library whose original 4 // notices appear below. 5 // 6 // It is distributed under a license compatible with the licensing terms of the 7 // original code from which it is derived. 8 // 9 // Much love to the original authors for their work. 10 // ********** 11 // Copyright 2015 The go-ethereum Authors 12 // This file is part of the go-ethereum library. 13 // 14 // The go-ethereum library is free software: you can redistribute it and/or modify 15 // it under the terms of the GNU Lesser General Public License as published by 16 // the Free Software Foundation, either version 3 of the License, or 17 // (at your option) any later version. 18 // 19 // The go-ethereum library is distributed in the hope that it will be useful, 20 // but WITHOUT ANY WARRANTY; without even the implied warranty of 21 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 22 // GNU Lesser General Public License for more details. 23 // 24 // You should have received a copy of the GNU Lesser General Public License 25 // along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>. 26 27 package rpc 28 29 import ( 30 "bytes" 31 "context" 32 "encoding/json" 33 "errors" 34 "fmt" 35 "io" 36 "mime" 37 "net/http" 38 "net/url" 39 "sync" 40 "time" 41 ) 42 43 const ( 44 maxRequestContentLength = 1024 * 1024 * 5 45 contentType = "application/json" 46 ) 47 48 // https://www.jsonrpc.org/historical/json-rpc-over-http.html#id13 49 var acceptedContentTypes = []string{contentType, "application/json-rpc", "application/jsonrequest"} 50 51 type httpConn struct { 52 client *http.Client 53 url string 54 closeOnce sync.Once 55 closeCh chan interface{} 56 mu sync.Mutex // protects headers 57 headers http.Header 58 } 59 60 // httpConn implements ServerCodec, but it is treated specially by Client 61 // and some methods don't work. The panic() stubs here exist to ensure 62 // this special treatment is correct. 63 64 func (hc *httpConn) writeJSON(ctx context.Context, val interface{}) error { 65 return hc.writeJSONSkipDeadline(ctx, val, false) 66 } 67 68 func (hc *httpConn) writeJSONSkipDeadline(context.Context, interface{}, bool) error { 69 panic("writeJSON called on httpConn") 70 } 71 72 func (hc *httpConn) peerInfo() PeerInfo { 73 panic("peerInfo called on httpConn") 74 } 75 76 func (hc *httpConn) remoteAddr() string { 77 return hc.url 78 } 79 80 func (hc *httpConn) readBatch() ([]*jsonrpcMessage, bool, error) { 81 <-hc.closeCh 82 return nil, false, io.EOF 83 } 84 85 func (hc *httpConn) close() { 86 hc.closeOnce.Do(func() { close(hc.closeCh) }) 87 } 88 89 func (hc *httpConn) closed() <-chan interface{} { 90 return hc.closeCh 91 } 92 93 // HTTPTimeouts represents the configuration params for the HTTP RPC server. 94 type HTTPTimeouts struct { 95 // ReadTimeout is the maximum duration for reading the entire 96 // request, including the body. 97 // 98 // Because ReadTimeout does not let Handlers make per-request 99 // decisions on each request body's acceptable deadline or 100 // upload rate, most users will prefer to use 101 // ReadHeaderTimeout. It is valid to use them both. 102 ReadTimeout time.Duration 103 104 // ReadHeaderTimeout is the amount of time allowed to read 105 // request headers. The connection's read deadline is reset 106 // after reading the headers and the Handler can decide what 107 // is considered too slow for the body. If ReadHeaderTimeout 108 // is zero, the value of ReadTimeout is used. If both are 109 // zero, there is no timeout. 110 ReadHeaderTimeout time.Duration 111 112 // WriteTimeout is the maximum duration before timing out 113 // writes of the response. It is reset whenever a new 114 // request's header is read. Like ReadTimeout, it does not 115 // let Handlers make decisions on a per-request basis. 116 WriteTimeout time.Duration 117 118 // IdleTimeout is the maximum amount of time to wait for the 119 // next request when keep-alives are enabled. If IdleTimeout 120 // is zero, the value of ReadTimeout is used. If both are 121 // zero, ReadHeaderTimeout is used. 122 IdleTimeout time.Duration 123 } 124 125 // DefaultHTTPTimeouts represents the default timeout values used if further 126 // configuration is not provided. 127 var DefaultHTTPTimeouts = HTTPTimeouts{ 128 ReadTimeout: 30 * time.Second, 129 ReadHeaderTimeout: 30 * time.Second, 130 WriteTimeout: 30 * time.Second, 131 IdleTimeout: 120 * time.Second, 132 } 133 134 // DialHTTPWithClient creates a new RPC client that connects to an RPC server over HTTP 135 // using the provided HTTP Client. 136 func DialHTTPWithClient(endpoint string, client *http.Client) (*Client, error) { 137 // Sanity check URL so we don't end up with a client that will fail every request. 138 _, err := url.Parse(endpoint) 139 if err != nil { 140 return nil, err 141 } 142 143 initctx := context.Background() 144 headers := make(http.Header, 2) 145 headers.Set("accept", contentType) 146 headers.Set("content-type", contentType) 147 return newClient(initctx, func(context.Context) (ServerCodec, error) { 148 hc := &httpConn{ 149 client: client, 150 headers: headers, 151 url: endpoint, 152 closeCh: make(chan interface{}), 153 } 154 return hc, nil 155 }) 156 } 157 158 // DialHTTP creates a new RPC client that connects to an RPC server over HTTP. 159 func DialHTTP(endpoint string) (*Client, error) { 160 return DialHTTPWithClient(endpoint, new(http.Client)) 161 } 162 163 func (c *Client) sendHTTP(ctx context.Context, op *requestOp, msg interface{}) error { 164 hc := c.writeConn.(*httpConn) 165 respBody, err := hc.doRequest(ctx, msg) 166 if err != nil { 167 return err 168 } 169 defer respBody.Close() 170 171 var respmsg jsonrpcMessage 172 if err := json.NewDecoder(respBody).Decode(&respmsg); err != nil { 173 return err 174 } 175 op.resp <- &respmsg 176 return nil 177 } 178 179 func (c *Client) sendBatchHTTP(ctx context.Context, op *requestOp, msgs []*jsonrpcMessage) error { 180 hc := c.writeConn.(*httpConn) 181 respBody, err := hc.doRequest(ctx, msgs) 182 if err != nil { 183 return err 184 } 185 defer respBody.Close() 186 var respmsgs []jsonrpcMessage 187 if err := json.NewDecoder(respBody).Decode(&respmsgs); err != nil { 188 return err 189 } 190 if len(respmsgs) != len(msgs) { 191 return fmt.Errorf("batch has %d requests but response has %d: %w", len(msgs), len(respmsgs), ErrBadResult) 192 } 193 for i := 0; i < len(respmsgs); i++ { 194 op.resp <- &respmsgs[i] 195 } 196 return nil 197 } 198 199 func (hc *httpConn) doRequest(ctx context.Context, msg interface{}) (io.ReadCloser, error) { 200 body, err := json.Marshal(msg) 201 if err != nil { 202 return nil, err 203 } 204 req, err := http.NewRequestWithContext(ctx, "POST", hc.url, io.NopCloser(bytes.NewReader(body))) 205 if err != nil { 206 return nil, err 207 } 208 req.ContentLength = int64(len(body)) 209 req.GetBody = func() (io.ReadCloser, error) { return io.NopCloser(bytes.NewReader(body)), nil } 210 211 // set headers 212 hc.mu.Lock() 213 req.Header = hc.headers.Clone() 214 hc.mu.Unlock() 215 216 // do request 217 resp, err := hc.client.Do(req) 218 if err != nil { 219 return nil, err 220 } 221 if resp.StatusCode < 200 || resp.StatusCode >= 300 { 222 var buf bytes.Buffer 223 var body []byte 224 if _, err := buf.ReadFrom(resp.Body); err == nil { 225 body = buf.Bytes() 226 } 227 228 return nil, HTTPError{ 229 Status: resp.Status, 230 StatusCode: resp.StatusCode, 231 Body: body, 232 } 233 } 234 return resp.Body, nil 235 } 236 237 // httpServerConn turns a HTTP connection into a Conn. 238 type httpServerConn struct { 239 io.Reader 240 io.Writer 241 r *http.Request 242 } 243 244 func newHTTPServerConn(r *http.Request, w http.ResponseWriter) ServerCodec { 245 body := io.LimitReader(r.Body, maxRequestContentLength) 246 conn := &httpServerConn{Reader: body, Writer: w, r: r} 247 return NewCodec(conn) 248 } 249 250 // Close does nothing and always returns nil. 251 func (t *httpServerConn) Close() error { return nil } 252 253 // RemoteAddr returns the peer address of the underlying connection. 254 func (t *httpServerConn) RemoteAddr() string { 255 return t.r.RemoteAddr 256 } 257 258 // SetWriteDeadline does nothing and always returns nil. 259 func (t *httpServerConn) SetWriteDeadline(time.Time) error { return nil } 260 261 // ServeHTTP serves JSON-RPC requests over HTTP. 262 func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { 263 // Permit dumb empty requests for remote health-checks (AWS) 264 if r.Method == http.MethodGet && r.ContentLength == 0 && r.URL.RawQuery == "" { 265 w.WriteHeader(http.StatusOK) 266 return 267 } 268 if code, err := validateRequest(r); err != nil { 269 http.Error(w, err.Error(), code) 270 return 271 } 272 273 // Create request-scoped context. 274 connInfo := PeerInfo{Transport: "http", RemoteAddr: r.RemoteAddr} 275 connInfo.HTTP.Version = r.Proto 276 connInfo.HTTP.Host = r.Host 277 connInfo.HTTP.Origin = r.Header.Get("Origin") 278 connInfo.HTTP.UserAgent = r.Header.Get("User-Agent") 279 ctx := r.Context() 280 ctx = context.WithValue(ctx, peerInfoContextKey{}, connInfo) 281 // All checks passed, create a codec that reads directly from the request body 282 // until EOF, writes the response to w, and orders the server to process a 283 // single request. 284 285 w.Header().Set("content-type", contentType) 286 codec := newHTTPServerConn(r, w) 287 defer codec.close() 288 s.serveSingleRequest(ctx, codec) 289 } 290 291 // validateRequest returns a non-zero response code and error message if the 292 // request is invalid. 293 func validateRequest(r *http.Request) (int, error) { 294 if r.Method == http.MethodPut || r.Method == http.MethodDelete { 295 return http.StatusMethodNotAllowed, errors.New("method not allowed") 296 } 297 if r.ContentLength > maxRequestContentLength { 298 err := fmt.Errorf("content length too large (%d>%d)", r.ContentLength, maxRequestContentLength) 299 return http.StatusRequestEntityTooLarge, err 300 } 301 // Allow OPTIONS (regardless of content-type) 302 if r.Method == http.MethodOptions { 303 return 0, nil 304 } 305 // Check content-type 306 if mt, _, err := mime.ParseMediaType(r.Header.Get("content-type")); err == nil { 307 for _, accepted := range acceptedContentTypes { 308 if accepted == mt { 309 return 0, nil 310 } 311 } 312 } 313 // Invalid content-type 314 err := fmt.Errorf("invalid content type, only %s is supported", contentType) 315 return http.StatusUnsupportedMediaType, err 316 }