dynatrace-api-balancer
Version:
A wrapper around Axios that balances and throttles requests across tenants, clusters and cluster nodes.
125 lines (113 loc) • 5.1 kB
JavaScript
'use strict';
const axios = require('axios').default;
const { CancellableEventEmitter } = require('./Cancellables.js');
class Request {
options = null;
tenant = null;
queue = null; // Set by Queues.
createTime = null; // Set here.
queueTime = null; // Set by Queues.
releaseTime = null; // Set by Queues.
autoCancel = null; // Set by Queues.
emitter = null;
callback = null;
abort = null; // Will be set by the CancelToken once the request is issued.
constructor(tenant, options, limits, onDone) {
/* The 'options' object will be passed to Axios as-is,
with a few amendments made in this constructor.
*/
options.timeout = !options.timeout
? limits.timeout
: options.timeout < 100
? options.timeout * 1000
: options.timeout;
options.method = options.method || (options.data ? "post" : "get");
options.headers = Object.assign(options.headers || {}, {
'Authorization': 'Api-Token ' + tenant.token,
'Content-Type' : 'application/json',
'Accept' : 'application/json'
});
// Older code may still pass a query string.
if (typeof options.params === 'string') {
const params = options.params.split('&');
options.params = {};
params.forEach(param => {
const parts = param.split('=');
options.params[parts.shift] = parts.join('=');
});
}
// Setting a cancelToken makes the request cancellable (once it has been
// issued). The argument to 'CancelToken()' is a function that receives
// a 'cancel()' function as a parameter, which we store so that we can
// invoke it when we are asked to cancel the request.
options.cancelToken = new axios.CancelToken(cancel => this.abort = cancel);
this.options = options;
this.tenant = tenant;
this.createTime = (new Date()).getTime();
// Remove the specific properties Axios doesn't understand.
delete options.tenant;
delete options.noQueue;
// This object here will emit events regarding the requests's
// progress. Callers can also call 'cancel()' on it.
this.emitter = new CancellableEventEmitter(this);
this.callback = (err, data) => {
try {
onDone(err, data);
}
catch (ex) {
this.emitter.emit("error", "Error delivering data: " + ex.message);
}
};
}
setHost(host) {
// Note that 'baseURL' is something Axios needs. Once a host accepts a
// request, it calls us here, and then we update the request so that
// Axios can issue it. Thus, the URL in 'options' is kept relative.
this.options.baseURL = (this.tenant.protocol || "https") + "://" + host
+ (this.tenant.port ? ":" + this.tenant.port : "")
+ (this.tenant.url || "");
return this.options.baseURL;
}
// The queues can call this if the request has been on the queue for too long.
// Original requestors can call this as well from the CancellableEventEmitter.
// Queues will give us the timeout they applied. Through the emitter we may
// get a reason. If we do, the request will be reported as 'Cancelled' to the
// original requestor with that reason(promise or callback). If we don't get
// a reason, the request will be cancelled silently.
cancel(reason) {
// The 'abort()' function is set once Axios has created the HTTP request.
// See the use of the 'CancelToken', above. That's what sets it.
if (this.abort) {
this.abort();
this.queue.release(this,
typeof reason === "number"
? {
status: 408,
message: "Request Timeout - no response" + (reason ? " within " + reason + "s" : "")
}
: reason
? {
status: 512, // Cancelled.
message: reason
}
: undefined
);
}
else {
this.queue.release(this,
typeof reason === "number"
? {
status: 429,
message: "Too Many Requests - not issued" + (reason ? " within " + reason + "s" : "")
}
: reason
? {
status: 412, // Precondition not met (i.e. some other error).
message: reason
}
: undefined
);
}
}
}
module.exports = Request;