github.com/0xsequence/ethkit@v1.25.0/go-ethereum/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  	"fmt"
    24  	"io"
    25  	"net/http"
    26  	"net/url"
    27  	"sync"
    28  	"time"
    29  )
    30  
    31  const (
    32  	maxRequestContentLength = 1024 * 1024 * 5
    33  	contentType             = "application/json"
    34  )
    35  
    36  // https://www.jsonrpc.org/historical/json-rpc-over-http.html#id13
    37  var acceptedContentTypes = []string{contentType, "application/json-rpc", "application/jsonrequest"}
    38  
    39  type httpConn struct {
    40  	client    *http.Client
    41  	url       string
    42  	closeOnce sync.Once
    43  	closeCh   chan interface{}
    44  	mu        sync.Mutex // protects headers
    45  	headers   http.Header
    46  }
    47  
    48  // httpConn implements ServerCodec, but it is treated specially by Client
    49  // and some methods don't work. The panic() stubs here exist to ensure
    50  // this special treatment is correct.
    51  
    52  func (hc *httpConn) writeJSON(context.Context, interface{}) error {
    53  	panic("writeJSON called on httpConn")
    54  }
    55  
    56  func (hc *httpConn) peerInfo() PeerInfo {
    57  	panic("peerInfo called on httpConn")
    58  }
    59  
    60  func (hc *httpConn) remoteAddr() string {
    61  	return hc.url
    62  }
    63  
    64  func (hc *httpConn) readBatch() ([]*jsonrpcMessage, bool, error) {
    65  	<-hc.closeCh
    66  	return nil, false, io.EOF
    67  }
    68  
    69  func (hc *httpConn) close() {
    70  	hc.closeOnce.Do(func() { close(hc.closeCh) })
    71  }
    72  
    73  func (hc *httpConn) closed() <-chan interface{} {
    74  	return hc.closeCh
    75  }
    76  
    77  // HTTPTimeouts represents the configuration params for the HTTP RPC server.
    78  type HTTPTimeouts struct {
    79  	// ReadTimeout is the maximum duration for reading the entire
    80  	// request, including the body.
    81  	//
    82  	// Because ReadTimeout does not let Handlers make per-request
    83  	// decisions on each request body's acceptable deadline or
    84  	// upload rate, most users will prefer to use
    85  	// ReadHeaderTimeout. It is valid to use them both.
    86  	ReadTimeout time.Duration
    87  
    88  	// ReadHeaderTimeout is the amount of time allowed to read
    89  	// request headers. The connection's read deadline is reset
    90  	// after reading the headers and the Handler can decide what
    91  	// is considered too slow for the body. If ReadHeaderTimeout
    92  	// is zero, the value of ReadTimeout is used. If both are
    93  	// zero, there is no timeout.
    94  	ReadHeaderTimeout time.Duration
    95  
    96  	// WriteTimeout is the maximum duration before timing out
    97  	// writes of the response. It is reset whenever a new
    98  	// request's header is read. Like ReadTimeout, it does not
    99  	// let Handlers make decisions on a per-request basis.
   100  	WriteTimeout time.Duration
   101  
   102  	// IdleTimeout is the maximum amount of time to wait for the
   103  	// next request when keep-alives are enabled. If IdleTimeout
   104  	// is zero, the value of ReadTimeout is used. If both are
   105  	// zero, ReadHeaderTimeout is used.
   106  	IdleTimeout time.Duration
   107  }
   108  
   109  // DefaultHTTPTimeouts represents the default timeout values used if further
   110  // configuration is not provided.
   111  var DefaultHTTPTimeouts = HTTPTimeouts{
   112  	ReadTimeout:       30 * time.Second,
   113  	ReadHeaderTimeout: 30 * time.Second,
   114  	WriteTimeout:      30 * time.Second,
   115  	IdleTimeout:       120 * time.Second,
   116  }
   117  
   118  // DialHTTPWithClient creates a new RPC client that connects to an RPC server over HTTP
   119  // using the provided HTTP Client.
   120  func DialHTTPWithClient(endpoint string, client *http.Client) (*Client, error) {
   121  	// Sanity check URL so we don't end up with a client that will fail every request.
   122  	_, err := url.Parse(endpoint)
   123  	if err != nil {
   124  		return nil, err
   125  	}
   126  
   127  	initctx := context.Background()
   128  	headers := make(http.Header, 2)
   129  	headers.Set("accept", contentType)
   130  	headers.Set("content-type", contentType)
   131  	return newClient(initctx, func(context.Context) (ServerCodec, error) {
   132  		hc := &httpConn{
   133  			client:  client,
   134  			headers: headers,
   135  			url:     endpoint,
   136  			closeCh: make(chan interface{}),
   137  		}
   138  		return hc, nil
   139  	})
   140  }
   141  
   142  // DialHTTP creates a new RPC client that connects to an RPC server over HTTP.
   143  func DialHTTP(endpoint string) (*Client, error) {
   144  	return DialHTTPWithClient(endpoint, new(http.Client))
   145  }
   146  
   147  func (c *Client) sendHTTP(ctx context.Context, op *requestOp, msg interface{}) error {
   148  	hc := c.writeConn.(*httpConn)
   149  	respBody, err := hc.doRequest(ctx, msg)
   150  	if err != nil {
   151  		return err
   152  	}
   153  	defer respBody.Close()
   154  
   155  	var respmsg jsonrpcMessage
   156  	if err := json.NewDecoder(respBody).Decode(&respmsg); err != nil {
   157  		return err
   158  	}
   159  	op.resp <- &respmsg
   160  	return nil
   161  }
   162  
   163  func (c *Client) sendBatchHTTP(ctx context.Context, op *requestOp, msgs []*jsonrpcMessage) error {
   164  	hc := c.writeConn.(*httpConn)
   165  	respBody, err := hc.doRequest(ctx, msgs)
   166  	if err != nil {
   167  		return err
   168  	}
   169  	defer respBody.Close()
   170  	var respmsgs []jsonrpcMessage
   171  	if err := json.NewDecoder(respBody).Decode(&respmsgs); err != nil {
   172  		return err
   173  	}
   174  	if len(respmsgs) != len(msgs) {
   175  		return fmt.Errorf("batch has %d requests but response has %d: %w", len(msgs), len(respmsgs), ErrBadResult)
   176  	}
   177  	for i := 0; i < len(respmsgs); i++ {
   178  		op.resp <- &respmsgs[i]
   179  	}
   180  	return nil
   181  }
   182  
   183  type nopReadCloser struct {
   184  	io.Reader
   185  }
   186  
   187  func (nopReadCloser) Close() error { return nil }
   188  
   189  func (hc *httpConn) doRequest(ctx context.Context, msg interface{}) (io.ReadCloser, error) {
   190  	body, err := json.Marshal(msg)
   191  	if err != nil {
   192  		return nil, err
   193  	}
   194  	req, err := http.NewRequestWithContext(ctx, "POST", hc.url, io.NopCloser(bytes.NewReader(body)))
   195  	if err != nil {
   196  		return nil, err
   197  	}
   198  	req.ContentLength = int64(len(body))
   199  	req.GetBody = func() (io.ReadCloser, error) { return io.NopCloser(bytes.NewReader(body)), nil }
   200  
   201  	req.GetBody = func() (io.ReadCloser, error) {
   202  		return nopReadCloser{bytes.NewReader(body)}, nil
   203  	}
   204  
   205  	// set headers
   206  	hc.mu.Lock()
   207  	req.Header = hc.headers.Clone()
   208  	hc.mu.Unlock()
   209  
   210  	// do request
   211  	resp, err := hc.client.Do(req)
   212  	if err != nil {
   213  		return nil, err
   214  	}
   215  	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
   216  		var buf bytes.Buffer
   217  		var body []byte
   218  		if _, err := buf.ReadFrom(resp.Body); err == nil {
   219  			body = buf.Bytes()
   220  		}
   221  
   222  		return nil, HTTPError{
   223  			Status:     resp.Status,
   224  			StatusCode: resp.StatusCode,
   225  			Body:       body,
   226  		}
   227  	}
   228  	return resp.Body, nil
   229  }