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 }