gitlab.com/flarenetwork/coreth@v0.1.1/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 "io/ioutil" 37 "mime" 38 "net/http" 39 "net/url" 40 "sync" 41 "time" 42 ) 43 44 const ( 45 maxRequestContentLength = 1024 * 1024 * 5 46 contentType = "application/json" 47 ) 48 49 // https://www.jsonrpc.org/historical/json-rpc-over-http.html#id13 50 var acceptedContentTypes = []string{contentType, "application/json-rpc", "application/jsonrequest"} 51 52 type httpConn struct { 53 client *http.Client 54 url string 55 closeOnce sync.Once 56 closeCh chan interface{} 57 mu sync.Mutex // protects headers 58 headers http.Header 59 } 60 61 // httpConn is treated specially by Client. 62 func (hc *httpConn) writeJSON(ctx context.Context, val interface{}) error { 63 return hc.writeJSONSkipDeadline(ctx, val, false) 64 } 65 66 func (hc *httpConn) writeJSONSkipDeadline(context.Context, interface{}, bool) error { 67 panic("writeJSON called on httpConn") 68 } 69 70 func (hc *httpConn) remoteAddr() string { 71 return hc.url 72 } 73 74 func (hc *httpConn) readBatch() ([]*jsonrpcMessage, bool, error) { 75 <-hc.closeCh 76 return nil, false, io.EOF 77 } 78 79 func (hc *httpConn) close() { 80 hc.closeOnce.Do(func() { close(hc.closeCh) }) 81 } 82 83 func (hc *httpConn) closed() <-chan interface{} { 84 return hc.closeCh 85 } 86 87 // HTTPTimeouts represents the configuration params for the HTTP RPC server. 88 type HTTPTimeouts struct { 89 // ReadTimeout is the maximum duration for reading the entire 90 // request, including the body. 91 // 92 // Because ReadTimeout does not let Handlers make per-request 93 // decisions on each request body's acceptable deadline or 94 // upload rate, most users will prefer to use 95 // ReadHeaderTimeout. It is valid to use them both. 96 ReadTimeout time.Duration 97 98 // WriteTimeout is the maximum duration before timing out 99 // writes of the response. It is reset whenever a new 100 // request's header is read. Like ReadTimeout, it does not 101 // let Handlers make decisions on a per-request basis. 102 WriteTimeout time.Duration 103 104 // IdleTimeout is the maximum amount of time to wait for the 105 // next request when keep-alives are enabled. If IdleTimeout 106 // is zero, the value of ReadTimeout is used. If both are 107 // zero, ReadHeaderTimeout is used. 108 IdleTimeout time.Duration 109 } 110 111 // DefaultHTTPTimeouts represents the default timeout values used if further 112 // configuration is not provided. 113 var DefaultHTTPTimeouts = HTTPTimeouts{ 114 ReadTimeout: 30 * time.Second, 115 WriteTimeout: 30 * time.Second, 116 IdleTimeout: 120 * time.Second, 117 } 118 119 // DialHTTPWithClient creates a new RPC client that connects to an RPC server over HTTP 120 // using the provided HTTP Client. 121 func DialHTTPWithClient(endpoint string, client *http.Client) (*Client, error) { 122 // Sanity check URL so we don't end up with a client that will fail every request. 123 _, err := url.Parse(endpoint) 124 if err != nil { 125 return nil, err 126 } 127 128 initctx := context.Background() 129 headers := make(http.Header, 2) 130 headers.Set("accept", contentType) 131 headers.Set("content-type", contentType) 132 return newClient(initctx, func(context.Context) (ServerCodec, error) { 133 hc := &httpConn{ 134 client: client, 135 headers: headers, 136 url: endpoint, 137 closeCh: make(chan interface{}), 138 } 139 return hc, nil 140 }) 141 } 142 143 // DialHTTP creates a new RPC client that connects to an RPC server over HTTP. 144 func DialHTTP(endpoint string) (*Client, error) { 145 return DialHTTPWithClient(endpoint, new(http.Client)) 146 } 147 148 func (c *Client) sendHTTP(ctx context.Context, op *requestOp, msg interface{}) error { 149 hc := c.writeConn.(*httpConn) 150 respBody, err := hc.doRequest(ctx, msg) 151 if err != nil { 152 return err 153 } 154 defer respBody.Close() 155 156 var respmsg jsonrpcMessage 157 if err := json.NewDecoder(respBody).Decode(&respmsg); err != nil { 158 return err 159 } 160 op.resp <- &respmsg 161 return nil 162 } 163 164 func (c *Client) sendBatchHTTP(ctx context.Context, op *requestOp, msgs []*jsonrpcMessage) error { 165 hc := c.writeConn.(*httpConn) 166 respBody, err := hc.doRequest(ctx, msgs) 167 if err != nil { 168 return err 169 } 170 defer respBody.Close() 171 var respmsgs []jsonrpcMessage 172 if err := json.NewDecoder(respBody).Decode(&respmsgs); err != nil { 173 return err 174 } 175 for i := 0; i < len(respmsgs); i++ { 176 op.resp <- &respmsgs[i] 177 } 178 return nil 179 } 180 181 func (hc *httpConn) doRequest(ctx context.Context, msg interface{}) (io.ReadCloser, error) { 182 body, err := json.Marshal(msg) 183 if err != nil { 184 return nil, err 185 } 186 req, err := http.NewRequestWithContext(ctx, "POST", hc.url, ioutil.NopCloser(bytes.NewReader(body))) 187 if err != nil { 188 return nil, err 189 } 190 req.ContentLength = int64(len(body)) 191 192 // set headers 193 hc.mu.Lock() 194 req.Header = hc.headers.Clone() 195 hc.mu.Unlock() 196 197 // do request 198 resp, err := hc.client.Do(req) 199 if err != nil { 200 return nil, err 201 } 202 if resp.StatusCode < 200 || resp.StatusCode >= 300 { 203 var buf bytes.Buffer 204 var body []byte 205 if _, err := buf.ReadFrom(resp.Body); err == nil { 206 body = buf.Bytes() 207 } 208 209 return nil, HTTPError{ 210 Status: resp.Status, 211 StatusCode: resp.StatusCode, 212 Body: body, 213 } 214 } 215 return resp.Body, nil 216 } 217 218 // httpServerConn turns a HTTP connection into a Conn. 219 type httpServerConn struct { 220 io.Reader 221 io.Writer 222 r *http.Request 223 } 224 225 func newHTTPServerConn(r *http.Request, w http.ResponseWriter) ServerCodec { 226 body := io.LimitReader(r.Body, maxRequestContentLength) 227 conn := &httpServerConn{Reader: body, Writer: w, r: r} 228 return NewCodec(conn) 229 } 230 231 // Close does nothing and always returns nil. 232 func (t *httpServerConn) Close() error { return nil } 233 234 // RemoteAddr returns the peer address of the underlying connection. 235 func (t *httpServerConn) RemoteAddr() string { 236 return t.r.RemoteAddr 237 } 238 239 // SetWriteDeadline does nothing and always returns nil. 240 func (t *httpServerConn) SetWriteDeadline(time.Time) error { return nil } 241 242 // ServeHTTP serves JSON-RPC requests over HTTP. 243 func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { 244 // Permit dumb empty requests for remote health-checks (AWS) 245 if r.Method == http.MethodGet && r.ContentLength == 0 && r.URL.RawQuery == "" { 246 w.WriteHeader(http.StatusOK) 247 return 248 } 249 if code, err := validateRequest(r); err != nil { 250 http.Error(w, err.Error(), code) 251 return 252 } 253 // All checks passed, create a codec that reads directly from the request body 254 // until EOF, writes the response to w, and orders the server to process a 255 // single request. 256 ctx := r.Context() 257 ctx = context.WithValue(ctx, "remote", r.RemoteAddr) 258 ctx = context.WithValue(ctx, "scheme", r.Proto) 259 ctx = context.WithValue(ctx, "local", r.Host) 260 if ua := r.Header.Get("User-Agent"); ua != "" { 261 ctx = context.WithValue(ctx, "User-Agent", ua) 262 } 263 if origin := r.Header.Get("Origin"); origin != "" { 264 ctx = context.WithValue(ctx, "Origin", origin) 265 } 266 267 w.Header().Set("content-type", contentType) 268 codec := newHTTPServerConn(r, w) 269 defer codec.close() 270 s.serveSingleRequest(ctx, codec) 271 } 272 273 // validateRequest returns a non-zero response code and error message if the 274 // request is invalid. 275 func validateRequest(r *http.Request) (int, error) { 276 if r.Method == http.MethodPut || r.Method == http.MethodDelete { 277 return http.StatusMethodNotAllowed, errors.New("method not allowed") 278 } 279 if r.ContentLength > maxRequestContentLength { 280 err := fmt.Errorf("content length too large (%d>%d)", r.ContentLength, maxRequestContentLength) 281 return http.StatusRequestEntityTooLarge, err 282 } 283 // Allow OPTIONS (regardless of content-type) 284 if r.Method == http.MethodOptions { 285 return 0, nil 286 } 287 // Check content-type 288 if mt, _, err := mime.ParseMediaType(r.Header.Get("content-type")); err == nil { 289 for _, accepted := range acceptedContentTypes { 290 if accepted == mt { 291 return 0, nil 292 } 293 } 294 } 295 // Invalid content-type 296 err := fmt.Errorf("invalid content type, only %s is supported", contentType) 297 return http.StatusUnsupportedMediaType, err 298 }