/
fetch.js
134 lines (114 loc) · 3.58 KB
/
fetch.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
const fetch = global.fetch || require('fetch-ponyfill')().fetch
const inherits = require('util').inherits
const retry = require('async/retry')
const waterfall = require('async/waterfall')
const asyncify = require('async/asyncify')
const JsonRpcError = require('json-rpc-error')
const promiseToCallback = require('promise-to-callback')
const createPayload = require('../util/create-payload.js')
const Subprovider = require('./subprovider.js')
const RETRIABLE_ERRORS = [
// ignore server overload errors
'Gateway timeout',
'ETIMEDOUT',
// ignore server sent html error pages
// or truncated json responses
'SyntaxError',
]
module.exports = RpcSource
inherits(RpcSource, Subprovider)
function RpcSource (opts) {
const self = this
self.rpcUrl = opts.rpcUrl
self.originHttpHeaderKey = opts.originHttpHeaderKey
}
RpcSource.prototype.handleRequest = function (payload, next, end) {
const self = this
const originDomain = payload.origin
// overwrite id to not conflict with other concurrent users
const newPayload = createPayload(payload)
// remove extra parameter from request
delete newPayload.origin
const reqParams = {
method: 'POST',
headers: {
'Accept': 'application/json',
'Content-Type': 'application/json'
},
body: JSON.stringify(newPayload)
}
if (self.originHttpHeaderKey && originDomain) {
reqParams.headers[self.originHttpHeaderKey] = originDomain
}
retry({
times: 5,
interval: 1000,
errorFilter: isErrorRetriable,
},
(cb) => self._submitRequest(reqParams, cb),
(err, result) => {
// ends on retriable error
if (err && isErrorRetriable(err)) {
const errMsg = `FetchSubprovider - cannot complete request. All retries exhausted.\nOriginal Error:\n${err.toString()}\n\n`
const retriesExhaustedErr = new Error(errMsg)
return end(retriesExhaustedErr)
}
// otherwise continue normally
return end(err, result)
})
}
RpcSource.prototype._submitRequest = function (reqParams, cb) {
const self = this
const targetUrl = self.rpcUrl
promiseToCallback(fetch(targetUrl, reqParams))((err, res) => {
if (err) return cb(err)
// continue parsing result
waterfall([
checkForHttpErrors,
// buffer body
(cb) => promiseToCallback(res.text())(cb),
// parse body
asyncify((rawBody) => JSON.parse(rawBody)),
parseResponse
], cb)
function checkForHttpErrors (cb) {
// check for errors
switch (res.status) {
case 405:
return cb(new JsonRpcError.MethodNotFound())
case 418:
return cb(createRatelimitError())
case 503:
case 504:
return cb(createTimeoutError())
default:
return cb()
}
}
function parseResponse (body, cb) {
// check for error code
if (res.status !== 200) {
return cb(new JsonRpcError.InternalError(body))
}
// check for rpc error
if (body.error) return cb(new JsonRpcError.InternalError(body.error))
// return successful result
cb(null, body.result)
}
})
}
function isErrorRetriable(err){
const errMsg = err.toString()
return RETRIABLE_ERRORS.some(phrase => errMsg.includes(phrase))
}
function createRatelimitError () {
let msg = `Request is being rate limited.`
const err = new Error(msg)
return new JsonRpcError.InternalError(err)
}
function createTimeoutError () {
let msg = `Gateway timeout. The request took too long to process. `
msg += `This can happen when querying logs over too wide a block range.`
const err = new Error(msg)
return new JsonRpcError.InternalError(err)
}