aboutsummaryrefslogtreecommitdiff
path: root/node_modules/typed-rest-client/HttpClient.js
diff options
context:
space:
mode:
Diffstat (limited to 'node_modules/typed-rest-client/HttpClient.js')
-rw-r--r--node_modules/typed-rest-client/HttpClient.js455
1 files changed, 455 insertions, 0 deletions
diff --git a/node_modules/typed-rest-client/HttpClient.js b/node_modules/typed-rest-client/HttpClient.js
new file mode 100644
index 0000000..169b8f7
--- /dev/null
+++ b/node_modules/typed-rest-client/HttpClient.js
@@ -0,0 +1,455 @@
+"use strict";
+// Copyright (c) Microsoft. All rights reserved.
+// Licensed under the MIT license. See LICENSE file in the project root for full license information.
+var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
+ return new (P || (P = Promise))(function (resolve, reject) {
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
+ function step(result) { result.done ? resolve(result.value) : new P(function (resolve) { resolve(result.value); }).then(fulfilled, rejected); }
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
+ });
+};
+Object.defineProperty(exports, "__esModule", { value: true });
+const url = require("url");
+const http = require("http");
+const https = require("https");
+let fs;
+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["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 = {}));
+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((resolve, reject) => __awaiter(this, void 0, void 0, function* () {
+ let output = '';
+ this.message.on('data', (chunk) => {
+ output += chunk;
+ });
+ this.message.on('end', () => {
+ resolve(output);
+ });
+ }));
+ }
+}
+exports.HttpClientResponse = HttpClientResponse;
+function isHttps(requestUrl) {
+ let parsedUrl = url.parse(requestUrl);
+ return parsedUrl.protocol === 'https:';
+}
+exports.isHttps = isHttps;
+var EnvironmentVariables;
+(function (EnvironmentVariables) {
+ EnvironmentVariables["HTTP_PROXY"] = "HTTP_PROXY";
+ EnvironmentVariables["HTTPS_PROXY"] = "HTTPS_PROXY";
+})(EnvironmentVariables || (EnvironmentVariables = {}));
+class HttpClient {
+ constructor(userAgent, handlers, requestOptions) {
+ this._ignoreSslError = false;
+ this._allowRedirects = true;
+ 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;
+ this._httpProxy = requestOptions.proxy;
+ if (requestOptions.proxy && requestOptions.proxy.proxyBypassHosts) {
+ this._httpProxyBypassHosts = [];
+ requestOptions.proxy.proxyBypassHosts.forEach(bypass => {
+ this._httpProxyBypassHosts.push(new RegExp(bypass, 'i'));
+ });
+ }
+ this._certConfig = requestOptions.cert;
+ if (this._certConfig) {
+ // If using cert, need fs
+ fs = require('fs');
+ // cache the cert content into memory, so we don't have to read it from disk every time
+ if (this._certConfig.caFile && fs.existsSync(this._certConfig.caFile)) {
+ this._ca = fs.readFileSync(this._certConfig.caFile, 'utf8');
+ }
+ if (this._certConfig.certFile && fs.existsSync(this._certConfig.certFile)) {
+ this._cert = fs.readFileSync(this._certConfig.certFile, 'utf8');
+ }
+ if (this._certConfig.keyFile && fs.existsSync(this._certConfig.keyFile)) {
+ this._key = fs.readFileSync(this._certConfig.keyFile, 'utf8');
+ }
+ }
+ if (requestOptions.allowRedirects != null) {
+ this._allowRedirects = requestOptions.allowRedirects;
+ }
+ 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);
+ }
+ /**
+ * Makes a raw http request.
+ * All other methods such as get, post, patch, and request ultimately call this.
+ * Prefer get, del, post and patch
+ */
+ request(verb, requestUrl, data, headers) {
+ return __awaiter(this, void 0, void 0, function* () {
+ if (this._disposed) {
+ throw new Error("Client has already been disposed.");
+ }
+ let info = this._prepareRequest(verb, requestUrl, 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 = yield 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;
+ }
+ // we need to finish reading the response before reassigning response
+ // which will leak the open socket.
+ yield response.readBody();
+ // let's make the request with the new redirectUrl
+ info = this._prepareRequest(verb, redirectUrl, headers);
+ response = yield 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) {
+ yield response.readBody();
+ yield 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;
+ let isDataString = typeof (data) === 'string';
+ 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();
+ }
+ }
+ _prepareRequest(method, requestUrl, headers) {
+ const info = {};
+ info.parsedUrl = url.parse(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);
+ info.options.headers["user-agent"] = this.userAgent;
+ info.options.agent = this._getAgent(requestUrl);
+ // gives handlers an opportunity to participate
+ if (this.handlers && !this._isPresigned(requestUrl)) {
+ this.handlers.forEach((handler) => {
+ handler.prepareRequest(info.options);
+ });
+ }
+ return info;
+ }
+ _isPresigned(requestUrl) {
+ if (this.requestOptions && this.requestOptions.presignedUrlPatterns) {
+ const patterns = this.requestOptions.presignedUrlPatterns;
+ for (let i = 0; i < patterns.length; i++) {
+ if (requestUrl.match(patterns[i])) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+ _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 || {});
+ }
+ _getAgent(requestUrl) {
+ let agent;
+ let proxy = this._getProxy(requestUrl);
+ let useProxy = proxy.proxyUrl && proxy.proxyUrl.hostname && !this._isBypassProxy(requestUrl);
+ 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;
+ }
+ let parsedUrl = url.parse(requestUrl);
+ 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: proxy.proxyAuth,
+ host: proxy.proxyUrl.hostname,
+ port: proxy.proxyUrl.port
+ },
+ };
+ let tunnelAgent;
+ const overHttps = proxy.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 });
+ }
+ if (usingSsl && this._certConfig) {
+ agent.options = Object.assign(agent.options || {}, { ca: this._ca, cert: this._cert, key: this._key, passphrase: this._certConfig.passphrase });
+ }
+ return agent;
+ }
+ _getProxy(requestUrl) {
+ const parsedUrl = url.parse(requestUrl);
+ let usingSsl = parsedUrl.protocol === 'https:';
+ let proxyConfig = this._httpProxy;
+ // fallback to http_proxy and https_proxy env
+ let https_proxy = process.env[EnvironmentVariables.HTTPS_PROXY];
+ let http_proxy = process.env[EnvironmentVariables.HTTP_PROXY];
+ if (!proxyConfig) {
+ if (https_proxy && usingSsl) {
+ proxyConfig = {
+ proxyUrl: https_proxy
+ };
+ }
+ else if (http_proxy) {
+ proxyConfig = {
+ proxyUrl: http_proxy
+ };
+ }
+ }
+ let proxyUrl;
+ let proxyAuth;
+ if (proxyConfig) {
+ if (proxyConfig.proxyUrl.length > 0) {
+ proxyUrl = url.parse(proxyConfig.proxyUrl);
+ }
+ if (proxyConfig.proxyUsername || proxyConfig.proxyPassword) {
+ proxyAuth = proxyConfig.proxyUsername + ":" + proxyConfig.proxyPassword;
+ }
+ }
+ return { proxyUrl: proxyUrl, proxyAuth: proxyAuth };
+ }
+ _isBypassProxy(requestUrl) {
+ if (!this._httpProxyBypassHosts) {
+ return false;
+ }
+ let bypass = false;
+ this._httpProxyBypassHosts.forEach(bypassHost => {
+ if (bypassHost.test(requestUrl)) {
+ bypass = true;
+ }
+ });
+ return bypass;
+ }
+ _performExponentialBackoff(retryNumber) {
+ retryNumber = Math.min(ExponentialBackoffCeiling, retryNumber);
+ const ms = ExponentialBackoffTimeSlice * Math.pow(2, retryNumber);
+ return new Promise(resolve => setTimeout(() => resolve(), ms));
+ }
+}
+exports.HttpClient = HttpClient;