| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501 |
- "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;
|