github.com/amazechain/amc@v0.1.3/modules/rpc/jsonrpc/http.go (about) 1 // Copyright 2022 The AmazeChain Authors 2 // This file is part of the AmazeChain library. 3 // 4 // The AmazeChain 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 AmazeChain 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 AmazeChain library. If not, see <http://www.gnu.org/licenses/>. 16 17 package jsonrpc 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 var acceptedContentTypes = []string{contentType, "application/json-rpc", "application/jsonrequest"} 40 41 type httpConn struct { 42 client *http.Client 43 url string 44 closeOnce sync.Once 45 closeCh chan interface{} 46 mu sync.Mutex 47 headers http.Header 48 } 49 50 func (hc *httpConn) writeJSON(context.Context, interface{}) error { 51 panic("writeJSON called on httpConn") 52 } 53 54 func (hc *httpConn) remoteAddr() string { 55 return hc.url 56 } 57 58 func (hc *httpConn) readBatch() ([]*jsonrpcMessage, bool, error) { 59 <-hc.closeCh 60 return nil, false, io.EOF 61 } 62 63 func (hc *httpConn) close() { 64 hc.closeOnce.Do(func() { close(hc.closeCh) }) 65 } 66 67 func (hc *httpConn) closed() <-chan interface{} { 68 return hc.closeCh 69 } 70 71 func DialHTTPWithClient(endpoint string, client *http.Client) (*Client, error) { 72 _, err := url.Parse(endpoint) 73 if err != nil { 74 return nil, err 75 } 76 77 initctx := context.Background() 78 headers := make(http.Header, 2) 79 headers.Set("accept", contentType) 80 headers.Set("content-type", contentType) 81 return newClient(initctx, func(context.Context) (ServerCodec, error) { 82 hc := &httpConn{ 83 client: client, 84 headers: headers, 85 url: endpoint, 86 closeCh: make(chan interface{}), 87 } 88 return hc, nil 89 }) 90 } 91 92 func DialHTTP(endpoint string) (*Client, error) { 93 return DialHTTPWithClient(endpoint, new(http.Client)) 94 } 95 96 func (c *Client) sendHTTP(ctx context.Context, op *requestOp, msg interface{}) error { 97 hc := c.writeConn.(*httpConn) 98 respBody, err := hc.doRequest(ctx, msg) 99 if err != nil { 100 return err 101 } 102 defer respBody.Close() 103 104 var respmsg jsonrpcMessage 105 if err := json.NewDecoder(respBody).Decode(&respmsg); err != nil { 106 return err 107 } 108 op.resp <- &respmsg 109 return nil 110 } 111 112 func (c *Client) sendBatchHTTP(ctx context.Context, op *requestOp, msgs []*jsonrpcMessage) error { 113 hc := c.writeConn.(*httpConn) 114 respBody, err := hc.doRequest(ctx, msgs) 115 if err != nil { 116 return err 117 } 118 defer respBody.Close() 119 var respmsgs []jsonrpcMessage 120 if err := json.NewDecoder(respBody).Decode(&respmsgs); err != nil { 121 return err 122 } 123 for i := 0; i < len(respmsgs); i++ { 124 op.resp <- &respmsgs[i] 125 } 126 return nil 127 } 128 129 func (hc *httpConn) doRequest(ctx context.Context, msg interface{}) (io.ReadCloser, error) { 130 body, err := json.Marshal(msg) 131 if err != nil { 132 return nil, err 133 } 134 req, err := http.NewRequestWithContext(ctx, "POST", hc.url, ioutil.NopCloser(bytes.NewReader(body))) 135 if err != nil { 136 return nil, err 137 } 138 req.ContentLength = int64(len(body)) 139 140 hc.mu.Lock() 141 req.Header = hc.headers.Clone() 142 hc.mu.Unlock() 143 144 resp, err := hc.client.Do(req) 145 if err != nil { 146 return nil, err 147 } 148 if resp.StatusCode < 200 || resp.StatusCode >= 300 { 149 var buf bytes.Buffer 150 var body []byte 151 if _, err := buf.ReadFrom(resp.Body); err == nil { 152 body = buf.Bytes() 153 } 154 155 return nil, HTTPError{ 156 Status: resp.Status, 157 StatusCode: resp.StatusCode, 158 Body: body, 159 } 160 } 161 return resp.Body, nil 162 } 163 164 type httpServerConn struct { 165 io.Reader 166 io.Writer 167 r *http.Request 168 } 169 170 func newHTTPServerConn(r *http.Request, w http.ResponseWriter) ServerCodec { 171 body := io.LimitReader(r.Body, maxRequestContentLength) 172 conn := &httpServerConn{Reader: body, Writer: w, r: r} 173 return NewCodec(conn) 174 } 175 176 func (t *httpServerConn) Close() error { return nil } 177 178 func (t *httpServerConn) RemoteAddr() string { 179 return t.r.RemoteAddr 180 } 181 182 func (t *httpServerConn) SetWriteDeadline(time.Time) error { return nil } 183 184 func (s *Server) ServeHTTP(w http.ResponseWriter, r *http.Request) { 185 if r.Method == http.MethodGet && r.ContentLength == 0 && r.URL.RawQuery == "" { 186 w.WriteHeader(http.StatusOK) 187 return 188 } 189 if code, err := validateRequest(r); err != nil { 190 http.Error(w, err.Error(), code) 191 return 192 } 193 ctx := r.Context() 194 ctx = context.WithValue(ctx, "remote", r.RemoteAddr) 195 ctx = context.WithValue(ctx, "scheme", r.Proto) 196 ctx = context.WithValue(ctx, "local", r.Host) 197 if ua := r.Header.Get("User-Agent"); ua != "" { 198 ctx = context.WithValue(ctx, "User-Agent", ua) 199 } 200 if origin := r.Header.Get("Origin"); origin != "" { 201 ctx = context.WithValue(ctx, "Origin", origin) 202 } 203 204 w.Header().Set("content-type", contentType) 205 codec := newHTTPServerConn(r, w) 206 defer codec.close() 207 s.serveSingleRequest(ctx, codec) 208 } 209 210 func validateRequest(r *http.Request) (int, error) { 211 if r.Method == http.MethodPut || r.Method == http.MethodDelete { 212 return http.StatusMethodNotAllowed, errors.New("method not allowed") 213 } 214 if r.ContentLength > maxRequestContentLength { 215 err := fmt.Errorf("content length too large (%d>%d)", r.ContentLength, maxRequestContentLength) 216 return http.StatusRequestEntityTooLarge, err 217 } 218 if r.Method == http.MethodOptions { 219 return 0, nil 220 } 221 if mt, _, err := mime.ParseMediaType(r.Header.Get("content-type")); err == nil { 222 for _, accepted := range acceptedContentTypes { 223 if accepted == mt { 224 return 0, nil 225 } 226 } 227 } 228 err := fmt.Errorf("invalid content type, only %s is supported", contentType) 229 return http.StatusUnsupportedMediaType, err 230 }