github.com/shyftnetwork/go-empyrean@v1.8.3-0.20191127201940-fbfca9338f04/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 "io/ioutil" 27 "mime" 28 "net" 29 "net/http" 30 "strings" 31 "sync" 32 "time" 33 34 "github.com/ShyftNetwork/go-empyrean/log" 35 "github.com/rs/cors" 36 ) 37 38 const ( 39 maxRequestContentLength = 1024 * 512 40 ) 41 42 var ( 43 // https://www.jsonrpc.org/historical/json-rpc-over-http.html#id13 44 acceptedContentTypes = []string{"application/json", "application/json-rpc", "application/jsonrequest"} 45 contentType = acceptedContentTypes[0] 46 nullAddr, _ = net.ResolveTCPAddr("tcp", "127.0.0.1:0") 47 ) 48 49 type httpConn struct { 50 client *http.Client 51 req *http.Request 52 closeOnce sync.Once 53 closed chan struct{} 54 } 55 56 // httpConn is treated specially by Client. 57 func (hc *httpConn) LocalAddr() net.Addr { return nullAddr } 58 func (hc *httpConn) RemoteAddr() net.Addr { return nullAddr } 59 func (hc *httpConn) SetReadDeadline(time.Time) error { return nil } 60 func (hc *httpConn) SetWriteDeadline(time.Time) error { return nil } 61 func (hc *httpConn) SetDeadline(time.Time) error { return nil } 62 func (hc *httpConn) Write([]byte) (int, error) { panic("Write called") } 63 64 func (hc *httpConn) Read(b []byte) (int, error) { 65 <-hc.closed 66 return 0, io.EOF 67 } 68 69 func (hc *httpConn) Close() error { 70 hc.closeOnce.Do(func() { close(hc.closed) }) 71 return nil 72 } 73 74 // HTTPTimeouts represents the configuration params for the HTTP RPC server. 75 type HTTPTimeouts struct { 76 // ReadTimeout is the maximum duration for reading the entire 77 // request, including the body. 78 // 79 // Because ReadTimeout does not let Handlers make per-request 80 // decisions on each request body's acceptable deadline or 81 // upload rate, most users will prefer to use 82 // ReadHeaderTimeout. It is valid to use them both. 83 ReadTimeout time.Duration 84 85 // WriteTimeout is the maximum duration before timing out 86 // writes of the response. It is reset whenever a new 87 // request's header is read. Like ReadTimeout, it does not 88 // let Handlers make decisions on a per-request basis. 89 WriteTimeout time.Duration 90 91 // IdleTimeout is the maximum amount of time to wait for the 92 // next request when keep-alives are enabled. If IdleTimeout 93 // is zero, the value of ReadTimeout is used. If both are 94 // zero, ReadHeaderTimeout is used. 95 IdleTimeout time.Duration 96 } 97 98 // DefaultHTTPTimeouts represents the default timeout values used if further 99 // configuration is not provided. 100 var DefaultHTTPTimeouts = HTTPTimeouts{ 101 ReadTimeout: 30 * time.Second, 102 WriteTimeout: 30 * time.Second, 103 IdleTimeout: 120 * time.Second, 104 } 105 106 // DialHTTPWithClient creates a new RPC client that connects to an RPC server over HTTP 107 // using the provided HTTP Client. 108 func DialHTTPWithClient(endpoint string, client *http.Client) (*Client, error) { 109 req, err := http.NewRequest(http.MethodPost, endpoint, nil) 110 if err != nil { 111 return nil, err 112 } 113 req.Header.Set("Content-Type", contentType) 114 req.Header.Set("Accept", contentType) 115 116 initctx := context.Background() 117 return newClient(initctx, func(context.Context) (net.Conn, error) { 118 return &httpConn{client: client, req: req, closed: make(chan struct{})}, nil 119 }) 120 } 121 122 // DialHTTP creates a new RPC client that connects to an RPC server over HTTP. 123 func DialHTTP(endpoint string) (*Client, error) { 124 return DialHTTPWithClient(endpoint, new(http.Client)) 125 } 126 127 func (c *Client) sendHTTP(ctx context.Context, op *requestOp, msg interface{}) error { 128 hc := c.writeConn.(*httpConn) 129 respBody, err := hc.doRequest(ctx, msg) 130 if respBody != nil { 131 defer respBody.Close() 132 } 133 134 if err != nil { 135 if respBody != nil { 136 buf := new(bytes.Buffer) 137 if _, err2 := buf.ReadFrom(respBody); err2 == nil { 138 return fmt.Errorf("%v %v", err, buf.String()) 139 } 140 } 141 return err 142 } 143 var respmsg jsonrpcMessage 144 if err := json.NewDecoder(respBody).Decode(&respmsg); err != nil { 145 return err 146 } 147 op.resp <- &respmsg 148 return nil 149 } 150 151 func (c *Client) sendBatchHTTP(ctx context.Context, op *requestOp, msgs []*jsonrpcMessage) error { 152 hc := c.writeConn.(*httpConn) 153 respBody, err := hc.doRequest(ctx, msgs) 154 if err != nil { 155 return err 156 } 157 defer respBody.Close() 158 var respmsgs []jsonrpcMessage 159 if err := json.NewDecoder(respBody).Decode(&respmsgs); err != nil { 160 return err 161 } 162 for i := 0; i < len(respmsgs); i++ { 163 op.resp <- &respmsgs[i] 164 } 165 return nil 166 } 167 168 func (hc *httpConn) doRequest(ctx context.Context, msg interface{}) (io.ReadCloser, error) { 169 body, err := json.Marshal(msg) 170 if err != nil { 171 return nil, err 172 } 173 req := hc.req.WithContext(ctx) 174 req.Body = ioutil.NopCloser(bytes.NewReader(body)) 175 req.ContentLength = int64(len(body)) 176 177 resp, err := hc.client.Do(req) 178 if err != nil { 179 return nil, err 180 } 181 if resp.StatusCode < 200 || resp.StatusCode >= 300 { 182 return resp.Body, errors.New(resp.Status) 183 } 184 return resp.Body, nil 185 } 186 187 // httpReadWriteNopCloser wraps a io.Reader and io.Writer with a NOP Close method. 188 type httpReadWriteNopCloser struct { 189 io.Reader 190 io.Writer 191 } 192 193 // Close does nothing and returns always nil 194 func (t *httpReadWriteNopCloser) Close() error { 195 return nil 196 } 197 198 // NewHTTPServer creates a new HTTP RPC server around an API provider. 199 // 200 // Deprecated: Server implements http.Handler 201 func NewHTTPServer(cors []string, vhosts []string, timeouts HTTPTimeouts, srv *Server) *http.Server { 202 // Wrap the CORS-handler within a host-handler 203 handler := newCorsHandler(srv, cors) 204 handler = newVHostHandler(vhosts, handler) 205 206 // Make sure timeout values are meaningful 207 if timeouts.ReadTimeout < time.Second { 208 log.Warn("Sanitizing invalid HTTP read timeout", "provided", timeouts.ReadTimeout, "updated", DefaultHTTPTimeouts.ReadTimeout) 209 timeouts.ReadTimeout = DefaultHTTPTimeouts.ReadTimeout 210 } 211 if timeouts.WriteTimeout < time.Second { 212 log.Warn("Sanitizing invalid HTTP write timeout", "provided", timeouts.WriteTimeout, "updated", DefaultHTTPTimeouts.WriteTimeout) 213 timeouts.WriteTimeout = DefaultHTTPTimeouts.WriteTimeout 214 } 215 if timeouts.IdleTimeout < time.Second { 216 log.Warn("Sanitizing invalid HTTP idle timeout", "provided", timeouts.IdleTimeout, "updated", DefaultHTTPTimeouts.IdleTimeout) 217 timeouts.IdleTimeout = DefaultHTTPTimeouts.IdleTimeout 218 } 219 // Bundle and start the HTTP server 220 return &http.Server{ 221 Handler: handler, 222 ReadTimeout: timeouts.ReadTimeout, 223 WriteTimeout: timeouts.WriteTimeout, 224 IdleTimeout: timeouts.IdleTimeout, 225 } 226 } 227 228 // ServeHTTP serves JSON-RPC requests over HTTP. 229 func (srv *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { 230 // Permit dumb empty requests for remote health-checks (AWS) 231 if r.Method == http.MethodGet && r.ContentLength == 0 && r.URL.RawQuery == "" { 232 return 233 } 234 if code, err := validateRequest(r); err != nil { 235 http.Error(w, err.Error(), code) 236 return 237 } 238 // All checks passed, create a codec that reads direct from the request body 239 // untilEOF and writes the response to w and order the server to process a 240 // single request. 241 ctx := r.Context() 242 ctx = context.WithValue(ctx, "remote", r.RemoteAddr) 243 ctx = context.WithValue(ctx, "scheme", r.Proto) 244 ctx = context.WithValue(ctx, "local", r.Host) 245 if ua := r.Header.Get("User-Agent"); ua != "" { 246 ctx = context.WithValue(ctx, "User-Agent", ua) 247 } 248 if origin := r.Header.Get("Origin"); origin != "" { 249 ctx = context.WithValue(ctx, "Origin", origin) 250 } 251 252 body := io.LimitReader(r.Body, maxRequestContentLength) 253 codec := NewJSONCodec(&httpReadWriteNopCloser{body, w}) 254 defer codec.Close() 255 256 w.Header().Set("content-type", contentType) 257 srv.ServeSingleRequest(ctx, codec, OptionMethodInvocation) 258 } 259 260 // validateRequest returns a non-zero response code and error message if the 261 // request is invalid. 262 func validateRequest(r *http.Request) (int, error) { 263 if r.Method == http.MethodPut || r.Method == http.MethodDelete { 264 return http.StatusMethodNotAllowed, errors.New("method not allowed") 265 } 266 if r.ContentLength > maxRequestContentLength { 267 err := fmt.Errorf("content length too large (%d>%d)", r.ContentLength, maxRequestContentLength) 268 return http.StatusRequestEntityTooLarge, err 269 } 270 // Allow OPTIONS (regardless of content-type) 271 if r.Method == http.MethodOptions { 272 return 0, nil 273 } 274 // Check content-type 275 if mt, _, err := mime.ParseMediaType(r.Header.Get("content-type")); err == nil { 276 for _, accepted := range acceptedContentTypes { 277 if accepted == mt { 278 return 0, nil 279 } 280 } 281 } 282 // Invalid content-type 283 err := fmt.Errorf("invalid content type, only %s is supported", contentType) 284 return http.StatusUnsupportedMediaType, err 285 } 286 287 func newCorsHandler(srv *Server, allowedOrigins []string) http.Handler { 288 // disable CORS support if user has not specified a custom CORS configuration 289 if len(allowedOrigins) == 0 { 290 return srv 291 } 292 c := cors.New(cors.Options{ 293 AllowedOrigins: allowedOrigins, 294 AllowedMethods: []string{http.MethodPost, http.MethodGet}, 295 MaxAge: 600, 296 AllowedHeaders: []string{"*"}, 297 }) 298 return c.Handler(srv) 299 } 300 301 // virtualHostHandler is a handler which validates the Host-header of incoming requests. 302 // The virtualHostHandler can prevent DNS rebinding attacks, which do not utilize CORS-headers, 303 // since they do in-domain requests against the RPC api. Instead, we can see on the Host-header 304 // which domain was used, and validate that against a whitelist. 305 type virtualHostHandler struct { 306 vhosts map[string]struct{} 307 next http.Handler 308 } 309 310 // ServeHTTP serves JSON-RPC requests over HTTP, implements http.Handler 311 func (h *virtualHostHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 312 // if r.Host is not set, we can continue serving since a browser would set the Host header 313 if r.Host == "" { 314 h.next.ServeHTTP(w, r) 315 return 316 } 317 host, _, err := net.SplitHostPort(r.Host) 318 if err != nil { 319 // Either invalid (too many colons) or no port specified 320 host = r.Host 321 } 322 if ipAddr := net.ParseIP(host); ipAddr != nil { 323 // It's an IP address, we can serve that 324 h.next.ServeHTTP(w, r) 325 return 326 327 } 328 // Not an ip address, but a hostname. Need to validate 329 if _, exist := h.vhosts["*"]; exist { 330 h.next.ServeHTTP(w, r) 331 return 332 } 333 if _, exist := h.vhosts[host]; exist { 334 h.next.ServeHTTP(w, r) 335 return 336 } 337 http.Error(w, "invalid host specified", http.StatusForbidden) 338 } 339 340 func newVHostHandler(vhosts []string, next http.Handler) http.Handler { 341 vhostMap := make(map[string]struct{}) 342 for _, allowedHost := range vhosts { 343 vhostMap[strings.ToLower(allowedHost)] = struct{}{} 344 } 345 return &virtualHostHandler{vhostMap, next} 346 }