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