| 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202 |
- import utils from '../utils.js';
- import settle from '../core/settle.js';
- import buildFullPath from '../core/buildFullPath.js';
- import buildURL from '../helpers/buildURL.js';
- import { getProxyForUrl } from 'proxy-from-env';
- import http from 'http';
- import https from 'https';
- import http2 from 'http2';
- import util from 'util';
- import { resolve as resolvePath } from 'path';
- import followRedirects from 'follow-redirects';
- import zlib from 'zlib';
- import { VERSION } from '../env/data.js';
- import transitionalDefaults from '../defaults/transitional.js';
- import AxiosError from '../core/AxiosError.js';
- import CanceledError from '../cancel/CanceledError.js';
- import platform from '../platform/index.js';
- import fromDataURI from '../helpers/fromDataURI.js';
- import stream from 'stream';
- import AxiosHeaders from '../core/AxiosHeaders.js';
- import AxiosTransformStream from '../helpers/AxiosTransformStream.js';
- import { EventEmitter } from 'events';
- import formDataToStream from '../helpers/formDataToStream.js';
- import readBlob from '../helpers/readBlob.js';
- import ZlibHeaderTransformStream from '../helpers/ZlibHeaderTransformStream.js';
- import callbackify from '../helpers/callbackify.js';
- import shouldBypassProxy from '../helpers/shouldBypassProxy.js';
- import {
- progressEventReducer,
- progressEventDecorator,
- asyncDecorator,
- } from '../helpers/progressEventReducer.js';
- import estimateDataURLDecodedBytes from '../helpers/estimateDataURLDecodedBytes.js';
- const zlibOptions = {
- flush: zlib.constants.Z_SYNC_FLUSH,
- finishFlush: zlib.constants.Z_SYNC_FLUSH,
- };
- const brotliOptions = {
- flush: zlib.constants.BROTLI_OPERATION_FLUSH,
- finishFlush: zlib.constants.BROTLI_OPERATION_FLUSH,
- };
- const isBrotliSupported = utils.isFunction(zlib.createBrotliDecompress);
- const { http: httpFollow, https: httpsFollow } = followRedirects;
- const isHttps = /https:?/;
- const FORM_DATA_CONTENT_HEADERS = ['content-type', 'content-length'];
- function setFormDataHeaders(headers, formHeaders, policy) {
- if (policy !== 'content-only') {
- headers.set(formHeaders);
- return;
- }
- Object.entries(formHeaders).forEach(([key, val]) => {
- if (FORM_DATA_CONTENT_HEADERS.includes(key.toLowerCase())) {
- headers.set(key, val);
- }
- });
- }
- // Symbols used to bind a single 'error' listener to a pooled socket and track
- // the request currently owning that socket across keep-alive reuse (issue #10780).
- const kAxiosSocketListener = Symbol('axios.http.socketListener');
- const kAxiosCurrentReq = Symbol('axios.http.currentReq');
- const supportedProtocols = platform.protocols.map((protocol) => {
- return protocol + ':';
- });
- // Node's WHATWG URL parser returns `username` and `password` percent-encoded.
- // Decode before composing the `auth` option so credentials such as
- // `my%40email.com:pass` are sent as `my@email.com:pass`. Falls back to the
- // original value for malformed input so a bad encoding never throws.
- const decodeURIComponentSafe = (value) => {
- if (!utils.isString(value)) {
- return value;
- }
- try {
- return decodeURIComponent(value);
- } catch (error) {
- return value;
- }
- };
- const flushOnFinish = (stream, [throttled, flush]) => {
- stream.on('end', flush).on('error', flush);
- return throttled;
- };
- class Http2Sessions {
- constructor() {
- this.sessions = Object.create(null);
- }
- getSession(authority, options) {
- options = Object.assign(
- {
- sessionTimeout: 1000,
- },
- options
- );
- let authoritySessions = this.sessions[authority];
- if (authoritySessions) {
- let len = authoritySessions.length;
- for (let i = 0; i < len; i++) {
- const [sessionHandle, sessionOptions] = authoritySessions[i];
- if (
- !sessionHandle.destroyed &&
- !sessionHandle.closed &&
- util.isDeepStrictEqual(sessionOptions, options)
- ) {
- return sessionHandle;
- }
- }
- }
- const session = http2.connect(authority, options);
- let removed;
- const removeSession = () => {
- if (removed) {
- return;
- }
- removed = true;
- let entries = authoritySessions,
- len = entries.length,
- i = len;
- while (i--) {
- if (entries[i][0] === session) {
- if (len === 1) {
- delete this.sessions[authority];
- } else {
- entries.splice(i, 1);
- }
- if (!session.closed) {
- session.close();
- }
- return;
- }
- }
- };
- const originalRequestFn = session.request;
- const { sessionTimeout } = options;
- if (sessionTimeout != null) {
- let timer;
- let streamsCount = 0;
- session.request = function () {
- const stream = originalRequestFn.apply(this, arguments);
- streamsCount++;
- if (timer) {
- clearTimeout(timer);
- timer = null;
- }
- stream.once('close', () => {
- if (!--streamsCount) {
- timer = setTimeout(() => {
- timer = null;
- removeSession();
- }, sessionTimeout);
- }
- });
- return stream;
- };
- }
- session.once('close', removeSession);
- let entry = [session, options];
- authoritySessions
- ? authoritySessions.push(entry)
- : (authoritySessions = this.sessions[authority] = [entry]);
- return session;
- }
- }
- const http2Sessions = new Http2Sessions();
- /**
- * If the proxy or config beforeRedirects functions are defined, call them with the options
- * object.
- *
- * @param {Object<string, any>} options - The options object that was passed to the request.
- *
- * @returns {Object<string, any>}
- */
- function dispatchBeforeRedirect(options, responseDetails, requestDetails) {
- if (options.beforeRedirects.proxy) {
- options.beforeRedirects.proxy(options);
- }
- if (options.beforeRedirects.config) {
- options.beforeRedirects.config(options, responseDetails, requestDetails);
- }
- }
- /**
- * If the proxy or config afterRedirects functions are defined, call them with the options
- *
- * @param {http.ClientRequestArgs} options
- * @param {AxiosProxyConfig} configProxy configuration from Axios options object
- * @param {string} location
- *
- * @returns {http.ClientRequestArgs}
- */
- function setProxy(options, configProxy, location, isRedirect) {
- let proxy = configProxy;
- if (!proxy && proxy !== false) {
- const proxyUrl = getProxyForUrl(location);
- if (proxyUrl) {
- if (!shouldBypassProxy(location)) {
- proxy = new URL(proxyUrl);
- }
- }
- }
- // On redirect re-invocation, strip any stale Proxy-Authorization header carried
- // over from the prior request (e.g. new target no longer uses a proxy, or uses
- // a different proxy). Skip on the initial request so user-supplied headers are
- // preserved. Header names are case-insensitive, so remove every case variant.
- if (isRedirect && options.headers) {
- for (const name of Object.keys(options.headers)) {
- if (name.toLowerCase() === 'proxy-authorization') {
- delete options.headers[name];
- }
- }
- }
- if (proxy) {
- // Read proxy fields without traversing the prototype chain. URL instances expose
- // username/password/hostname/host/port/protocol via getters on URL.prototype (so
- // direct reads are shielded), but plain object proxies — and the `auth` field
- // (which URL does not expose) — must be guarded so a polluted Object.prototype
- // (e.g. Object.prototype.auth = { username, password }) cannot inject
- // attacker-controlled credentials into the Proxy-Authorization header or
- // redirect proxying to an attacker-controlled host.
- const isProxyURL = proxy instanceof URL;
- const readProxyField = (key) =>
- isProxyURL || utils.hasOwnProp(proxy, key) ? proxy[key] : undefined;
- const proxyUsername = readProxyField('username');
- const proxyPassword = readProxyField('password');
- let proxyAuth = utils.hasOwnProp(proxy, 'auth') ? proxy.auth : undefined;
- // Basic proxy authorization
- if (proxyUsername) {
- proxyAuth = (proxyUsername || '') + ':' + (proxyPassword || '');
- }
- if (proxyAuth) {
- // Support proxy auth object form. Read sub-fields via own-prop checks so a
- // plain object inheriting from polluted Object.prototype cannot leak creds.
- const authIsObject = typeof proxyAuth === 'object';
- const authUsername =
- authIsObject && utils.hasOwnProp(proxyAuth, 'username') ? proxyAuth.username : undefined;
- const authPassword =
- authIsObject && utils.hasOwnProp(proxyAuth, 'password') ? proxyAuth.password : undefined;
- const validProxyAuth = Boolean(authUsername || authPassword);
- if (validProxyAuth) {
- proxyAuth = (authUsername || '') + ':' + (authPassword || '');
- } else if (authIsObject) {
- throw new AxiosError('Invalid proxy authorization', AxiosError.ERR_BAD_OPTION, { proxy });
- }
- const base64 = Buffer.from(proxyAuth, 'utf8').toString('base64');
- options.headers['Proxy-Authorization'] = 'Basic ' + base64;
- }
- // Preserve a user-supplied Host header (case-insensitive) so callers can override
- // the value forwarded to the proxy; otherwise default to the request URL's host.
- let hasUserHostHeader = false;
- for (const name of Object.keys(options.headers)) {
- if (name.toLowerCase() === 'host') {
- hasUserHostHeader = true;
- break;
- }
- }
- if (!hasUserHostHeader) {
- options.headers.host = options.hostname + (options.port ? ':' + options.port : '');
- }
- const proxyHost = readProxyField('hostname') || readProxyField('host');
- options.hostname = proxyHost;
- // Replace 'host' since options is not a URL object
- options.host = proxyHost;
- options.port = readProxyField('port');
- options.path = location;
- const proxyProtocol = readProxyField('protocol');
- if (proxyProtocol) {
- options.protocol = proxyProtocol.includes(':') ? proxyProtocol : `${proxyProtocol}:`;
- }
- }
- options.beforeRedirects.proxy = function beforeRedirect(redirectOptions) {
- // Configure proxy for redirected request, passing the original config proxy to apply
- // the exact same logic as if the redirected request was performed by axios directly.
- setProxy(redirectOptions, configProxy, redirectOptions.href, true);
- };
- }
- const isHttpAdapterSupported =
- typeof process !== 'undefined' && utils.kindOf(process) === 'process';
- // temporary hotfix
- const wrapAsync = (asyncExecutor) => {
- return new Promise((resolve, reject) => {
- let onDone;
- let isDone;
- const done = (value, isRejected) => {
- if (isDone) return;
- isDone = true;
- onDone && onDone(value, isRejected);
- };
- const _resolve = (value) => {
- done(value);
- resolve(value);
- };
- const _reject = (reason) => {
- done(reason, true);
- reject(reason);
- };
- asyncExecutor(_resolve, _reject, (onDoneHandler) => (onDone = onDoneHandler)).catch(_reject);
- });
- };
- const resolveFamily = ({ address, family }) => {
- if (!utils.isString(address)) {
- throw TypeError('address must be a string');
- }
- return {
- address,
- family: family || (address.indexOf('.') < 0 ? 6 : 4),
- };
- };
- const buildAddressEntry = (address, family) =>
- resolveFamily(utils.isObject(address) ? address : { address, family });
- const http2Transport = {
- request(options, cb) {
- const authority =
- options.protocol +
- '//' +
- options.hostname +
- ':' +
- (options.port || (options.protocol === 'https:' ? 443 : 80));
- const { http2Options, headers } = options;
- const session = http2Sessions.getSession(authority, http2Options);
- const { HTTP2_HEADER_SCHEME, HTTP2_HEADER_METHOD, HTTP2_HEADER_PATH, HTTP2_HEADER_STATUS } =
- http2.constants;
- const http2Headers = {
- [HTTP2_HEADER_SCHEME]: options.protocol.replace(':', ''),
- [HTTP2_HEADER_METHOD]: options.method,
- [HTTP2_HEADER_PATH]: options.path,
- };
- utils.forEach(headers, (header, name) => {
- name.charAt(0) !== ':' && (http2Headers[name] = header);
- });
- const req = session.request(http2Headers);
- req.once('response', (responseHeaders) => {
- const response = req; //duplex
- responseHeaders = Object.assign({}, responseHeaders);
- const status = responseHeaders[HTTP2_HEADER_STATUS];
- delete responseHeaders[HTTP2_HEADER_STATUS];
- response.headers = responseHeaders;
- response.statusCode = +status;
- cb(response);
- });
- return req;
- },
- };
- /*eslint consistent-return:0*/
- export default isHttpAdapterSupported &&
- function httpAdapter(config) {
- return wrapAsync(async function dispatchHttpRequest(resolve, reject, onDone) {
- const own = (key) => (utils.hasOwnProp(config, key) ? config[key] : undefined);
- let data = own('data');
- let lookup = own('lookup');
- let family = own('family');
- let httpVersion = own('httpVersion');
- if (httpVersion === undefined) httpVersion = 1;
- let http2Options = own('http2Options');
- const responseType = own('responseType');
- const responseEncoding = own('responseEncoding');
- const method = config.method.toUpperCase();
- let isDone;
- let rejected = false;
- let req;
- let connectPhaseTimer;
- httpVersion = +httpVersion;
- if (Number.isNaN(httpVersion)) {
- throw TypeError(`Invalid protocol version: '${config.httpVersion}' is not a number`);
- }
- if (httpVersion !== 1 && httpVersion !== 2) {
- throw TypeError(`Unsupported protocol version '${httpVersion}'`);
- }
- const isHttp2 = httpVersion === 2;
- if (lookup) {
- const _lookup = callbackify(lookup, (value) => (utils.isArray(value) ? value : [value]));
- // hotfix to support opt.all option which is required for node 20.x
- lookup = (hostname, opt, cb) => {
- _lookup(hostname, opt, (err, arg0, arg1) => {
- if (err) {
- return cb(err);
- }
- const addresses = utils.isArray(arg0)
- ? arg0.map((addr) => buildAddressEntry(addr))
- : [buildAddressEntry(arg0, arg1)];
- opt.all ? cb(err, addresses) : cb(err, addresses[0].address, addresses[0].family);
- });
- };
- }
- const abortEmitter = new EventEmitter();
- function abort(reason) {
- try {
- abortEmitter.emit(
- 'abort',
- !reason || reason.type ? new CanceledError(null, config, req) : reason
- );
- } catch (err) {
- console.warn('emit error', err);
- }
- }
- function clearConnectPhaseTimer() {
- if (connectPhaseTimer) {
- clearTimeout(connectPhaseTimer);
- connectPhaseTimer = null;
- }
- }
- function createTimeoutError() {
- let timeoutErrorMessage = config.timeout
- ? 'timeout of ' + config.timeout + 'ms exceeded'
- : 'timeout exceeded';
- const transitional = config.transitional || transitionalDefaults;
- if (config.timeoutErrorMessage) {
- timeoutErrorMessage = config.timeoutErrorMessage;
- }
- return new AxiosError(
- timeoutErrorMessage,
- transitional.clarifyTimeoutError ? AxiosError.ETIMEDOUT : AxiosError.ECONNABORTED,
- config,
- req
- );
- }
- abortEmitter.once('abort', reject);
- const onFinished = () => {
- clearConnectPhaseTimer();
- if (config.cancelToken) {
- config.cancelToken.unsubscribe(abort);
- }
- if (config.signal) {
- config.signal.removeEventListener('abort', abort);
- }
- abortEmitter.removeAllListeners();
- };
- if (config.cancelToken || config.signal) {
- config.cancelToken && config.cancelToken.subscribe(abort);
- if (config.signal) {
- config.signal.aborted ? abort() : config.signal.addEventListener('abort', abort);
- }
- }
- onDone((response, isRejected) => {
- isDone = true;
- clearConnectPhaseTimer();
- if (isRejected) {
- rejected = true;
- onFinished();
- return;
- }
- const { data } = response;
- if (data instanceof stream.Readable || data instanceof stream.Duplex) {
- const offListeners = stream.finished(data, () => {
- offListeners();
- onFinished();
- });
- } else {
- onFinished();
- }
- });
- // Parse url
- const fullPath = buildFullPath(config.baseURL, config.url, config.allowAbsoluteUrls);
- const parsed = new URL(fullPath, platform.hasBrowserEnv ? platform.origin : undefined);
- const protocol = parsed.protocol || supportedProtocols[0];
- if (protocol === 'data:') {
- // Apply the same semantics as HTTP: only enforce if a finite, non-negative cap is set.
- if (config.maxContentLength > -1) {
- // Use the exact string passed to fromDataURI (config.url); fall back to fullPath if needed.
- const dataUrl = String(config.url || fullPath || '');
- const estimated = estimateDataURLDecodedBytes(dataUrl);
- if (estimated > config.maxContentLength) {
- return reject(
- new AxiosError(
- 'maxContentLength size of ' + config.maxContentLength + ' exceeded',
- AxiosError.ERR_BAD_RESPONSE,
- config
- )
- );
- }
- }
- let convertedData;
- if (method !== 'GET') {
- return settle(resolve, reject, {
- status: 405,
- statusText: 'method not allowed',
- headers: {},
- config,
- });
- }
- try {
- convertedData = fromDataURI(config.url, responseType === 'blob', {
- Blob: config.env && config.env.Blob,
- });
- } catch (err) {
- throw AxiosError.from(err, AxiosError.ERR_BAD_REQUEST, config);
- }
- if (responseType === 'text') {
- convertedData = convertedData.toString(responseEncoding);
- if (!responseEncoding || responseEncoding === 'utf8') {
- convertedData = utils.stripBOM(convertedData);
- }
- } else if (responseType === 'stream') {
- convertedData = stream.Readable.from(convertedData);
- }
- return settle(resolve, reject, {
- data: convertedData,
- status: 200,
- statusText: 'OK',
- headers: new AxiosHeaders(),
- config,
- });
- }
- if (supportedProtocols.indexOf(protocol) === -1) {
- return reject(
- new AxiosError('Unsupported protocol ' + protocol, AxiosError.ERR_BAD_REQUEST, config)
- );
- }
- const headers = AxiosHeaders.from(config.headers).normalize();
- // Set User-Agent (required by some servers)
- // See https://github.com/axios/axios/issues/69
- // User-Agent is specified; handle case where no UA header is desired
- // Only set header if it hasn't been set in config
- headers.set('User-Agent', 'axios/' + VERSION, false);
- const { onUploadProgress, onDownloadProgress } = config;
- const maxRate = config.maxRate;
- let maxUploadRate = undefined;
- let maxDownloadRate = undefined;
- // support for spec compliant FormData objects
- if (utils.isSpecCompliantForm(data)) {
- const userBoundary = headers.getContentType(/boundary=([-_\w\d]{10,70})/i);
- data = formDataToStream(
- data,
- (formHeaders) => {
- headers.set(formHeaders);
- },
- {
- tag: `axios-${VERSION}-boundary`,
- boundary: (userBoundary && userBoundary[1]) || undefined,
- }
- );
- // support for https://www.npmjs.com/package/form-data api
- } else if (
- utils.isFormData(data) &&
- utils.isFunction(data.getHeaders) &&
- data.getHeaders !== Object.prototype.getHeaders
- ) {
- setFormDataHeaders(headers, data.getHeaders(), own('formDataHeaderPolicy'));
- if (!headers.hasContentLength()) {
- try {
- const knownLength = await util.promisify(data.getLength).call(data);
- Number.isFinite(knownLength) &&
- knownLength >= 0 &&
- headers.setContentLength(knownLength);
- /*eslint no-empty:0*/
- } catch (e) {}
- }
- } else if (utils.isBlob(data) || utils.isFile(data)) {
- data.size && headers.setContentType(data.type || 'application/octet-stream');
- headers.setContentLength(data.size || 0);
- data = stream.Readable.from(readBlob(data));
- } else if (data && !utils.isStream(data)) {
- if (Buffer.isBuffer(data)) {
- // Nothing to do...
- } else if (utils.isArrayBuffer(data)) {
- data = Buffer.from(new Uint8Array(data));
- } else if (utils.isString(data)) {
- data = Buffer.from(data, 'utf-8');
- } else {
- return reject(
- new AxiosError(
- 'Data after transformation must be a string, an ArrayBuffer, a Buffer, or a Stream',
- AxiosError.ERR_BAD_REQUEST,
- config
- )
- );
- }
- // Add Content-Length header if data exists
- headers.setContentLength(data.length, false);
- if (config.maxBodyLength > -1 && data.length > config.maxBodyLength) {
- return reject(
- new AxiosError(
- 'Request body larger than maxBodyLength limit',
- AxiosError.ERR_BAD_REQUEST,
- config
- )
- );
- }
- }
- const contentLength = utils.toFiniteNumber(headers.getContentLength());
- if (utils.isArray(maxRate)) {
- maxUploadRate = maxRate[0];
- maxDownloadRate = maxRate[1];
- } else {
- maxUploadRate = maxDownloadRate = maxRate;
- }
- if (data && (onUploadProgress || maxUploadRate)) {
- if (!utils.isStream(data)) {
- data = stream.Readable.from(data, { objectMode: false });
- }
- data = stream.pipeline(
- [
- data,
- new AxiosTransformStream({
- maxRate: utils.toFiniteNumber(maxUploadRate),
- }),
- ],
- utils.noop
- );
- onUploadProgress &&
- data.on(
- 'progress',
- flushOnFinish(
- data,
- progressEventDecorator(
- contentLength,
- progressEventReducer(asyncDecorator(onUploadProgress), false, 3)
- )
- )
- );
- }
- // HTTP basic authentication
- let auth = undefined;
- const configAuth = own('auth');
- if (configAuth) {
- const username = configAuth.username || '';
- const password = configAuth.password || '';
- auth = username + ':' + password;
- }
- if (!auth && parsed.username) {
- const urlUsername = decodeURIComponentSafe(parsed.username);
- const urlPassword = decodeURIComponentSafe(parsed.password);
- auth = urlUsername + ':' + urlPassword;
- }
- auth && headers.delete('authorization');
- let path;
- try {
- path = buildURL(
- parsed.pathname + parsed.search,
- config.params,
- config.paramsSerializer
- ).replace(/^\?/, '');
- } catch (err) {
- const customErr = new Error(err.message);
- customErr.config = config;
- customErr.url = config.url;
- customErr.exists = true;
- return reject(customErr);
- }
- headers.set(
- 'Accept-Encoding',
- 'gzip, compress, deflate' + (isBrotliSupported ? ', br' : ''),
- false
- );
- // Null-prototype to block prototype pollution gadgets on properties read
- // directly by Node's http.request (e.g. insecureHTTPParser, lookup).
- const options = Object.assign(Object.create(null), {
- path,
- method: method,
- headers: headers.toJSON(),
- agents: { http: config.httpAgent, https: config.httpsAgent },
- auth,
- protocol,
- family,
- beforeRedirect: dispatchBeforeRedirect,
- beforeRedirects: Object.create(null),
- http2Options,
- });
- // cacheable-lookup integration hotfix
- !utils.isUndefined(lookup) && (options.lookup = lookup);
- if (config.socketPath) {
- if (typeof config.socketPath !== 'string') {
- return reject(
- new AxiosError('socketPath must be a string', AxiosError.ERR_BAD_OPTION_VALUE, config)
- );
- }
- if (config.allowedSocketPaths != null) {
- const allowed = Array.isArray(config.allowedSocketPaths)
- ? config.allowedSocketPaths
- : [config.allowedSocketPaths];
- const resolvedSocket = resolvePath(config.socketPath);
- const isAllowed = allowed.some(
- (entry) => typeof entry === 'string' && resolvePath(entry) === resolvedSocket
- );
- if (!isAllowed) {
- return reject(
- new AxiosError(
- `socketPath "${config.socketPath}" is not permitted by allowedSocketPaths`,
- AxiosError.ERR_BAD_OPTION_VALUE,
- config
- )
- );
- }
- }
- options.socketPath = config.socketPath;
- } else {
- options.hostname = parsed.hostname.startsWith('[')
- ? parsed.hostname.slice(1, -1)
- : parsed.hostname;
- options.port = parsed.port;
- setProxy(
- options,
- config.proxy,
- protocol + '//' + parsed.hostname + (parsed.port ? ':' + parsed.port : '') + options.path
- );
- }
- let transport;
- let isNativeTransport = false;
- const isHttpsRequest = isHttps.test(options.protocol);
- options.agent = isHttpsRequest ? config.httpsAgent : config.httpAgent;
- if (isHttp2) {
- transport = http2Transport;
- } else {
- const configTransport = own('transport');
- if (configTransport) {
- transport = configTransport;
- } else if (config.maxRedirects === 0) {
- transport = isHttpsRequest ? https : http;
- isNativeTransport = true;
- } else {
- if (config.maxRedirects) {
- options.maxRedirects = config.maxRedirects;
- }
- const configBeforeRedirect = own('beforeRedirect');
- if (configBeforeRedirect) {
- options.beforeRedirects.config = configBeforeRedirect;
- }
- transport = isHttpsRequest ? httpsFollow : httpFollow;
- }
- }
- if (config.maxBodyLength > -1) {
- options.maxBodyLength = config.maxBodyLength;
- } else {
- // follow-redirects does not skip comparison, so it should always succeed for axios -1 unlimited
- options.maxBodyLength = Infinity;
- }
- // Always set an explicit own value so a polluted
- // Object.prototype.insecureHTTPParser cannot enable the lenient parser
- // through Node's internal options copy
- options.insecureHTTPParser = Boolean(own('insecureHTTPParser'));
- // Create the request
- req = transport.request(options, function handleResponse(res) {
- clearConnectPhaseTimer();
- if (req.destroyed) return;
- const streams = [res];
- const responseLength = utils.toFiniteNumber(res.headers['content-length']);
- if (onDownloadProgress || maxDownloadRate) {
- const transformStream = new AxiosTransformStream({
- maxRate: utils.toFiniteNumber(maxDownloadRate),
- });
- onDownloadProgress &&
- transformStream.on(
- 'progress',
- flushOnFinish(
- transformStream,
- progressEventDecorator(
- responseLength,
- progressEventReducer(asyncDecorator(onDownloadProgress), true, 3)
- )
- )
- );
- streams.push(transformStream);
- }
- // decompress the response body transparently if required
- let responseStream = res;
- // return the last request in case of redirects
- const lastRequest = res.req || req;
- // if decompress disabled we should not decompress
- if (config.decompress !== false && res.headers['content-encoding']) {
- // if no content, but headers still say that it is encoded,
- // remove the header not confuse downstream operations
- if (method === 'HEAD' || res.statusCode === 204) {
- delete res.headers['content-encoding'];
- }
- switch ((res.headers['content-encoding'] || '').toLowerCase()) {
- /*eslint default-case:0*/
- case 'gzip':
- case 'x-gzip':
- case 'compress':
- case 'x-compress':
- // add the unzipper to the body stream processing pipeline
- streams.push(zlib.createUnzip(zlibOptions));
- // remove the content-encoding in order to not confuse downstream operations
- delete res.headers['content-encoding'];
- break;
- case 'deflate':
- streams.push(new ZlibHeaderTransformStream());
- // add the unzipper to the body stream processing pipeline
- streams.push(zlib.createUnzip(zlibOptions));
- // remove the content-encoding in order to not confuse downstream operations
- delete res.headers['content-encoding'];
- break;
- case 'br':
- if (isBrotliSupported) {
- streams.push(zlib.createBrotliDecompress(brotliOptions));
- delete res.headers['content-encoding'];
- }
- }
- }
- responseStream = streams.length > 1 ? stream.pipeline(streams, utils.noop) : streams[0];
- const response = {
- status: res.statusCode,
- statusText: res.statusMessage,
- headers: new AxiosHeaders(res.headers),
- config,
- request: lastRequest,
- };
- if (responseType === 'stream') {
- // Enforce maxContentLength on streamed responses; previously this
- // was applied only to buffered responses.
- if (config.maxContentLength > -1) {
- const limit = config.maxContentLength;
- const source = responseStream;
- async function* enforceMaxContentLength() {
- let totalResponseBytes = 0;
- for await (const chunk of source) {
- totalResponseBytes += chunk.length;
- if (totalResponseBytes > limit) {
- throw new AxiosError(
- 'maxContentLength size of ' + limit + ' exceeded',
- AxiosError.ERR_BAD_RESPONSE,
- config,
- lastRequest
- );
- }
- yield chunk;
- }
- }
- responseStream = stream.Readable.from(enforceMaxContentLength(), {
- objectMode: false,
- });
- }
- response.data = responseStream;
- settle(resolve, reject, response);
- } else {
- const responseBuffer = [];
- let totalResponseBytes = 0;
- responseStream.on('data', function handleStreamData(chunk) {
- responseBuffer.push(chunk);
- totalResponseBytes += chunk.length;
- // make sure the content length is not over the maxContentLength if specified
- if (config.maxContentLength > -1 && totalResponseBytes > config.maxContentLength) {
- // stream.destroy() emit aborted event before calling reject() on Node.js v16
- rejected = true;
- responseStream.destroy();
- abort(
- new AxiosError(
- 'maxContentLength size of ' + config.maxContentLength + ' exceeded',
- AxiosError.ERR_BAD_RESPONSE,
- config,
- lastRequest
- )
- );
- }
- });
- responseStream.on('aborted', function handlerStreamAborted() {
- if (rejected) {
- return;
- }
- const err = new AxiosError(
- 'stream has been aborted',
- AxiosError.ERR_BAD_RESPONSE,
- config,
- lastRequest,
- response
- );
- responseStream.destroy(err);
- reject(err);
- });
- responseStream.on('error', function handleStreamError(err) {
- if (rejected) return;
- reject(AxiosError.from(err, null, config, lastRequest, response));
- });
- responseStream.on('end', function handleStreamEnd() {
- try {
- let responseData =
- responseBuffer.length === 1 ? responseBuffer[0] : Buffer.concat(responseBuffer);
- if (responseType !== 'arraybuffer') {
- responseData = responseData.toString(responseEncoding);
- if (!responseEncoding || responseEncoding === 'utf8') {
- responseData = utils.stripBOM(responseData);
- }
- }
- response.data = responseData;
- } catch (err) {
- return reject(AxiosError.from(err, null, config, response.request, response));
- }
- settle(resolve, reject, response);
- });
- }
- abortEmitter.once('abort', (err) => {
- if (!responseStream.destroyed) {
- responseStream.emit('error', err);
- responseStream.destroy();
- }
- });
- });
- abortEmitter.once('abort', (err) => {
- if (req.close) {
- req.close();
- } else {
- req.destroy(err);
- }
- });
- // Handle errors
- req.on('error', function handleRequestError(err) {
- reject(AxiosError.from(err, null, config, req));
- });
- // set tcp keep alive to prevent drop connection by peer
- // Track every socket bound to this outer RedirectableRequest so a single
- // 'close' listener can release ownership on all of them. follow-redirects
- // re-emits the 'socket' event for each hop's native request onto the same
- // outer request, so attaching per-request listeners inside this handler
- // would accumulate across hops and trigger MaxListenersExceededWarning at
- // >= 11 redirects. Clearing only the last-bound socket would leave stale
- // kAxiosCurrentReq refs on earlier hop sockets returned to the keep-alive
- // pool, causing an idle-pool 'error' to be attributed to a closed req.
- const boundSockets = new Set();
- req.on('socket', function handleRequestSocket(socket) {
- // default interval of sending ack packet is 1 minute
- socket.setKeepAlive(true, 1000 * 60);
- // Install a single 'error' listener per socket (not per request) to avoid
- // accumulating listeners on pooled keep-alive sockets that get reassigned
- // to new requests before the previous request's 'close' fires (issue #10780).
- // The listener is bound to the socket's currently-active request via a
- // symbol, which is swapped as the socket is reassigned.
- if (!socket[kAxiosSocketListener]) {
- socket.on('error', function handleSocketError(err) {
- const current = socket[kAxiosCurrentReq];
- if (current && !current.destroyed) {
- current.destroy(err);
- }
- });
- socket[kAxiosSocketListener] = true;
- }
- socket[kAxiosCurrentReq] = req;
- boundSockets.add(socket);
- });
- req.once('close', function clearCurrentReq() {
- clearConnectPhaseTimer();
- for (const socket of boundSockets) {
- if (socket[kAxiosCurrentReq] === req) {
- socket[kAxiosCurrentReq] = null;
- }
- }
- boundSockets.clear();
- });
- // Handle request timeout
- if (config.timeout) {
- // This is forcing a int timeout to avoid problems if the `req` interface doesn't handle other types.
- const timeout = parseInt(config.timeout, 10);
- if (Number.isNaN(timeout)) {
- abort(
- new AxiosError(
- 'error trying to parse `config.timeout` to int',
- AxiosError.ERR_BAD_OPTION_VALUE,
- config,
- req
- )
- );
- return;
- }
- const handleTimeout = function handleTimeout() {
- if (isDone) return;
- abort(createTimeoutError());
- };
- if (isNativeTransport && timeout > 0) {
- // Native ClientRequest#setTimeout starts from the socket lifecycle and
- // may not fire while TCP connect is still pending. Mirror the
- // follow-redirects wall-clock timer for the maxRedirects === 0 path.
- connectPhaseTimer = setTimeout(handleTimeout, timeout);
- }
- // Sometime, the response will be very slow, and does not respond, the connect event will be block by event loop system.
- // And timer callback will be fired, and abort() will be invoked before connection, then get "socket hang up" and code ECONNRESET.
- // At this time, if we have a large number of request, nodejs will hang up some socket on background. and the number will up and up.
- // And then these socket which be hang up will devouring CPU little by little.
- // ClientRequest.setTimeout will be fired on the specify milliseconds, and can make sure that abort() will be fired after connect.
- req.setTimeout(timeout, handleTimeout);
- } else {
- // explicitly reset the socket timeout value for a possible `keep-alive` request
- req.setTimeout(0);
- }
- // Send the request
- if (utils.isStream(data)) {
- let ended = false;
- let errored = false;
- data.on('end', () => {
- ended = true;
- });
- data.once('error', (err) => {
- errored = true;
- req.destroy(err);
- });
- data.on('close', () => {
- if (!ended && !errored) {
- abort(new CanceledError('Request stream has been aborted', config, req));
- }
- });
- // Enforce maxBodyLength for streamed uploads on the native http/https
- // transport (maxRedirects === 0); follow-redirects enforces it on the
- // other path.
- let uploadStream = data;
- if (config.maxBodyLength > -1 && config.maxRedirects === 0) {
- const limit = config.maxBodyLength;
- let bytesSent = 0;
- uploadStream = stream.pipeline(
- [
- data,
- new stream.Transform({
- transform(chunk, _enc, cb) {
- bytesSent += chunk.length;
- if (bytesSent > limit) {
- return cb(
- new AxiosError(
- 'Request body larger than maxBodyLength limit',
- AxiosError.ERR_BAD_REQUEST,
- config,
- req
- )
- );
- }
- cb(null, chunk);
- },
- }),
- ],
- utils.noop
- );
- uploadStream.on('error', (err) => {
- if (!req.destroyed) req.destroy(err);
- });
- }
- uploadStream.pipe(req);
- } else {
- data && req.write(data);
- req.end();
- }
- });
- };
- export const __setProxy = setProxy;
|