diff options
Diffstat (limited to 'node_modules/typed-rest-client/HttpClient.js')
-rw-r--r-- | node_modules/typed-rest-client/HttpClient.js | 455 |
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; |