|
|
@@ -0,0 +1,501 @@
|
|
|
+"use strict";
|
|
|
+Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
+const url = require("url");
|
|
|
+const http = require("http");
|
|
|
+const https = require("https");
|
|
|
+const pm = require("./proxy");
|
|
|
+let tunnel;
|
|
|
+var HttpCodes;
|
|
|
+(function (HttpCodes) {
|
|
|
+ HttpCodes[HttpCodes["OK"] = 200] = "OK";
|
|
|
+ HttpCodes[HttpCodes["MultipleChoices"] = 300] = "MultipleChoices";
|
|
|
+ HttpCodes[HttpCodes["MovedPermanently"] = 301] = "MovedPermanently";
|
|
|
+ HttpCodes[HttpCodes["ResourceMoved"] = 302] = "ResourceMoved";
|
|
|
+ HttpCodes[HttpCodes["SeeOther"] = 303] = "SeeOther";
|
|
|
+ HttpCodes[HttpCodes["NotModified"] = 304] = "NotModified";
|
|
|
+ HttpCodes[HttpCodes["UseProxy"] = 305] = "UseProxy";
|
|
|
+ HttpCodes[HttpCodes["SwitchProxy"] = 306] = "SwitchProxy";
|
|
|
+ HttpCodes[HttpCodes["TemporaryRedirect"] = 307] = "TemporaryRedirect";
|
|
|
+ HttpCodes[HttpCodes["PermanentRedirect"] = 308] = "PermanentRedirect";
|
|
|
+ HttpCodes[HttpCodes["BadRequest"] = 400] = "BadRequest";
|
|
|
+ HttpCodes[HttpCodes["Unauthorized"] = 401] = "Unauthorized";
|
|
|
+ HttpCodes[HttpCodes["PaymentRequired"] = 402] = "PaymentRequired";
|
|
|
+ HttpCodes[HttpCodes["Forbidden"] = 403] = "Forbidden";
|
|
|
+ HttpCodes[HttpCodes["NotFound"] = 404] = "NotFound";
|
|
|
+ HttpCodes[HttpCodes["MethodNotAllowed"] = 405] = "MethodNotAllowed";
|
|
|
+ HttpCodes[HttpCodes["NotAcceptable"] = 406] = "NotAcceptable";
|
|
|
+ HttpCodes[HttpCodes["ProxyAuthenticationRequired"] = 407] = "ProxyAuthenticationRequired";
|
|
|
+ HttpCodes[HttpCodes["RequestTimeout"] = 408] = "RequestTimeout";
|
|
|
+ HttpCodes[HttpCodes["Conflict"] = 409] = "Conflict";
|
|
|
+ HttpCodes[HttpCodes["Gone"] = 410] = "Gone";
|
|
|
+ HttpCodes[HttpCodes["TooManyRequests"] = 429] = "TooManyRequests";
|
|
|
+ HttpCodes[HttpCodes["InternalServerError"] = 500] = "InternalServerError";
|
|
|
+ HttpCodes[HttpCodes["NotImplemented"] = 501] = "NotImplemented";
|
|
|
+ HttpCodes[HttpCodes["BadGateway"] = 502] = "BadGateway";
|
|
|
+ HttpCodes[HttpCodes["ServiceUnavailable"] = 503] = "ServiceUnavailable";
|
|
|
+ HttpCodes[HttpCodes["GatewayTimeout"] = 504] = "GatewayTimeout";
|
|
|
+})(HttpCodes = exports.HttpCodes || (exports.HttpCodes = {}));
|
|
|
+var Headers;
|
|
|
+(function (Headers) {
|
|
|
+ Headers["Accept"] = "accept";
|
|
|
+ Headers["ContentType"] = "content-type";
|
|
|
+})(Headers = exports.Headers || (exports.Headers = {}));
|
|
|
+var MediaTypes;
|
|
|
+(function (MediaTypes) {
|
|
|
+ MediaTypes["ApplicationJson"] = "application/json";
|
|
|
+})(MediaTypes = exports.MediaTypes || (exports.MediaTypes = {}));
|
|
|
+/**
|
|
|
+ * Returns the proxy URL, depending upon the supplied url and proxy environment variables.
|
|
|
+ * @param serverUrl The server URL where the request will be sent. For example, https://api.github.com
|
|
|
+ */
|
|
|
+function getProxyUrl(serverUrl) {
|
|
|
+ let proxyUrl = pm.getProxyUrl(url.parse(serverUrl));
|
|
|
+ return proxyUrl ? proxyUrl.href : '';
|
|
|
+}
|
|
|
+exports.getProxyUrl = getProxyUrl;
|
|
|
+const HttpRedirectCodes = [HttpCodes.MovedPermanently, HttpCodes.ResourceMoved, HttpCodes.SeeOther, HttpCodes.TemporaryRedirect, HttpCodes.PermanentRedirect];
|
|
|
+const HttpResponseRetryCodes = [HttpCodes.BadGateway, HttpCodes.ServiceUnavailable, HttpCodes.GatewayTimeout];
|
|
|
+const RetryableHttpVerbs = ['OPTIONS', 'GET', 'DELETE', 'HEAD'];
|
|
|
+const ExponentialBackoffCeiling = 10;
|
|
|
+const ExponentialBackoffTimeSlice = 5;
|
|
|
+class HttpClientResponse {
|
|
|
+ constructor(message) {
|
|
|
+ this.message = message;
|
|
|
+ }
|
|
|
+ readBody() {
|
|
|
+ return new Promise(async (resolve, reject) => {
|
|
|
+ let output = Buffer.alloc(0);
|
|
|
+ this.message.on('data', (chunk) => {
|
|
|
+ output = Buffer.concat([output, chunk]);
|
|
|
+ });
|
|
|
+ this.message.on('end', () => {
|
|
|
+ resolve(output.toString());
|
|
|
+ });
|
|
|
+ });
|
|
|
+ }
|
|
|
+}
|
|
|
+exports.HttpClientResponse = HttpClientResponse;
|
|
|
+function isHttps(requestUrl) {
|
|
|
+ let parsedUrl = url.parse(requestUrl);
|
|
|
+ return parsedUrl.protocol === 'https:';
|
|
|
+}
|
|
|
+exports.isHttps = isHttps;
|
|
|
+class HttpClient {
|
|
|
+ constructor(userAgent, handlers, requestOptions) {
|
|
|
+ this._ignoreSslError = false;
|
|
|
+ this._allowRedirects = true;
|
|
|
+ this._allowRedirectDowngrade = false;
|
|
|
+ this._maxRedirects = 50;
|
|
|
+ this._allowRetries = false;
|
|
|
+ this._maxRetries = 1;
|
|
|
+ this._keepAlive = false;
|
|
|
+ this._disposed = false;
|
|
|
+ this.userAgent = userAgent;
|
|
|
+ this.handlers = handlers || [];
|
|
|
+ this.requestOptions = requestOptions;
|
|
|
+ if (requestOptions) {
|
|
|
+ if (requestOptions.ignoreSslError != null) {
|
|
|
+ this._ignoreSslError = requestOptions.ignoreSslError;
|
|
|
+ }
|
|
|
+ this._socketTimeout = requestOptions.socketTimeout;
|
|
|
+ if (requestOptions.allowRedirects != null) {
|
|
|
+ this._allowRedirects = requestOptions.allowRedirects;
|
|
|
+ }
|
|
|
+ if (requestOptions.allowRedirectDowngrade != null) {
|
|
|
+ this._allowRedirectDowngrade = requestOptions.allowRedirectDowngrade;
|
|
|
+ }
|
|
|
+ if (requestOptions.maxRedirects != null) {
|
|
|
+ this._maxRedirects = Math.max(requestOptions.maxRedirects, 0);
|
|
|
+ }
|
|
|
+ if (requestOptions.keepAlive != null) {
|
|
|
+ this._keepAlive = requestOptions.keepAlive;
|
|
|
+ }
|
|
|
+ if (requestOptions.allowRetries != null) {
|
|
|
+ this._allowRetries = requestOptions.allowRetries;
|
|
|
+ }
|
|
|
+ if (requestOptions.maxRetries != null) {
|
|
|
+ this._maxRetries = requestOptions.maxRetries;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ options(requestUrl, additionalHeaders) {
|
|
|
+ return this.request('OPTIONS', requestUrl, null, additionalHeaders || {});
|
|
|
+ }
|
|
|
+ get(requestUrl, additionalHeaders) {
|
|
|
+ return this.request('GET', requestUrl, null, additionalHeaders || {});
|
|
|
+ }
|
|
|
+ del(requestUrl, additionalHeaders) {
|
|
|
+ return this.request('DELETE', requestUrl, null, additionalHeaders || {});
|
|
|
+ }
|
|
|
+ post(requestUrl, data, additionalHeaders) {
|
|
|
+ return this.request('POST', requestUrl, data, additionalHeaders || {});
|
|
|
+ }
|
|
|
+ patch(requestUrl, data, additionalHeaders) {
|
|
|
+ return this.request('PATCH', requestUrl, data, additionalHeaders || {});
|
|
|
+ }
|
|
|
+ put(requestUrl, data, additionalHeaders) {
|
|
|
+ return this.request('PUT', requestUrl, data, additionalHeaders || {});
|
|
|
+ }
|
|
|
+ head(requestUrl, additionalHeaders) {
|
|
|
+ return this.request('HEAD', requestUrl, null, additionalHeaders || {});
|
|
|
+ }
|
|
|
+ sendStream(verb, requestUrl, stream, additionalHeaders) {
|
|
|
+ return this.request(verb, requestUrl, stream, additionalHeaders);
|
|
|
+ }
|
|
|
+ /**
|
|
|
+ * Gets a typed object from an endpoint
|
|
|
+ * Be aware that not found returns a null. Other errors (4xx, 5xx) reject the promise
|
|
|
+ */
|
|
|
+ async getJson(requestUrl, additionalHeaders = {}) {
|
|
|
+ additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson);
|
|
|
+ let res = await this.get(requestUrl, additionalHeaders);
|
|
|
+ return this._processResponse(res, this.requestOptions);
|
|
|
+ }
|
|
|
+ async postJson(requestUrl, obj, additionalHeaders = {}) {
|
|
|
+ let data = JSON.stringify(obj, null, 2);
|
|
|
+ additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson);
|
|
|
+ additionalHeaders[Headers.ContentType] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.ContentType, MediaTypes.ApplicationJson);
|
|
|
+ let res = await this.post(requestUrl, data, additionalHeaders);
|
|
|
+ return this._processResponse(res, this.requestOptions);
|
|
|
+ }
|
|
|
+ async putJson(requestUrl, obj, additionalHeaders = {}) {
|
|
|
+ let data = JSON.stringify(obj, null, 2);
|
|
|
+ additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson);
|
|
|
+ additionalHeaders[Headers.ContentType] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.ContentType, MediaTypes.ApplicationJson);
|
|
|
+ let res = await this.put(requestUrl, data, additionalHeaders);
|
|
|
+ return this._processResponse(res, this.requestOptions);
|
|
|
+ }
|
|
|
+ async patchJson(requestUrl, obj, additionalHeaders = {}) {
|
|
|
+ let data = JSON.stringify(obj, null, 2);
|
|
|
+ additionalHeaders[Headers.Accept] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.Accept, MediaTypes.ApplicationJson);
|
|
|
+ additionalHeaders[Headers.ContentType] = this._getExistingOrDefaultHeader(additionalHeaders, Headers.ContentType, MediaTypes.ApplicationJson);
|
|
|
+ let res = await this.patch(requestUrl, data, additionalHeaders);
|
|
|
+ return this._processResponse(res, this.requestOptions);
|
|
|
+ }
|
|
|
+ /**
|
|
|
+ * Makes a raw http request.
|
|
|
+ * All other methods such as get, post, patch, and request ultimately call this.
|
|
|
+ * Prefer get, del, post and patch
|
|
|
+ */
|
|
|
+ async request(verb, requestUrl, data, headers) {
|
|
|
+ if (this._disposed) {
|
|
|
+ throw new Error("Client has already been disposed.");
|
|
|
+ }
|
|
|
+ let parsedUrl = url.parse(requestUrl);
|
|
|
+ let info = this._prepareRequest(verb, parsedUrl, headers);
|
|
|
+ // Only perform retries on reads since writes may not be idempotent.
|
|
|
+ let maxTries = (this._allowRetries && RetryableHttpVerbs.indexOf(verb) != -1) ? this._maxRetries + 1 : 1;
|
|
|
+ let numTries = 0;
|
|
|
+ let response;
|
|
|
+ while (numTries < maxTries) {
|
|
|
+ response = await this.requestRaw(info, data);
|
|
|
+ // Check if it's an authentication challenge
|
|
|
+ if (response && response.message && response.message.statusCode === HttpCodes.Unauthorized) {
|
|
|
+ let authenticationHandler;
|
|
|
+ for (let i = 0; i < this.handlers.length; i++) {
|
|
|
+ if (this.handlers[i].canHandleAuthentication(response)) {
|
|
|
+ authenticationHandler = this.handlers[i];
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (authenticationHandler) {
|
|
|
+ return authenticationHandler.handleAuthentication(this, info, data);
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ // We have received an unauthorized response but have no handlers to handle it.
|
|
|
+ // Let the response return to the caller.
|
|
|
+ return response;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ let redirectsRemaining = this._maxRedirects;
|
|
|
+ while (HttpRedirectCodes.indexOf(response.message.statusCode) != -1
|
|
|
+ && this._allowRedirects
|
|
|
+ && redirectsRemaining > 0) {
|
|
|
+ const redirectUrl = response.message.headers["location"];
|
|
|
+ if (!redirectUrl) {
|
|
|
+ // if there's no location to redirect to, we won't
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ let parsedRedirectUrl = url.parse(redirectUrl);
|
|
|
+ if (parsedUrl.protocol == 'https:' && parsedUrl.protocol != parsedRedirectUrl.protocol && !this._allowRedirectDowngrade) {
|
|
|
+ throw new Error("Redirect from HTTPS to HTTP protocol. This downgrade is not allowed for security reasons. If you want to allow this behavior, set the allowRedirectDowngrade option to true.");
|
|
|
+ }
|
|
|
+ // we need to finish reading the response before reassigning response
|
|
|
+ // which will leak the open socket.
|
|
|
+ await response.readBody();
|
|
|
+ // let's make the request with the new redirectUrl
|
|
|
+ info = this._prepareRequest(verb, parsedRedirectUrl, headers);
|
|
|
+ response = await this.requestRaw(info, data);
|
|
|
+ redirectsRemaining--;
|
|
|
+ }
|
|
|
+ if (HttpResponseRetryCodes.indexOf(response.message.statusCode) == -1) {
|
|
|
+ // If not a retry code, return immediately instead of retrying
|
|
|
+ return response;
|
|
|
+ }
|
|
|
+ numTries += 1;
|
|
|
+ if (numTries < maxTries) {
|
|
|
+ await response.readBody();
|
|
|
+ await this._performExponentialBackoff(numTries);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return response;
|
|
|
+ }
|
|
|
+ /**
|
|
|
+ * Needs to be called if keepAlive is set to true in request options.
|
|
|
+ */
|
|
|
+ dispose() {
|
|
|
+ if (this._agent) {
|
|
|
+ this._agent.destroy();
|
|
|
+ }
|
|
|
+ this._disposed = true;
|
|
|
+ }
|
|
|
+ /**
|
|
|
+ * Raw request.
|
|
|
+ * @param info
|
|
|
+ * @param data
|
|
|
+ */
|
|
|
+ requestRaw(info, data) {
|
|
|
+ return new Promise((resolve, reject) => {
|
|
|
+ let callbackForResult = function (err, res) {
|
|
|
+ if (err) {
|
|
|
+ reject(err);
|
|
|
+ }
|
|
|
+ resolve(res);
|
|
|
+ };
|
|
|
+ this.requestRawWithCallback(info, data, callbackForResult);
|
|
|
+ });
|
|
|
+ }
|
|
|
+ /**
|
|
|
+ * Raw request with callback.
|
|
|
+ * @param info
|
|
|
+ * @param data
|
|
|
+ * @param onResult
|
|
|
+ */
|
|
|
+ requestRawWithCallback(info, data, onResult) {
|
|
|
+ let socket;
|
|
|
+ if (typeof (data) === 'string') {
|
|
|
+ info.options.headers["Content-Length"] = Buffer.byteLength(data, 'utf8');
|
|
|
+ }
|
|
|
+ let callbackCalled = false;
|
|
|
+ let handleResult = (err, res) => {
|
|
|
+ if (!callbackCalled) {
|
|
|
+ callbackCalled = true;
|
|
|
+ onResult(err, res);
|
|
|
+ }
|
|
|
+ };
|
|
|
+ let req = info.httpModule.request(info.options, (msg) => {
|
|
|
+ let res = new HttpClientResponse(msg);
|
|
|
+ handleResult(null, res);
|
|
|
+ });
|
|
|
+ req.on('socket', (sock) => {
|
|
|
+ socket = sock;
|
|
|
+ });
|
|
|
+ // If we ever get disconnected, we want the socket to timeout eventually
|
|
|
+ req.setTimeout(this._socketTimeout || 3 * 60000, () => {
|
|
|
+ if (socket) {
|
|
|
+ socket.end();
|
|
|
+ }
|
|
|
+ handleResult(new Error('Request timeout: ' + info.options.path), null);
|
|
|
+ });
|
|
|
+ req.on('error', function (err) {
|
|
|
+ // err has statusCode property
|
|
|
+ // res should have headers
|
|
|
+ handleResult(err, null);
|
|
|
+ });
|
|
|
+ if (data && typeof (data) === 'string') {
|
|
|
+ req.write(data, 'utf8');
|
|
|
+ }
|
|
|
+ if (data && typeof (data) !== 'string') {
|
|
|
+ data.on('close', function () {
|
|
|
+ req.end();
|
|
|
+ });
|
|
|
+ data.pipe(req);
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ req.end();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ /**
|
|
|
+ * Gets an http agent. This function is useful when you need an http agent that handles
|
|
|
+ * routing through a proxy server - depending upon the url and proxy environment variables.
|
|
|
+ * @param serverUrl The server URL where the request will be sent. For example, https://api.github.com
|
|
|
+ */
|
|
|
+ getAgent(serverUrl) {
|
|
|
+ let parsedUrl = url.parse(serverUrl);
|
|
|
+ return this._getAgent(parsedUrl);
|
|
|
+ }
|
|
|
+ _prepareRequest(method, requestUrl, headers) {
|
|
|
+ const info = {};
|
|
|
+ info.parsedUrl = requestUrl;
|
|
|
+ const usingSsl = info.parsedUrl.protocol === 'https:';
|
|
|
+ info.httpModule = usingSsl ? https : http;
|
|
|
+ const defaultPort = usingSsl ? 443 : 80;
|
|
|
+ info.options = {};
|
|
|
+ info.options.host = info.parsedUrl.hostname;
|
|
|
+ info.options.port = info.parsedUrl.port ? parseInt(info.parsedUrl.port) : defaultPort;
|
|
|
+ info.options.path = (info.parsedUrl.pathname || '') + (info.parsedUrl.search || '');
|
|
|
+ info.options.method = method;
|
|
|
+ info.options.headers = this._mergeHeaders(headers);
|
|
|
+ if (this.userAgent != null) {
|
|
|
+ info.options.headers["user-agent"] = this.userAgent;
|
|
|
+ }
|
|
|
+ info.options.agent = this._getAgent(info.parsedUrl);
|
|
|
+ // gives handlers an opportunity to participate
|
|
|
+ if (this.handlers) {
|
|
|
+ this.handlers.forEach((handler) => {
|
|
|
+ handler.prepareRequest(info.options);
|
|
|
+ });
|
|
|
+ }
|
|
|
+ return info;
|
|
|
+ }
|
|
|
+ _mergeHeaders(headers) {
|
|
|
+ const lowercaseKeys = obj => Object.keys(obj).reduce((c, k) => (c[k.toLowerCase()] = obj[k], c), {});
|
|
|
+ if (this.requestOptions && this.requestOptions.headers) {
|
|
|
+ return Object.assign({}, lowercaseKeys(this.requestOptions.headers), lowercaseKeys(headers));
|
|
|
+ }
|
|
|
+ return lowercaseKeys(headers || {});
|
|
|
+ }
|
|
|
+ _getExistingOrDefaultHeader(additionalHeaders, header, _default) {
|
|
|
+ const lowercaseKeys = obj => Object.keys(obj).reduce((c, k) => (c[k.toLowerCase()] = obj[k], c), {});
|
|
|
+ let clientHeader;
|
|
|
+ if (this.requestOptions && this.requestOptions.headers) {
|
|
|
+ clientHeader = lowercaseKeys(this.requestOptions.headers)[header];
|
|
|
+ }
|
|
|
+ return additionalHeaders[header] || clientHeader || _default;
|
|
|
+ }
|
|
|
+ _getAgent(parsedUrl) {
|
|
|
+ let agent;
|
|
|
+ let proxyUrl = pm.getProxyUrl(parsedUrl);
|
|
|
+ let useProxy = proxyUrl && proxyUrl.hostname;
|
|
|
+ if (this._keepAlive && useProxy) {
|
|
|
+ agent = this._proxyAgent;
|
|
|
+ }
|
|
|
+ if (this._keepAlive && !useProxy) {
|
|
|
+ agent = this._agent;
|
|
|
+ }
|
|
|
+ // if agent is already assigned use that agent.
|
|
|
+ if (!!agent) {
|
|
|
+ return agent;
|
|
|
+ }
|
|
|
+ const usingSsl = parsedUrl.protocol === 'https:';
|
|
|
+ let maxSockets = 100;
|
|
|
+ if (!!this.requestOptions) {
|
|
|
+ maxSockets = this.requestOptions.maxSockets || http.globalAgent.maxSockets;
|
|
|
+ }
|
|
|
+ if (useProxy) {
|
|
|
+ // If using proxy, need tunnel
|
|
|
+ if (!tunnel) {
|
|
|
+ tunnel = require('tunnel');
|
|
|
+ }
|
|
|
+ const agentOptions = {
|
|
|
+ maxSockets: maxSockets,
|
|
|
+ keepAlive: this._keepAlive,
|
|
|
+ proxy: {
|
|
|
+ proxyAuth: proxyUrl.auth,
|
|
|
+ host: proxyUrl.hostname,
|
|
|
+ port: proxyUrl.port
|
|
|
+ },
|
|
|
+ };
|
|
|
+ let tunnelAgent;
|
|
|
+ const overHttps = proxyUrl.protocol === 'https:';
|
|
|
+ if (usingSsl) {
|
|
|
+ tunnelAgent = overHttps ? tunnel.httpsOverHttps : tunnel.httpsOverHttp;
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ tunnelAgent = overHttps ? tunnel.httpOverHttps : tunnel.httpOverHttp;
|
|
|
+ }
|
|
|
+ agent = tunnelAgent(agentOptions);
|
|
|
+ this._proxyAgent = agent;
|
|
|
+ }
|
|
|
+ // if reusing agent across request and tunneling agent isn't assigned create a new agent
|
|
|
+ if (this._keepAlive && !agent) {
|
|
|
+ const options = { keepAlive: this._keepAlive, maxSockets: maxSockets };
|
|
|
+ agent = usingSsl ? new https.Agent(options) : new http.Agent(options);
|
|
|
+ this._agent = agent;
|
|
|
+ }
|
|
|
+ // if not using private agent and tunnel agent isn't setup then use global agent
|
|
|
+ if (!agent) {
|
|
|
+ agent = usingSsl ? https.globalAgent : http.globalAgent;
|
|
|
+ }
|
|
|
+ if (usingSsl && this._ignoreSslError) {
|
|
|
+ // we don't want to set NODE_TLS_REJECT_UNAUTHORIZED=0 since that will affect request for entire process
|
|
|
+ // http.RequestOptions doesn't expose a way to modify RequestOptions.agent.options
|
|
|
+ // we have to cast it to any and change it directly
|
|
|
+ agent.options = Object.assign(agent.options || {}, { rejectUnauthorized: false });
|
|
|
+ }
|
|
|
+ return agent;
|
|
|
+ }
|
|
|
+ _performExponentialBackoff(retryNumber) {
|
|
|
+ retryNumber = Math.min(ExponentialBackoffCeiling, retryNumber);
|
|
|
+ const ms = ExponentialBackoffTimeSlice * Math.pow(2, retryNumber);
|
|
|
+ return new Promise(resolve => setTimeout(() => resolve(), ms));
|
|
|
+ }
|
|
|
+ static dateTimeDeserializer(key, value) {
|
|
|
+ if (typeof value === 'string') {
|
|
|
+ let a = new Date(value);
|
|
|
+ if (!isNaN(a.valueOf())) {
|
|
|
+ return a;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return value;
|
|
|
+ }
|
|
|
+ async _processResponse(res, options) {
|
|
|
+ return new Promise(async (resolve, reject) => {
|
|
|
+ const statusCode = res.message.statusCode;
|
|
|
+ const response = {
|
|
|
+ statusCode: statusCode,
|
|
|
+ result: null,
|
|
|
+ headers: {}
|
|
|
+ };
|
|
|
+ // not found leads to null obj returned
|
|
|
+ if (statusCode == HttpCodes.NotFound) {
|
|
|
+ resolve(response);
|
|
|
+ }
|
|
|
+ let obj;
|
|
|
+ let contents;
|
|
|
+ // get the result from the body
|
|
|
+ try {
|
|
|
+ contents = await res.readBody();
|
|
|
+ if (contents && contents.length > 0) {
|
|
|
+ if (options && options.deserializeDates) {
|
|
|
+ obj = JSON.parse(contents, HttpClient.dateTimeDeserializer);
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ obj = JSON.parse(contents);
|
|
|
+ }
|
|
|
+ response.result = obj;
|
|
|
+ }
|
|
|
+ response.headers = res.message.headers;
|
|
|
+ }
|
|
|
+ catch (err) {
|
|
|
+ // Invalid resource (contents not json); leaving result obj null
|
|
|
+ }
|
|
|
+ // note that 3xx redirects are handled by the http layer.
|
|
|
+ if (statusCode > 299) {
|
|
|
+ let msg;
|
|
|
+ // if exception/error in body, attempt to get better error
|
|
|
+ if (obj && obj.message) {
|
|
|
+ msg = obj.message;
|
|
|
+ }
|
|
|
+ else if (contents && contents.length > 0) {
|
|
|
+ // it may be the case that the exception is in the body message as string
|
|
|
+ msg = contents;
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ msg = "Failed request: (" + statusCode + ")";
|
|
|
+ }
|
|
|
+ let err = new Error(msg);
|
|
|
+ // attach statusCode and body obj (if available) to the error object
|
|
|
+ err['statusCode'] = statusCode;
|
|
|
+ if (response.result) {
|
|
|
+ err['result'] = response.result;
|
|
|
+ }
|
|
|
+ reject(err);
|
|
|
+ }
|
|
|
+ else {
|
|
|
+ resolve(response);
|
|
|
+ }
|
|
|
+ });
|
|
|
+ }
|
|
|
+}
|
|
|
+exports.HttpClient = HttpClient;
|