github.com/calmw/ethereum@v0.1.1/rpc/http.go (about) 1 // Copyright 2015 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 rpc 18 19 import ( 20 "bytes" 21 "context" 22 "encoding/json" 23 "errors" 24 "fmt" 25 "io" 26 "math" 27 "mime" 28 "net/http" 29 "net/url" 30 "strconv" 31 "sync" 32 "time" 33 ) 34 35 const ( 36 maxRequestContentLength = 1024 * 1024 * 5 37 contentType = "application/json" 38 ) 39 40 // https://www.jsonrpc.org/historical/json-rpc-over-http.html#id13 41 var acceptedContentTypes = []string{contentType, "application/json-rpc", "application/jsonrequest"} 42 43 type httpConn struct { 44 client *http.Client 45 url string 46 closeOnce sync.Once 47 closeCh chan interface{} 48 mu sync.Mutex // protects headers 49 headers http.Header 50 auth HTTPAuth 51 } 52 53 // httpConn implements ServerCodec, but it is treated specially by Client 54 // and some methods don't work. The panic() stubs here exist to ensure 55 // this special treatment is correct. 56 57 func (hc *httpConn) writeJSON(context.Context, interface{}, bool) error { 58 panic("writeJSON called on httpConn") 59 } 60 61 func (hc *httpConn) peerInfo() PeerInfo { 62 panic("peerInfo called on httpConn") 63 } 64 65 func (hc *httpConn) remoteAddr() string { 66 return hc.url 67 } 68 69 func (hc *httpConn) readBatch() ([]*jsonrpcMessage, bool, error) { 70 <-hc.closeCh 71 return nil, false, io.EOF 72 } 73 74 func (hc *httpConn) close() { 75 hc.closeOnce.Do(func() { close(hc.closeCh) }) 76 } 77 78 func (hc *httpConn) closed() <-chan interface{} { 79 return hc.closeCh 80 } 81 82 // HTTPTimeouts represents the configuration params for the HTTP RPC server. 83 type HTTPTimeouts struct { 84 // ReadTimeout is the maximum duration for reading the entire 85 // request, including the body. 86 // 87 // Because ReadTimeout does not let Handlers make per-request 88 // decisions on each request body's acceptable deadline or 89 // upload rate, most users will prefer to use 90 // ReadHeaderTimeout. It is valid to use them both. 91 ReadTimeout time.Duration 92 93 // ReadHeaderTimeout is the amount of time allowed to read 94 // request headers. The connection's read deadline is reset 95 // after reading the headers and the Handler can decide what 96 // is considered too slow for the body. If ReadHeaderTimeout 97 // is zero, the value of ReadTimeout is used. If both are 98 // zero, there is no timeout. 99 ReadHeaderTimeout time.Duration 100 101 // WriteTimeout is the maximum duration before timing out 102 // writes of the response. It is reset whenever a new 103 // request's header is read. Like ReadTimeout, it does not 104 // let Handlers make decisions on a per-request basis. 105 WriteTimeout time.Duration 106 107 // IdleTimeout is the maximum amount of time to wait for the 108 // next request when keep-alives are enabled. If IdleTimeout 109 // is zero, the value of ReadTimeout is used. If both are 110 // zero, ReadHeaderTimeout is used. 111 IdleTimeout time.Duration 112 } 113 114 // DefaultHTTPTimeouts represents the default timeout values used if further 115 // configuration is not provided. 116 var DefaultHTTPTimeouts = HTTPTimeouts{ 117 ReadTimeout: 30 * time.Second, 118 ReadHeaderTimeout: 30 * time.Second, 119 WriteTimeout: 30 * time.Second, 120 IdleTimeout: 120 * time.Second, 121 } 122 123 // DialHTTP creates a new RPC client that connects to an RPC server over HTTP. 124 func DialHTTP(endpoint string) (*Client, error) { 125 return DialHTTPWithClient(endpoint, new(http.Client)) 126 } 127 128 // DialHTTPWithClient creates a new RPC client that connects to an RPC server over HTTP 129 // using the provided HTTP Client. 130 // 131 // Deprecated: use DialOptions and the WithHTTPClient option. 132 func DialHTTPWithClient(endpoint string, client *http.Client) (*Client, error) { 133 // Sanity check URL so we don't end up with a client that will fail every request. 134 _, err := url.Parse(endpoint) 135 if err != nil { 136 return nil, err 137 } 138 139 var cfg clientConfig 140 cfg.httpClient = client 141 fn := newClientTransportHTTP(endpoint, &cfg) 142 return newClient(context.Background(), fn) 143 } 144 145 func newClientTransportHTTP(endpoint string, cfg *clientConfig) reconnectFunc { 146 headers := make(http.Header, 2+len(cfg.httpHeaders)) 147 headers.Set("accept", contentType) 148 headers.Set("content-type", contentType) 149 for key, values := range cfg.httpHeaders { 150 headers[key] = values 151 } 152 153 client := cfg.httpClient 154 if client == nil { 155 client = new(http.Client) 156 } 157 158 hc := &httpConn{ 159 client: client, 160 headers: headers, 161 url: endpoint, 162 auth: cfg.httpAuth, 163 closeCh: make(chan interface{}), 164 } 165 166 return func(ctx context.Context) (ServerCodec, error) { 167 return hc, nil 168 } 169 } 170 171 func (c *Client) sendHTTP(ctx context.Context, op *requestOp, msg interface{}) error { 172 hc := c.writeConn.(*httpConn) 173 respBody, err := hc.doRequest(ctx, msg) 174 if err != nil { 175 return err 176 } 177 defer respBody.Close() 178 179 var respmsg jsonrpcMessage 180 if err := json.NewDecoder(respBody).Decode(&respmsg); err != nil { 181 return err 182 } 183 op.resp <- &respmsg 184 return nil 185 } 186 187 func (c *Client) sendBatchHTTP(ctx context.Context, op *requestOp, msgs []*jsonrpcMessage) error { 188 hc := c.writeConn.(*httpConn) 189 respBody, err := hc.doRequest(ctx, msgs) 190 if err != nil { 191 return err 192 } 193 defer respBody.Close() 194 var respmsgs []jsonrpcMessage 195 if err := json.NewDecoder(respBody).Decode(&respmsgs); err != nil { 196 return err 197 } 198 if len(respmsgs) != len(msgs) { 199 return fmt.Errorf("batch has %d requests but response has %d: %w", len(msgs), len(respmsgs), ErrBadResult) 200 } 201 for i := 0; i < len(respmsgs); i++ { 202 op.resp <- &respmsgs[i] 203 } 204 return nil 205 } 206 207 func (hc *httpConn) doRequest(ctx context.Context, msg interface{}) (io.ReadCloser, error) { 208 body, err := json.Marshal(msg) 209 if err != nil { 210 return nil, err 211 } 212 req, err := http.NewRequestWithContext(ctx, http.MethodPost, hc.url, io.NopCloser(bytes.NewReader(body))) 213 if err != nil { 214 return nil, err 215 } 216 req.ContentLength = int64(len(body)) 217 req.GetBody = func() (io.ReadCloser, error) { return io.NopCloser(bytes.NewReader(body)), nil } 218 219 // set headers 220 hc.mu.Lock() 221 req.Header = hc.headers.Clone() 222 hc.mu.Unlock() 223 setHeaders(req.Header, headersFromContext(ctx)) 224 225 if hc.auth != nil { 226 if err := hc.auth(req.Header); err != nil { 227 return nil, err 228 } 229 } 230 231 // do request 232 resp, err := hc.client.Do(req) 233 if err != nil { 234 return nil, err 235 } 236 if resp.StatusCode < 200 || resp.StatusCode >= 300 { 237 var buf bytes.Buffer 238 var body []byte 239 if _, err := buf.ReadFrom(resp.Body); err == nil { 240 body = buf.Bytes() 241 } 242 243 return nil, HTTPError{ 244 Status: resp.Status, 245 StatusCode: resp.StatusCode, 246 Body: body, 247 } 248 } 249 return resp.Body, nil 250 } 251 252 // httpServerConn turns a HTTP connection into a Conn. 253 type httpServerConn struct { 254 io.Reader 255 io.Writer 256 r *http.Request 257 } 258 259 func newHTTPServerConn(r *http.Request, w http.ResponseWriter) ServerCodec { 260 body := io.LimitReader(r.Body, maxRequestContentLength) 261 conn := &httpServerConn{Reader: body, Writer: w, r: r} 262 263 encoder := func(v any, isErrorResponse bool) error { 264 if !isErrorResponse { 265 return json.NewEncoder(conn).Encode(v) 266 } 267 268 // It's an error response and requires special treatment. 269 // 270 // In case of a timeout error, the response must be written before the HTTP 271 // server's write timeout occurs. So we need to flush the response. The 272 // Content-Length header also needs to be set to ensure the client knows 273 // when it has the full response. 274 encdata, err := json.Marshal(v) 275 if err != nil { 276 return err 277 } 278 w.Header().Set("content-length", strconv.Itoa(len(encdata))) 279 280 // If this request is wrapped in a handler that might remove Content-Length (such 281 // as the automatic gzip we do in package node), we need to ensure the HTTP server 282 // doesn't perform chunked encoding. In case WriteTimeout is reached, the chunked 283 // encoding might not be finished correctly, and some clients do not like it when 284 // the final chunk is missing. 285 w.Header().Set("transfer-encoding", "identity") 286 287 _, err = w.Write(encdata) 288 if f, ok := w.(http.Flusher); ok { 289 f.Flush() 290 } 291 return err 292 } 293 294 dec := json.NewDecoder(conn) 295 dec.UseNumber() 296 297 return NewFuncCodec(conn, encoder, dec.Decode) 298 } 299 300 // Close does nothing and always returns nil. 301 func (t *httpServerConn) Close() error { return nil } 302 303 // RemoteAddr returns the peer address of the underlying connection. 304 func (t *httpServerConn) RemoteAddr() string { 305 return t.r.RemoteAddr 306 } 307 308 // SetWriteDeadline does nothing and always returns nil. 309 func (t *httpServerConn) SetWriteDeadline(time.Time) error { return nil } 310 311 // ServeHTTP serves JSON-RPC requests over HTTP. 312 func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { 313 // Permit dumb empty requests for remote health-checks (AWS) 314 if r.Method == http.MethodGet && r.ContentLength == 0 && r.URL.RawQuery == "" { 315 w.WriteHeader(http.StatusOK) 316 return 317 } 318 if code, err := validateRequest(r); err != nil { 319 http.Error(w, err.Error(), code) 320 return 321 } 322 323 // Create request-scoped context. 324 connInfo := PeerInfo{Transport: "http", RemoteAddr: r.RemoteAddr} 325 connInfo.HTTP.Version = r.Proto 326 connInfo.HTTP.Host = r.Host 327 connInfo.HTTP.Origin = r.Header.Get("Origin") 328 connInfo.HTTP.UserAgent = r.Header.Get("User-Agent") 329 ctx := r.Context() 330 ctx = context.WithValue(ctx, peerInfoContextKey{}, connInfo) 331 332 // All checks passed, create a codec that reads directly from the request body 333 // until EOF, writes the response to w, and orders the server to process a 334 // single request. 335 w.Header().Set("content-type", contentType) 336 codec := newHTTPServerConn(r, w) 337 defer codec.close() 338 s.serveSingleRequest(ctx, codec) 339 } 340 341 // validateRequest returns a non-zero response code and error message if the 342 // request is invalid. 343 func validateRequest(r *http.Request) (int, error) { 344 if r.Method == http.MethodPut || r.Method == http.MethodDelete { 345 return http.StatusMethodNotAllowed, errors.New("method not allowed") 346 } 347 if r.ContentLength > maxRequestContentLength { 348 err := fmt.Errorf("content length too large (%d>%d)", r.ContentLength, maxRequestContentLength) 349 return http.StatusRequestEntityTooLarge, err 350 } 351 // Allow OPTIONS (regardless of content-type) 352 if r.Method == http.MethodOptions { 353 return 0, nil 354 } 355 // Check content-type 356 if mt, _, err := mime.ParseMediaType(r.Header.Get("content-type")); err == nil { 357 for _, accepted := range acceptedContentTypes { 358 if accepted == mt { 359 return 0, nil 360 } 361 } 362 } 363 // Invalid content-type 364 err := fmt.Errorf("invalid content type, only %s is supported", contentType) 365 return http.StatusUnsupportedMediaType, err 366 } 367 368 // ContextRequestTimeout returns the request timeout derived from the given context. 369 func ContextRequestTimeout(ctx context.Context) (time.Duration, bool) { 370 timeout := time.Duration(math.MaxInt64) 371 hasTimeout := false 372 setTimeout := func(d time.Duration) { 373 if d < timeout { 374 timeout = d 375 hasTimeout = true 376 } 377 } 378 379 if deadline, ok := ctx.Deadline(); ok { 380 setTimeout(time.Until(deadline)) 381 } 382 383 // If the context is an HTTP request context, use the server's WriteTimeout. 384 httpSrv, ok := ctx.Value(http.ServerContextKey).(*http.Server) 385 if ok && httpSrv.WriteTimeout > 0 { 386 wt := httpSrv.WriteTimeout 387 // When a write timeout is configured, we need to send the response message before 388 // the HTTP server cuts connection. So our internal timeout must be earlier than 389 // the server's true timeout. 390 // 391 // Note: Timeouts are sanitized to be a minimum of 1 second. 392 // Also see issue: https://github.com/golang/go/issues/47229 393 wt -= 100 * time.Millisecond 394 setTimeout(wt) 395 } 396 397 return timeout, hasTimeout 398 }