github.com/theQRL/go-zond@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(), &cfg, 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 resp jsonrpcMessage 180 batch := [1]*jsonrpcMessage{&resp} 181 if err := json.NewDecoder(respBody).Decode(&resp); err != nil { 182 return err 183 } 184 op.resp <- batch[:] 185 return nil 186 } 187 188 func (c *Client) sendBatchHTTP(ctx context.Context, op *requestOp, msgs []*jsonrpcMessage) error { 189 hc := c.writeConn.(*httpConn) 190 respBody, err := hc.doRequest(ctx, msgs) 191 if err != nil { 192 return err 193 } 194 defer respBody.Close() 195 196 var respmsgs []*jsonrpcMessage 197 if err := json.NewDecoder(respBody).Decode(&respmsgs); err != nil { 198 return err 199 } 200 op.resp <- respmsgs 201 return nil 202 } 203 204 func (hc *httpConn) doRequest(ctx context.Context, msg interface{}) (io.ReadCloser, error) { 205 body, err := json.Marshal(msg) 206 if err != nil { 207 return nil, err 208 } 209 req, err := http.NewRequestWithContext(ctx, http.MethodPost, hc.url, io.NopCloser(bytes.NewReader(body))) 210 if err != nil { 211 return nil, err 212 } 213 req.ContentLength = int64(len(body)) 214 req.GetBody = func() (io.ReadCloser, error) { return io.NopCloser(bytes.NewReader(body)), nil } 215 216 // set headers 217 hc.mu.Lock() 218 req.Header = hc.headers.Clone() 219 hc.mu.Unlock() 220 setHeaders(req.Header, headersFromContext(ctx)) 221 222 if hc.auth != nil { 223 if err := hc.auth(req.Header); err != nil { 224 return nil, err 225 } 226 } 227 228 // do request 229 resp, err := hc.client.Do(req) 230 if err != nil { 231 return nil, err 232 } 233 if resp.StatusCode < 200 || resp.StatusCode >= 300 { 234 var buf bytes.Buffer 235 var body []byte 236 if _, err := buf.ReadFrom(resp.Body); err == nil { 237 body = buf.Bytes() 238 } 239 240 return nil, HTTPError{ 241 Status: resp.Status, 242 StatusCode: resp.StatusCode, 243 Body: body, 244 } 245 } 246 return resp.Body, nil 247 } 248 249 // httpServerConn turns a HTTP connection into a Conn. 250 type httpServerConn struct { 251 io.Reader 252 io.Writer 253 r *http.Request 254 } 255 256 func newHTTPServerConn(r *http.Request, w http.ResponseWriter) ServerCodec { 257 body := io.LimitReader(r.Body, maxRequestContentLength) 258 conn := &httpServerConn{Reader: body, Writer: w, r: r} 259 260 encoder := func(v any, isErrorResponse bool) error { 261 if !isErrorResponse { 262 return json.NewEncoder(conn).Encode(v) 263 } 264 265 // It's an error response and requires special treatment. 266 // 267 // In case of a timeout error, the response must be written before the HTTP 268 // server's write timeout occurs. So we need to flush the response. The 269 // Content-Length header also needs to be set to ensure the client knows 270 // when it has the full response. 271 encdata, err := json.Marshal(v) 272 if err != nil { 273 return err 274 } 275 w.Header().Set("content-length", strconv.Itoa(len(encdata))) 276 277 // If this request is wrapped in a handler that might remove Content-Length (such 278 // as the automatic gzip we do in package node), we need to ensure the HTTP server 279 // doesn't perform chunked encoding. In case WriteTimeout is reached, the chunked 280 // encoding might not be finished correctly, and some clients do not like it when 281 // the final chunk is missing. 282 w.Header().Set("transfer-encoding", "identity") 283 284 _, err = w.Write(encdata) 285 if f, ok := w.(http.Flusher); ok { 286 f.Flush() 287 } 288 return err 289 } 290 291 dec := json.NewDecoder(conn) 292 dec.UseNumber() 293 294 return NewFuncCodec(conn, encoder, dec.Decode) 295 } 296 297 // Close does nothing and always returns nil. 298 func (t *httpServerConn) Close() error { return nil } 299 300 // RemoteAddr returns the peer address of the underlying connection. 301 func (t *httpServerConn) RemoteAddr() string { 302 return t.r.RemoteAddr 303 } 304 305 // SetWriteDeadline does nothing and always returns nil. 306 func (t *httpServerConn) SetWriteDeadline(time.Time) error { return nil } 307 308 // ServeHTTP serves JSON-RPC requests over HTTP. 309 func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { 310 // Permit dumb empty requests for remote health-checks (AWS) 311 if r.Method == http.MethodGet && r.ContentLength == 0 && r.URL.RawQuery == "" { 312 w.WriteHeader(http.StatusOK) 313 return 314 } 315 if code, err := validateRequest(r); err != nil { 316 http.Error(w, err.Error(), code) 317 return 318 } 319 320 // Create request-scoped context. 321 connInfo := PeerInfo{Transport: "http", RemoteAddr: r.RemoteAddr} 322 connInfo.HTTP.Version = r.Proto 323 connInfo.HTTP.Host = r.Host 324 connInfo.HTTP.Origin = r.Header.Get("Origin") 325 connInfo.HTTP.UserAgent = r.Header.Get("User-Agent") 326 ctx := r.Context() 327 ctx = context.WithValue(ctx, peerInfoContextKey{}, connInfo) 328 329 // All checks passed, create a codec that reads directly from the request body 330 // until EOF, writes the response to w, and orders the server to process a 331 // single request. 332 w.Header().Set("content-type", contentType) 333 codec := newHTTPServerConn(r, w) 334 defer codec.close() 335 s.serveSingleRequest(ctx, codec) 336 } 337 338 // validateRequest returns a non-zero response code and error message if the 339 // request is invalid. 340 func validateRequest(r *http.Request) (int, error) { 341 if r.Method == http.MethodPut || r.Method == http.MethodDelete { 342 return http.StatusMethodNotAllowed, errors.New("method not allowed") 343 } 344 if r.ContentLength > maxRequestContentLength { 345 err := fmt.Errorf("content length too large (%d>%d)", r.ContentLength, maxRequestContentLength) 346 return http.StatusRequestEntityTooLarge, err 347 } 348 // Allow OPTIONS (regardless of content-type) 349 if r.Method == http.MethodOptions { 350 return 0, nil 351 } 352 // Check content-type 353 if mt, _, err := mime.ParseMediaType(r.Header.Get("content-type")); err == nil { 354 for _, accepted := range acceptedContentTypes { 355 if accepted == mt { 356 return 0, nil 357 } 358 } 359 } 360 // Invalid content-type 361 err := fmt.Errorf("invalid content type, only %s is supported", contentType) 362 return http.StatusUnsupportedMediaType, err 363 } 364 365 // ContextRequestTimeout returns the request timeout derived from the given context. 366 func ContextRequestTimeout(ctx context.Context) (time.Duration, bool) { 367 timeout := time.Duration(math.MaxInt64) 368 hasTimeout := false 369 setTimeout := func(d time.Duration) { 370 if d < timeout { 371 timeout = d 372 hasTimeout = true 373 } 374 } 375 376 if deadline, ok := ctx.Deadline(); ok { 377 setTimeout(time.Until(deadline)) 378 } 379 380 // If the context is an HTTP request context, use the server's WriteTimeout. 381 httpSrv, ok := ctx.Value(http.ServerContextKey).(*http.Server) 382 if ok && httpSrv.WriteTimeout > 0 { 383 wt := httpSrv.WriteTimeout 384 // When a write timeout is configured, we need to send the response message before 385 // the HTTP server cuts connection. So our internal timeout must be earlier than 386 // the server's true timeout. 387 // 388 // Note: Timeouts are sanitized to be a minimum of 1 second. 389 // Also see issue: https://github.com/golang/go/issues/47229 390 wt -= 100 * time.Millisecond 391 setTimeout(wt) 392 } 393 394 return timeout, hasTimeout 395 }