js-bigchaindb-driver/src/request.js

105 lines
3.3 KiB
JavaScript
Raw Normal View History

// Copyright BigchainDB GmbH and BigchainDB contributors
// SPDX-License-Identifier: (Apache-2.0 AND CC-BY-4.0)
// Code is Apache-2.0 and docs are CC-BY-4.0
2017-06-12 16:57:29 +02:00
import baseRequest from './baseRequest'
import sanitize from './sanitize'
2017-04-26 15:58:19 +02:00
const DEFAULT_REQUEST_CONFIG = {
headers: {
'Accept': 'application/json'
}
2017-06-12 16:57:29 +02:00
}
2017-04-26 15:58:19 +02:00
2018-08-22 10:10:09 +02:00
const BACKOFF_DELAY = 0.5 // seconds
2017-04-26 15:58:19 +02:00
/**
* @private
2017-05-11 17:19:07 +02:00
* Small wrapper around js-utility-belt's request that provides url resolving,
* default settings, and response handling.
2017-04-26 15:58:19 +02:00
*/
2018-08-22 10:10:09 +02:00
export default class Request {
constructor(node, requestConfig) {
this.node = node
this.requestConfig = requestConfig
this.backoffTime = null
2018-08-23 17:14:59 +02:00
this.retries = 0
this.connectionError = null
2018-08-22 10:10:09 +02:00
}
2018-08-23 17:14:59 +02:00
async request(endpoint, config, setTimeout) {
2018-08-22 10:10:09 +02:00
// Load default fetch configuration and remove any falsy query parameters
const requestConfig = Object.assign({}, this.node.headers, DEFAULT_REQUEST_CONFIG, config, {
query: config.query && sanitize(config.query)
2017-06-12 16:57:29 +02:00
})
2018-08-22 10:10:09 +02:00
const apiUrl = this.node.endpoint + endpoint
if (requestConfig.jsonBody) {
requestConfig.headers = Object.assign({}, requestConfig.headers, {
'Content-Type': 'application/json'
})
}
if (!endpoint) {
return Promise.reject(new Error('Request was not given a url.'))
}
// If `ConnectionError` occurs, a timestamp equal to now +
// the default delay (`BACKOFF_DELAY`) is assigned to the object.
2018-08-23 17:14:59 +02:00
// Next time the function is called, it either
2018-08-22 10:10:09 +02:00
// waits till the timestamp is passed or raises `TimeoutError`.
// If `ConnectionError` occurs two or more times in a row,
// the retry count is incremented and the new timestamp is calculated
// as now + the default delay multiplied by two to the power of the
// number of retries.
// If a request is successful, the backoff timestamp is removed,
// the retry count is back to zero.
2018-08-23 17:14:59 +02:00
const backoffTimedelta = this.getBackoffTimedelta()
2018-08-22 10:10:09 +02:00
2018-08-23 17:14:59 +02:00
if (setTimeout != null && setTimeout < this.backoffTimedelta) {
const errorObject = {
message: 'TimeoutError'
}
throw errorObject
2018-08-22 10:10:09 +02:00
}
2018-08-23 17:14:59 +02:00
if (backoffTimedelta > 0) {
await Request.sleep(backoffTimedelta)
2018-08-22 10:10:09 +02:00
}
2018-08-23 17:14:59 +02:00
this.timeout = setTimeout ? setTimeout - backoffTimedelta : setTimeout
2018-08-22 10:10:09 +02:00
return baseRequest(apiUrl, requestConfig)
2018-08-23 17:14:59 +02:00
.then(res => res.json())
2018-08-22 10:10:09 +02:00
.catch(err => {
2018-08-23 17:14:59 +02:00
// ConnectionError
this.connectionError = err
// throw err
2018-08-22 10:10:09 +02:00
})
2018-08-23 17:14:59 +02:00
.finally(() => {
this.updateBackoffTime()
2018-08-22 10:10:09 +02:00
})
2017-04-26 15:58:19 +02:00
}
2017-06-22 17:19:31 +02:00
2018-08-22 10:10:09 +02:00
getBackoffTimedelta() {
if (!this.backoffTime) {
return 0
}
return (this.backoffTime - Date.now())
2017-04-26 15:58:19 +02:00
}
2018-08-23 17:14:59 +02:00
updateBackoffTime() {
if (!this.connectionError) {
2018-08-22 10:10:09 +02:00
this.retries = 0
this.backoffTime = null
} else {
2018-08-23 17:14:59 +02:00
const backoffTimedelta = BACKOFF_DELAY * (2 ** this.retries)
this.backoffTime = Date.now() + backoffTimedelta
2018-08-22 10:10:09 +02:00
this.retries += 1
}
}
static sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms))
}
2017-04-26 15:58:19 +02:00
}