github.com/arieschain/arieschain@v0.0.0-20191023063405-37c074544356/rpc/http.go (about) 1 package rpc 2 3 import ( 4 "bytes" 5 "context" 6 "encoding/json" 7 "errors" 8 "fmt" 9 "io" 10 "io/ioutil" 11 "mime" 12 "net" 13 "net/http" 14 "strings" 15 "sync" 16 "time" 17 18 "github.com/rs/cors" 19 ) 20 21 const ( 22 contentType = "application/json" 23 maxRequestContentLength = 1024 * 128 24 ) 25 26 var nullAddr, _ = net.ResolveTCPAddr("tcp", "127.0.0.1:0") 27 28 type httpConn struct { 29 client *http.Client 30 req *http.Request 31 closeOnce sync.Once 32 closed chan struct{} 33 } 34 35 // httpConn is treated specially by Client. 36 func (hc *httpConn) LocalAddr() net.Addr { return nullAddr } 37 func (hc *httpConn) RemoteAddr() net.Addr { return nullAddr } 38 func (hc *httpConn) SetReadDeadline(time.Time) error { return nil } 39 func (hc *httpConn) SetWriteDeadline(time.Time) error { return nil } 40 func (hc *httpConn) SetDeadline(time.Time) error { return nil } 41 func (hc *httpConn) Write([]byte) (int, error) { panic("Write called") } 42 43 func (hc *httpConn) Read(b []byte) (int, error) { 44 <-hc.closed 45 return 0, io.EOF 46 } 47 48 func (hc *httpConn) Close() error { 49 hc.closeOnce.Do(func() { close(hc.closed) }) 50 return nil 51 } 52 53 // DialHTTPWithClient creates a new RPC client that connects to an RPC server over HTTP 54 // using the provided HTTP Client. 55 func DialHTTPWithClient(endpoint string, client *http.Client) (*Client, error) { 56 req, err := http.NewRequest(http.MethodPost, endpoint, nil) 57 if err != nil { 58 return nil, err 59 } 60 req.Header.Set("Content-Type", contentType) 61 req.Header.Set("Accept", contentType) 62 63 initctx := context.Background() 64 return newClient(initctx, func(context.Context) (net.Conn, error) { 65 return &httpConn{client: client, req: req, closed: make(chan struct{})}, nil 66 }) 67 } 68 69 // DialHTTP creates a new RPC client that connects to an RPC server over HTTP. 70 func DialHTTP(endpoint string) (*Client, error) { 71 return DialHTTPWithClient(endpoint, new(http.Client)) 72 } 73 74 func (c *Client) sendHTTP(ctx context.Context, op *requestOp, msg interface{}) error { 75 hc := c.writeConn.(*httpConn) 76 respBody, err := hc.doRequest(ctx, msg) 77 if respBody != nil { 78 defer respBody.Close() 79 } 80 81 if err != nil { 82 if respBody != nil { 83 buf := new(bytes.Buffer) 84 if _, err2 := buf.ReadFrom(respBody); err2 == nil { 85 return fmt.Errorf("%v %v", err, buf.String()) 86 } 87 } 88 return err 89 } 90 var respmsg jsonrpcMessage 91 if err := json.NewDecoder(respBody).Decode(&respmsg); err != nil { 92 return err 93 } 94 op.resp <- &respmsg 95 return nil 96 } 97 98 func (c *Client) sendBatchHTTP(ctx context.Context, op *requestOp, msgs []*jsonrpcMessage) error { 99 hc := c.writeConn.(*httpConn) 100 respBody, err := hc.doRequest(ctx, msgs) 101 if err != nil { 102 return err 103 } 104 defer respBody.Close() 105 var respmsgs []jsonrpcMessage 106 if err := json.NewDecoder(respBody).Decode(&respmsgs); err != nil { 107 return err 108 } 109 for i := 0; i < len(respmsgs); i++ { 110 op.resp <- &respmsgs[i] 111 } 112 return nil 113 } 114 115 func (hc *httpConn) doRequest(ctx context.Context, msg interface{}) (io.ReadCloser, error) { 116 body, err := json.Marshal(msg) 117 if err != nil { 118 return nil, err 119 } 120 req := hc.req.WithContext(ctx) 121 req.Body = ioutil.NopCloser(bytes.NewReader(body)) 122 req.ContentLength = int64(len(body)) 123 124 resp, err := hc.client.Do(req) 125 if err != nil { 126 return nil, err 127 } 128 if resp.StatusCode < 200 || resp.StatusCode >= 300 { 129 return resp.Body, errors.New(resp.Status) 130 } 131 return resp.Body, nil 132 } 133 134 // httpReadWriteNopCloser wraps a io.Reader and io.Writer with a NOP Close method. 135 type httpReadWriteNopCloser struct { 136 io.Reader 137 io.Writer 138 } 139 140 // Close does nothing and returns always nil 141 func (t *httpReadWriteNopCloser) Close() error { 142 return nil 143 } 144 145 // NewHTTPServer creates a new HTTP RPC server around an API provider. 146 // 147 // Deprecated: Server implements http.Handler 148 func NewHTTPServer(cors []string, vhosts []string, srv *Server) *http.Server { 149 // Wrap the CORS-handler within a host-handler 150 handler := newCorsHandler(srv, cors) 151 handler = newVHostHandler(vhosts, handler) 152 return &http.Server{Handler: handler} 153 } 154 155 // ServeHTTP serves JSON-RPC requests over HTTP. 156 func (srv *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { 157 // Permit dumb empty requests for remote health-checks (AWS) 158 if r.Method == http.MethodGet && r.ContentLength == 0 && r.URL.RawQuery == "" { 159 return 160 } 161 if code, err := validateRequest(r); err != nil { 162 http.Error(w, err.Error(), code) 163 return 164 } 165 // All checks passed, create a codec that reads direct from the request body 166 // untilEOF and writes the response to w and order the server to process a 167 // single request. 168 ctx := context.Background() 169 ctx = context.WithValue(ctx, "remote", r.RemoteAddr) 170 ctx = context.WithValue(ctx, "scheme", r.Proto) 171 ctx = context.WithValue(ctx, "local", r.Host) 172 173 body := io.LimitReader(r.Body, maxRequestContentLength) 174 codec := NewJSONCodec(&httpReadWriteNopCloser{body, w}) 175 defer codec.Close() 176 177 w.Header().Set("content-type", contentType) 178 srv.ServeSingleRequest(codec, OptionMethodInvocation, ctx) 179 } 180 181 // validateRequest returns a non-zero response code and error message if the 182 // request is invalid. 183 func validateRequest(r *http.Request) (int, error) { 184 if r.Method == http.MethodPut || r.Method == http.MethodDelete { 185 return http.StatusMethodNotAllowed, errors.New("method not allowed") 186 } 187 if r.ContentLength > maxRequestContentLength { 188 err := fmt.Errorf("content length too large (%d>%d)", r.ContentLength, maxRequestContentLength) 189 return http.StatusRequestEntityTooLarge, err 190 } 191 mt, _, err := mime.ParseMediaType(r.Header.Get("content-type")) 192 if r.Method != http.MethodOptions && (err != nil || mt != contentType) { 193 err := fmt.Errorf("invalid content type, only %s is supported", contentType) 194 return http.StatusUnsupportedMediaType, err 195 } 196 return 0, nil 197 } 198 199 func newCorsHandler(srv *Server, allowedOrigins []string) http.Handler { 200 // disable CORS support if user has not specified a custom CORS configuration 201 if len(allowedOrigins) == 0 { 202 return srv 203 } 204 c := cors.New(cors.Options{ 205 AllowedOrigins: allowedOrigins, 206 AllowedMethods: []string{http.MethodPost, http.MethodGet}, 207 MaxAge: 600, 208 AllowedHeaders: []string{"*"}, 209 }) 210 return c.Handler(srv) 211 } 212 213 // virtualHostHandler is a handler which validates the Host-header of incoming requests. 214 // The virtualHostHandler can prevent DNS rebinding attacks, which do not utilize CORS-headers, 215 // since they do in-domain requests against the RPC api. Instead, we can see on the Host-header 216 // which domain was used, and validate that against a whitelist. 217 type virtualHostHandler struct { 218 vhosts map[string]struct{} 219 next http.Handler 220 } 221 222 // ServeHTTP serves JSON-RPC requests over HTTP, implements http.Handler 223 func (h *virtualHostHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { 224 // if r.Host is not set, we can continue serving since a browser would set the Host header 225 if r.Host == "" { 226 h.next.ServeHTTP(w, r) 227 return 228 } 229 host, _, err := net.SplitHostPort(r.Host) 230 if err != nil { 231 // Either invalid (too many colons) or no port specified 232 host = r.Host 233 } 234 if ipAddr := net.ParseIP(host); ipAddr != nil { 235 // It's an IP address, we can serve that 236 h.next.ServeHTTP(w, r) 237 return 238 239 } 240 // Not an ip address, but a hostname. Need to validate 241 if _, exist := h.vhosts["*"]; exist { 242 h.next.ServeHTTP(w, r) 243 return 244 } 245 if _, exist := h.vhosts[host]; exist { 246 h.next.ServeHTTP(w, r) 247 return 248 } 249 http.Error(w, "invalid host specified", http.StatusForbidden) 250 } 251 252 func newVHostHandler(vhosts []string, next http.Handler) http.Handler { 253 vhostMap := make(map[string]struct{}) 254 for _, allowedHost := range vhosts { 255 vhostMap[strings.ToLower(allowedHost)] = struct{}{} 256 } 257 return &virtualHostHandler{vhostMap, next} 258 }