github.com/okex/exchain@v1.8.0/libs/tendermint/rpc/jsonrpc/client/http_json_client.go (about) 1 package client 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "fmt" 7 "io/ioutil" 8 "net" 9 "net/http" 10 "net/url" 11 "strings" 12 "sync" 13 14 "github.com/pkg/errors" 15 amino "github.com/tendermint/go-amino" 16 17 types "github.com/okex/exchain/libs/tendermint/rpc/jsonrpc/types" 18 ) 19 20 const ( 21 protoHTTP = "http" 22 protoHTTPS = "https" 23 protoWSS = "wss" 24 protoWS = "ws" 25 protoTCP = "tcp" 26 ) 27 28 //------------------------------------------------------------- 29 30 // Parsed URL structure 31 type parsedURL struct { 32 url.URL 33 } 34 35 // Parse URL and set defaults 36 func newParsedURL(remoteAddr string) (*parsedURL, error) { 37 u, err := url.Parse(remoteAddr) 38 if err != nil { 39 return nil, err 40 } 41 42 // default to tcp if nothing specified 43 if u.Scheme == "" { 44 u.Scheme = protoTCP 45 } 46 47 return &parsedURL{*u}, nil 48 } 49 50 // Change protocol to HTTP for unknown protocols and TCP protocol - useful for RPC connections 51 func (u *parsedURL) SetDefaultSchemeHTTP() { 52 // protocol to use for http operations, to support both http and https 53 switch u.Scheme { 54 case protoHTTP, protoHTTPS, protoWS, protoWSS: 55 // known protocols not changed 56 default: 57 // default to http for unknown protocols (ex. tcp) 58 u.Scheme = protoHTTP 59 } 60 } 61 62 // Get full address without the protocol - useful for Dialer connections 63 func (u parsedURL) GetHostWithPath() string { 64 // Remove protocol, userinfo and # fragment, assume opaque is empty 65 return u.Host + u.EscapedPath() 66 } 67 68 // Get a trimmed address - useful for WS connections 69 func (u parsedURL) GetTrimmedHostWithPath() string { 70 // replace / with . for http requests (kvstore domain) 71 return strings.Replace(u.GetHostWithPath(), "/", ".", -1) 72 } 73 74 // Get a trimmed address with protocol - useful as address in RPC connections 75 func (u parsedURL) GetTrimmedURL() string { 76 return u.Scheme + "://" + u.GetTrimmedHostWithPath() 77 } 78 79 //------------------------------------------------------------- 80 81 // HTTPClient is a common interface for JSON-RPC HTTP clients. 82 type HTTPClient interface { 83 // Call calls the given method with the params and returns a result. 84 Call(method string, params map[string]interface{}, result interface{}) (interface{}, error) 85 // Codec returns an amino codec used. 86 Codec() *amino.Codec 87 // SetCodec sets an amino codec. 88 SetCodec(*amino.Codec) 89 } 90 91 // Caller implementers can facilitate calling the JSON-RPC endpoint. 92 type Caller interface { 93 Call(method string, params map[string]interface{}, result interface{}) (interface{}, error) 94 } 95 96 //------------------------------------------------------------- 97 98 // Client is a JSON-RPC client, which sends POST HTTP requests to the 99 // remote server. 100 // 101 // Request values are amino encoded. Response is expected to be amino encoded. 102 // New amino codec is used if no other codec was set using SetCodec. 103 // 104 // Client is safe for concurrent use by multiple goroutines. 105 type Client struct { 106 address string 107 username string 108 password string 109 110 client *http.Client 111 cdc *amino.Codec 112 113 mtx sync.Mutex 114 nextReqID int 115 } 116 117 var _ HTTPClient = (*Client)(nil) 118 119 // Both Client and RequestBatch can facilitate calls to the JSON 120 // RPC endpoint. 121 var _ Caller = (*Client)(nil) 122 var _ Caller = (*RequestBatch)(nil) 123 124 // New returns a Client pointed at the given address. 125 // An error is returned on invalid remote. The function panics when remote is nil. 126 func New(remote string) (*Client, error) { 127 httpClient, err := DefaultHTTPClient(remote) 128 if err != nil { 129 return nil, err 130 } 131 return NewWithHTTPClient(remote, httpClient) 132 } 133 134 // NewWithHTTPClient returns a Client pointed at the given 135 // address using a custom http client. An error is returned on invalid remote. 136 // The function panics when remote is nil. 137 func NewWithHTTPClient(remote string, client *http.Client) (*Client, error) { 138 if client == nil { 139 panic("nil http.Client provided") 140 } 141 142 parsedURL, err := newParsedURL(remote) 143 if err != nil { 144 return nil, fmt.Errorf("invalid remote %s: %s", remote, err) 145 } 146 147 parsedURL.SetDefaultSchemeHTTP() 148 149 address := parsedURL.GetTrimmedURL() 150 username := parsedURL.User.Username() 151 password, _ := parsedURL.User.Password() 152 153 rpcClient := &Client{ 154 address: address, 155 username: username, 156 password: password, 157 client: client, 158 cdc: amino.NewCodec(), 159 } 160 161 return rpcClient, nil 162 } 163 164 // Call issues a POST HTTP request. Requests are JSON encoded. Content-Type: 165 // text/json. 166 func (c *Client) Call(method string, params map[string]interface{}, result interface{}) (interface{}, error) { 167 id := c.nextRequestID() 168 169 request, err := types.MapToRequest(c.cdc, id, method, params) 170 if err != nil { 171 return nil, errors.Wrap(err, "failed to encode params") 172 } 173 174 requestBytes, err := json.Marshal(request) 175 if err != nil { 176 return nil, errors.Wrap(err, "failed to marshal request") 177 } 178 179 requestBuf := bytes.NewBuffer(requestBytes) 180 httpRequest, err := http.NewRequest(http.MethodPost, c.address, requestBuf) 181 if err != nil { 182 return nil, errors.Wrap(err, "Request failed") 183 } 184 httpRequest.Header.Set("Content-Type", "text/json") 185 if c.username != "" || c.password != "" { 186 httpRequest.SetBasicAuth(c.username, c.password) 187 } 188 httpResponse, err := c.client.Do(httpRequest) 189 if err != nil { 190 return nil, errors.Wrap(err, "Post failed") 191 } 192 defer httpResponse.Body.Close() // nolint: errcheck 193 194 responseBytes, err := ioutil.ReadAll(httpResponse.Body) 195 if err != nil { 196 return nil, errors.Wrap(err, "failed to read response body") 197 } 198 199 return unmarshalResponseBytes(c.cdc, responseBytes, id, result) 200 } 201 202 func (c *Client) Codec() *amino.Codec { return c.cdc } 203 func (c *Client) SetCodec(cdc *amino.Codec) { c.cdc = cdc } 204 205 // NewRequestBatch starts a batch of requests for this client. 206 func (c *Client) NewRequestBatch() *RequestBatch { 207 return &RequestBatch{ 208 requests: make([]*jsonRPCBufferedRequest, 0), 209 client: c, 210 } 211 } 212 213 func (c *Client) sendBatch(requests []*jsonRPCBufferedRequest) ([]interface{}, error) { 214 reqs := make([]types.RPCRequest, 0, len(requests)) 215 results := make([]interface{}, 0, len(requests)) 216 for _, req := range requests { 217 reqs = append(reqs, req.request) 218 results = append(results, req.result) 219 } 220 221 // serialize the array of requests into a single JSON object 222 requestBytes, err := json.Marshal(reqs) 223 if err != nil { 224 return nil, errors.Wrap(err, "failed to marshal requests") 225 } 226 227 httpRequest, err := http.NewRequest(http.MethodPost, c.address, bytes.NewBuffer(requestBytes)) 228 if err != nil { 229 return nil, errors.Wrap(err, "Request failed") 230 } 231 httpRequest.Header.Set("Content-Type", "text/json") 232 if c.username != "" || c.password != "" { 233 httpRequest.SetBasicAuth(c.username, c.password) 234 } 235 httpResponse, err := c.client.Do(httpRequest) 236 if err != nil { 237 return nil, errors.Wrap(err, "Post failed") 238 } 239 defer httpResponse.Body.Close() // nolint: errcheck 240 241 responseBytes, err := ioutil.ReadAll(httpResponse.Body) 242 if err != nil { 243 return nil, errors.Wrap(err, "failed to read response body") 244 } 245 246 // collect ids to check responses IDs in unmarshalResponseBytesArray 247 ids := make([]types.JSONRPCIntID, len(requests)) 248 for i, req := range requests { 249 ids[i] = req.request.ID.(types.JSONRPCIntID) 250 } 251 252 return unmarshalResponseBytesArray(c.cdc, responseBytes, ids, results) 253 } 254 255 func (c *Client) nextRequestID() types.JSONRPCIntID { 256 c.mtx.Lock() 257 id := c.nextReqID 258 c.nextReqID++ 259 c.mtx.Unlock() 260 return types.JSONRPCIntID(id) 261 } 262 263 //------------------------------------------------------------------------------------ 264 265 // jsonRPCBufferedRequest encapsulates a single buffered request, as well as its 266 // anticipated response structure. 267 type jsonRPCBufferedRequest struct { 268 request types.RPCRequest 269 result interface{} // The result will be deserialized into this object. 270 } 271 272 // RequestBatch allows us to buffer multiple request/response structures 273 // into a single batch request. Note that this batch acts like a FIFO queue, and 274 // is thread-safe. 275 type RequestBatch struct { 276 client *Client 277 278 mtx sync.Mutex 279 requests []*jsonRPCBufferedRequest 280 } 281 282 // Count returns the number of enqueued requests waiting to be sent. 283 func (b *RequestBatch) Count() int { 284 b.mtx.Lock() 285 defer b.mtx.Unlock() 286 return len(b.requests) 287 } 288 289 func (b *RequestBatch) enqueue(req *jsonRPCBufferedRequest) { 290 b.mtx.Lock() 291 defer b.mtx.Unlock() 292 b.requests = append(b.requests, req) 293 } 294 295 // Clear empties out the request batch. 296 func (b *RequestBatch) Clear() int { 297 b.mtx.Lock() 298 defer b.mtx.Unlock() 299 return b.clear() 300 } 301 302 func (b *RequestBatch) clear() int { 303 count := len(b.requests) 304 b.requests = make([]*jsonRPCBufferedRequest, 0) 305 return count 306 } 307 308 // Send will attempt to send the current batch of enqueued requests, and then 309 // will clear out the requests once done. On success, this returns the 310 // deserialized list of results from each of the enqueued requests. 311 func (b *RequestBatch) Send() ([]interface{}, error) { 312 b.mtx.Lock() 313 defer func() { 314 b.clear() 315 b.mtx.Unlock() 316 }() 317 return b.client.sendBatch(b.requests) 318 } 319 320 // Call enqueues a request to call the given RPC method with the specified 321 // parameters, in the same way that the `Client.Call` function would. 322 func (b *RequestBatch) Call( 323 method string, 324 params map[string]interface{}, 325 result interface{}, 326 ) (interface{}, error) { 327 id := b.client.nextRequestID() 328 request, err := types.MapToRequest(b.client.cdc, id, method, params) 329 if err != nil { 330 return nil, err 331 } 332 b.enqueue(&jsonRPCBufferedRequest{request: request, result: result}) 333 return result, nil 334 } 335 336 //------------------------------------------------------------- 337 338 func makeHTTPDialer(remoteAddr string) (func(string, string) (net.Conn, error), error) { 339 u, err := newParsedURL(remoteAddr) 340 if err != nil { 341 return nil, err 342 } 343 344 protocol := u.Scheme 345 346 // accept http(s) as an alias for tcp 347 var dialFn func(string, string) (net.Conn, error) 348 switch protocol { 349 case protoHTTP, protoHTTPS: 350 dialFn = nil 351 default: 352 dialFn = func(proto, addr string) (net.Conn, error) { 353 return net.Dial(protocol, u.GetHostWithPath()) 354 } 355 } 356 357 return dialFn, nil 358 } 359 360 // DefaultHTTPClient is used to create an http client with some default parameters. 361 // We overwrite the http.Client.Dial so we can do http over tcp or unix. 362 // remoteAddr should be fully featured (eg. with tcp:// or unix://). 363 // An error will be returned in case of invalid remoteAddr. 364 func DefaultHTTPClient(remoteAddr string) (*http.Client, error) { 365 dialFn, err := makeHTTPDialer(remoteAddr) 366 if err != nil { 367 return nil, err 368 } 369 370 client := &http.Client{ 371 Transport: &http.Transport{ 372 // Set to true to prevent GZIP-bomb DoS attacks 373 DisableCompression: true, 374 Dial: dialFn, 375 }, 376 } 377 378 return client, nil 379 }