http.js 29 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954
  1. import utils from '../utils.js';
  2. import settle from '../core/settle.js';
  3. import buildFullPath from '../core/buildFullPath.js';
  4. import buildURL from '../helpers/buildURL.js';
  5. import { getProxyForUrl } from 'proxy-from-env';
  6. import http from 'http';
  7. import https from 'https';
  8. import http2 from 'http2';
  9. import util from 'util';
  10. import followRedirects from 'follow-redirects';
  11. import zlib from 'zlib';
  12. import { VERSION } from '../env/data.js';
  13. import transitionalDefaults from '../defaults/transitional.js';
  14. import AxiosError from '../core/AxiosError.js';
  15. import CanceledError from '../cancel/CanceledError.js';
  16. import platform from '../platform/index.js';
  17. import fromDataURI from '../helpers/fromDataURI.js';
  18. import stream from 'stream';
  19. import AxiosHeaders from '../core/AxiosHeaders.js';
  20. import AxiosTransformStream from '../helpers/AxiosTransformStream.js';
  21. import { EventEmitter } from 'events';
  22. import formDataToStream from '../helpers/formDataToStream.js';
  23. import readBlob from '../helpers/readBlob.js';
  24. import ZlibHeaderTransformStream from '../helpers/ZlibHeaderTransformStream.js';
  25. import callbackify from '../helpers/callbackify.js';
  26. import shouldBypassProxy from '../helpers/shouldBypassProxy.js';
  27. import {
  28. progressEventReducer,
  29. progressEventDecorator,
  30. asyncDecorator,
  31. } from '../helpers/progressEventReducer.js';
  32. import estimateDataURLDecodedBytes from '../helpers/estimateDataURLDecodedBytes.js';
  33. const zlibOptions = {
  34. flush: zlib.constants.Z_SYNC_FLUSH,
  35. finishFlush: zlib.constants.Z_SYNC_FLUSH,
  36. };
  37. const brotliOptions = {
  38. flush: zlib.constants.BROTLI_OPERATION_FLUSH,
  39. finishFlush: zlib.constants.BROTLI_OPERATION_FLUSH,
  40. };
  41. const isBrotliSupported = utils.isFunction(zlib.createBrotliDecompress);
  42. const { http: httpFollow, https: httpsFollow } = followRedirects;
  43. const isHttps = /https:?/;
  44. const supportedProtocols = platform.protocols.map((protocol) => {
  45. return protocol + ':';
  46. });
  47. const flushOnFinish = (stream, [throttled, flush]) => {
  48. stream.on('end', flush).on('error', flush);
  49. return throttled;
  50. };
  51. class Http2Sessions {
  52. constructor() {
  53. this.sessions = Object.create(null);
  54. }
  55. getSession(authority, options) {
  56. options = Object.assign(
  57. {
  58. sessionTimeout: 1000,
  59. },
  60. options
  61. );
  62. let authoritySessions = this.sessions[authority];
  63. if (authoritySessions) {
  64. let len = authoritySessions.length;
  65. for (let i = 0; i < len; i++) {
  66. const [sessionHandle, sessionOptions] = authoritySessions[i];
  67. if (
  68. !sessionHandle.destroyed &&
  69. !sessionHandle.closed &&
  70. util.isDeepStrictEqual(sessionOptions, options)
  71. ) {
  72. return sessionHandle;
  73. }
  74. }
  75. }
  76. const session = http2.connect(authority, options);
  77. let removed;
  78. const removeSession = () => {
  79. if (removed) {
  80. return;
  81. }
  82. removed = true;
  83. let entries = authoritySessions,
  84. len = entries.length,
  85. i = len;
  86. while (i--) {
  87. if (entries[i][0] === session) {
  88. if (len === 1) {
  89. delete this.sessions[authority];
  90. } else {
  91. entries.splice(i, 1);
  92. }
  93. if (!session.closed) {
  94. session.close();
  95. }
  96. return;
  97. }
  98. }
  99. };
  100. const originalRequestFn = session.request;
  101. const { sessionTimeout } = options;
  102. if (sessionTimeout != null) {
  103. let timer;
  104. let streamsCount = 0;
  105. session.request = function () {
  106. const stream = originalRequestFn.apply(this, arguments);
  107. streamsCount++;
  108. if (timer) {
  109. clearTimeout(timer);
  110. timer = null;
  111. }
  112. stream.once('close', () => {
  113. if (!--streamsCount) {
  114. timer = setTimeout(() => {
  115. timer = null;
  116. removeSession();
  117. }, sessionTimeout);
  118. }
  119. });
  120. return stream;
  121. };
  122. }
  123. session.once('close', removeSession);
  124. let entry = [session, options];
  125. authoritySessions
  126. ? authoritySessions.push(entry)
  127. : (authoritySessions = this.sessions[authority] = [entry]);
  128. return session;
  129. }
  130. }
  131. const http2Sessions = new Http2Sessions();
  132. /**
  133. * If the proxy or config beforeRedirects functions are defined, call them with the options
  134. * object.
  135. *
  136. * @param {Object<string, any>} options - The options object that was passed to the request.
  137. *
  138. * @returns {Object<string, any>}
  139. */
  140. function dispatchBeforeRedirect(options, responseDetails) {
  141. if (options.beforeRedirects.proxy) {
  142. options.beforeRedirects.proxy(options);
  143. }
  144. if (options.beforeRedirects.config) {
  145. options.beforeRedirects.config(options, responseDetails);
  146. }
  147. }
  148. /**
  149. * If the proxy or config afterRedirects functions are defined, call them with the options
  150. *
  151. * @param {http.ClientRequestArgs} options
  152. * @param {AxiosProxyConfig} configProxy configuration from Axios options object
  153. * @param {string} location
  154. *
  155. * @returns {http.ClientRequestArgs}
  156. */
  157. function setProxy(options, configProxy, location) {
  158. let proxy = configProxy;
  159. if (!proxy && proxy !== false) {
  160. const proxyUrl = getProxyForUrl(location);
  161. if (proxyUrl) {
  162. if (!shouldBypassProxy(location)) {
  163. proxy = new URL(proxyUrl);
  164. }
  165. }
  166. }
  167. if (proxy) {
  168. // Basic proxy authorization
  169. if (proxy.username) {
  170. proxy.auth = (proxy.username || '') + ':' + (proxy.password || '');
  171. }
  172. if (proxy.auth) {
  173. // Support proxy auth object form
  174. const validProxyAuth = Boolean(proxy.auth.username || proxy.auth.password);
  175. if (validProxyAuth) {
  176. proxy.auth = (proxy.auth.username || '') + ':' + (proxy.auth.password || '');
  177. } else if (typeof proxy.auth === 'object') {
  178. throw new AxiosError('Invalid proxy authorization', AxiosError.ERR_BAD_OPTION, { proxy });
  179. }
  180. const base64 = Buffer.from(proxy.auth, 'utf8').toString('base64');
  181. options.headers['Proxy-Authorization'] = 'Basic ' + base64;
  182. }
  183. options.headers.host = options.hostname + (options.port ? ':' + options.port : '');
  184. const proxyHost = proxy.hostname || proxy.host;
  185. options.hostname = proxyHost;
  186. // Replace 'host' since options is not a URL object
  187. options.host = proxyHost;
  188. options.port = proxy.port;
  189. options.path = location;
  190. if (proxy.protocol) {
  191. options.protocol = proxy.protocol.includes(':') ? proxy.protocol : `${proxy.protocol}:`;
  192. }
  193. }
  194. options.beforeRedirects.proxy = function beforeRedirect(redirectOptions) {
  195. // Configure proxy for redirected request, passing the original config proxy to apply
  196. // the exact same logic as if the redirected request was performed by axios directly.
  197. setProxy(redirectOptions, configProxy, redirectOptions.href);
  198. };
  199. }
  200. const isHttpAdapterSupported =
  201. typeof process !== 'undefined' && utils.kindOf(process) === 'process';
  202. // temporary hotfix
  203. const wrapAsync = (asyncExecutor) => {
  204. return new Promise((resolve, reject) => {
  205. let onDone;
  206. let isDone;
  207. const done = (value, isRejected) => {
  208. if (isDone) return;
  209. isDone = true;
  210. onDone && onDone(value, isRejected);
  211. };
  212. const _resolve = (value) => {
  213. done(value);
  214. resolve(value);
  215. };
  216. const _reject = (reason) => {
  217. done(reason, true);
  218. reject(reason);
  219. };
  220. asyncExecutor(_resolve, _reject, (onDoneHandler) => (onDone = onDoneHandler)).catch(_reject);
  221. });
  222. };
  223. const resolveFamily = ({ address, family }) => {
  224. if (!utils.isString(address)) {
  225. throw TypeError('address must be a string');
  226. }
  227. return {
  228. address,
  229. family: family || (address.indexOf('.') < 0 ? 6 : 4),
  230. };
  231. };
  232. const buildAddressEntry = (address, family) =>
  233. resolveFamily(utils.isObject(address) ? address : { address, family });
  234. const http2Transport = {
  235. request(options, cb) {
  236. const authority =
  237. options.protocol +
  238. '//' +
  239. options.hostname +
  240. ':' +
  241. (options.port || (options.protocol === 'https:' ? 443 : 80));
  242. const { http2Options, headers } = options;
  243. const session = http2Sessions.getSession(authority, http2Options);
  244. const { HTTP2_HEADER_SCHEME, HTTP2_HEADER_METHOD, HTTP2_HEADER_PATH, HTTP2_HEADER_STATUS } =
  245. http2.constants;
  246. const http2Headers = {
  247. [HTTP2_HEADER_SCHEME]: options.protocol.replace(':', ''),
  248. [HTTP2_HEADER_METHOD]: options.method,
  249. [HTTP2_HEADER_PATH]: options.path,
  250. };
  251. utils.forEach(headers, (header, name) => {
  252. name.charAt(0) !== ':' && (http2Headers[name] = header);
  253. });
  254. const req = session.request(http2Headers);
  255. req.once('response', (responseHeaders) => {
  256. const response = req; //duplex
  257. responseHeaders = Object.assign({}, responseHeaders);
  258. const status = responseHeaders[HTTP2_HEADER_STATUS];
  259. delete responseHeaders[HTTP2_HEADER_STATUS];
  260. response.headers = responseHeaders;
  261. response.statusCode = +status;
  262. cb(response);
  263. });
  264. return req;
  265. },
  266. };
  267. /*eslint consistent-return:0*/
  268. export default isHttpAdapterSupported &&
  269. function httpAdapter(config) {
  270. return wrapAsync(async function dispatchHttpRequest(resolve, reject, onDone) {
  271. let { data, lookup, family, httpVersion = 1, http2Options } = config;
  272. const { responseType, responseEncoding } = config;
  273. const method = config.method.toUpperCase();
  274. let isDone;
  275. let rejected = false;
  276. let req;
  277. httpVersion = +httpVersion;
  278. if (Number.isNaN(httpVersion)) {
  279. throw TypeError(`Invalid protocol version: '${config.httpVersion}' is not a number`);
  280. }
  281. if (httpVersion !== 1 && httpVersion !== 2) {
  282. throw TypeError(`Unsupported protocol version '${httpVersion}'`);
  283. }
  284. const isHttp2 = httpVersion === 2;
  285. if (lookup) {
  286. const _lookup = callbackify(lookup, (value) => (utils.isArray(value) ? value : [value]));
  287. // hotfix to support opt.all option which is required for node 20.x
  288. lookup = (hostname, opt, cb) => {
  289. _lookup(hostname, opt, (err, arg0, arg1) => {
  290. if (err) {
  291. return cb(err);
  292. }
  293. const addresses = utils.isArray(arg0)
  294. ? arg0.map((addr) => buildAddressEntry(addr))
  295. : [buildAddressEntry(arg0, arg1)];
  296. opt.all ? cb(err, addresses) : cb(err, addresses[0].address, addresses[0].family);
  297. });
  298. };
  299. }
  300. const abortEmitter = new EventEmitter();
  301. function abort(reason) {
  302. try {
  303. abortEmitter.emit(
  304. 'abort',
  305. !reason || reason.type ? new CanceledError(null, config, req) : reason
  306. );
  307. } catch (err) {
  308. console.warn('emit error', err);
  309. }
  310. }
  311. abortEmitter.once('abort', reject);
  312. const onFinished = () => {
  313. if (config.cancelToken) {
  314. config.cancelToken.unsubscribe(abort);
  315. }
  316. if (config.signal) {
  317. config.signal.removeEventListener('abort', abort);
  318. }
  319. abortEmitter.removeAllListeners();
  320. };
  321. if (config.cancelToken || config.signal) {
  322. config.cancelToken && config.cancelToken.subscribe(abort);
  323. if (config.signal) {
  324. config.signal.aborted ? abort() : config.signal.addEventListener('abort', abort);
  325. }
  326. }
  327. onDone((response, isRejected) => {
  328. isDone = true;
  329. if (isRejected) {
  330. rejected = true;
  331. onFinished();
  332. return;
  333. }
  334. const { data } = response;
  335. if (data instanceof stream.Readable || data instanceof stream.Duplex) {
  336. const offListeners = stream.finished(data, () => {
  337. offListeners();
  338. onFinished();
  339. });
  340. } else {
  341. onFinished();
  342. }
  343. });
  344. // Parse url
  345. const fullPath = buildFullPath(config.baseURL, config.url, config.allowAbsoluteUrls);
  346. const parsed = new URL(fullPath, platform.hasBrowserEnv ? platform.origin : undefined);
  347. const protocol = parsed.protocol || supportedProtocols[0];
  348. if (protocol === 'data:') {
  349. // Apply the same semantics as HTTP: only enforce if a finite, non-negative cap is set.
  350. if (config.maxContentLength > -1) {
  351. // Use the exact string passed to fromDataURI (config.url); fall back to fullPath if needed.
  352. const dataUrl = String(config.url || fullPath || '');
  353. const estimated = estimateDataURLDecodedBytes(dataUrl);
  354. if (estimated > config.maxContentLength) {
  355. return reject(
  356. new AxiosError(
  357. 'maxContentLength size of ' + config.maxContentLength + ' exceeded',
  358. AxiosError.ERR_BAD_RESPONSE,
  359. config
  360. )
  361. );
  362. }
  363. }
  364. let convertedData;
  365. if (method !== 'GET') {
  366. return settle(resolve, reject, {
  367. status: 405,
  368. statusText: 'method not allowed',
  369. headers: {},
  370. config,
  371. });
  372. }
  373. try {
  374. convertedData = fromDataURI(config.url, responseType === 'blob', {
  375. Blob: config.env && config.env.Blob,
  376. });
  377. } catch (err) {
  378. throw AxiosError.from(err, AxiosError.ERR_BAD_REQUEST, config);
  379. }
  380. if (responseType === 'text') {
  381. convertedData = convertedData.toString(responseEncoding);
  382. if (!responseEncoding || responseEncoding === 'utf8') {
  383. convertedData = utils.stripBOM(convertedData);
  384. }
  385. } else if (responseType === 'stream') {
  386. convertedData = stream.Readable.from(convertedData);
  387. }
  388. return settle(resolve, reject, {
  389. data: convertedData,
  390. status: 200,
  391. statusText: 'OK',
  392. headers: new AxiosHeaders(),
  393. config,
  394. });
  395. }
  396. if (supportedProtocols.indexOf(protocol) === -1) {
  397. return reject(
  398. new AxiosError('Unsupported protocol ' + protocol, AxiosError.ERR_BAD_REQUEST, config)
  399. );
  400. }
  401. const headers = AxiosHeaders.from(config.headers).normalize();
  402. // Set User-Agent (required by some servers)
  403. // See https://github.com/axios/axios/issues/69
  404. // User-Agent is specified; handle case where no UA header is desired
  405. // Only set header if it hasn't been set in config
  406. headers.set('User-Agent', 'axios/' + VERSION, false);
  407. const { onUploadProgress, onDownloadProgress } = config;
  408. const maxRate = config.maxRate;
  409. let maxUploadRate = undefined;
  410. let maxDownloadRate = undefined;
  411. // support for spec compliant FormData objects
  412. if (utils.isSpecCompliantForm(data)) {
  413. const userBoundary = headers.getContentType(/boundary=([-_\w\d]{10,70})/i);
  414. data = formDataToStream(
  415. data,
  416. (formHeaders) => {
  417. headers.set(formHeaders);
  418. },
  419. {
  420. tag: `axios-${VERSION}-boundary`,
  421. boundary: (userBoundary && userBoundary[1]) || undefined,
  422. }
  423. );
  424. // support for https://www.npmjs.com/package/form-data api
  425. } else if (utils.isFormData(data) && utils.isFunction(data.getHeaders)) {
  426. headers.set(data.getHeaders());
  427. if (!headers.hasContentLength()) {
  428. try {
  429. const knownLength = await util.promisify(data.getLength).call(data);
  430. Number.isFinite(knownLength) &&
  431. knownLength >= 0 &&
  432. headers.setContentLength(knownLength);
  433. /*eslint no-empty:0*/
  434. } catch (e) {}
  435. }
  436. } else if (utils.isBlob(data) || utils.isFile(data)) {
  437. data.size && headers.setContentType(data.type || 'application/octet-stream');
  438. headers.setContentLength(data.size || 0);
  439. data = stream.Readable.from(readBlob(data));
  440. } else if (data && !utils.isStream(data)) {
  441. if (Buffer.isBuffer(data)) {
  442. // Nothing to do...
  443. } else if (utils.isArrayBuffer(data)) {
  444. data = Buffer.from(new Uint8Array(data));
  445. } else if (utils.isString(data)) {
  446. data = Buffer.from(data, 'utf-8');
  447. } else {
  448. return reject(
  449. new AxiosError(
  450. 'Data after transformation must be a string, an ArrayBuffer, a Buffer, or a Stream',
  451. AxiosError.ERR_BAD_REQUEST,
  452. config
  453. )
  454. );
  455. }
  456. // Add Content-Length header if data exists
  457. headers.setContentLength(data.length, false);
  458. if (config.maxBodyLength > -1 && data.length > config.maxBodyLength) {
  459. return reject(
  460. new AxiosError(
  461. 'Request body larger than maxBodyLength limit',
  462. AxiosError.ERR_BAD_REQUEST,
  463. config
  464. )
  465. );
  466. }
  467. }
  468. const contentLength = utils.toFiniteNumber(headers.getContentLength());
  469. if (utils.isArray(maxRate)) {
  470. maxUploadRate = maxRate[0];
  471. maxDownloadRate = maxRate[1];
  472. } else {
  473. maxUploadRate = maxDownloadRate = maxRate;
  474. }
  475. if (data && (onUploadProgress || maxUploadRate)) {
  476. if (!utils.isStream(data)) {
  477. data = stream.Readable.from(data, { objectMode: false });
  478. }
  479. data = stream.pipeline(
  480. [
  481. data,
  482. new AxiosTransformStream({
  483. maxRate: utils.toFiniteNumber(maxUploadRate),
  484. }),
  485. ],
  486. utils.noop
  487. );
  488. onUploadProgress &&
  489. data.on(
  490. 'progress',
  491. flushOnFinish(
  492. data,
  493. progressEventDecorator(
  494. contentLength,
  495. progressEventReducer(asyncDecorator(onUploadProgress), false, 3)
  496. )
  497. )
  498. );
  499. }
  500. // HTTP basic authentication
  501. let auth = undefined;
  502. if (config.auth) {
  503. const username = config.auth.username || '';
  504. const password = config.auth.password || '';
  505. auth = username + ':' + password;
  506. }
  507. if (!auth && parsed.username) {
  508. const urlUsername = parsed.username;
  509. const urlPassword = parsed.password;
  510. auth = urlUsername + ':' + urlPassword;
  511. }
  512. auth && headers.delete('authorization');
  513. let path;
  514. try {
  515. path = buildURL(
  516. parsed.pathname + parsed.search,
  517. config.params,
  518. config.paramsSerializer
  519. ).replace(/^\?/, '');
  520. } catch (err) {
  521. const customErr = new Error(err.message);
  522. customErr.config = config;
  523. customErr.url = config.url;
  524. customErr.exists = true;
  525. return reject(customErr);
  526. }
  527. headers.set(
  528. 'Accept-Encoding',
  529. 'gzip, compress, deflate' + (isBrotliSupported ? ', br' : ''),
  530. false
  531. );
  532. const options = {
  533. path,
  534. method: method,
  535. headers: headers.toJSON(),
  536. agents: { http: config.httpAgent, https: config.httpsAgent },
  537. auth,
  538. protocol,
  539. family,
  540. beforeRedirect: dispatchBeforeRedirect,
  541. beforeRedirects: {},
  542. http2Options,
  543. };
  544. // cacheable-lookup integration hotfix
  545. !utils.isUndefined(lookup) && (options.lookup = lookup);
  546. if (config.socketPath) {
  547. options.socketPath = config.socketPath;
  548. } else {
  549. options.hostname = parsed.hostname.startsWith('[')
  550. ? parsed.hostname.slice(1, -1)
  551. : parsed.hostname;
  552. options.port = parsed.port;
  553. setProxy(
  554. options,
  555. config.proxy,
  556. protocol + '//' + parsed.hostname + (parsed.port ? ':' + parsed.port : '') + options.path
  557. );
  558. }
  559. let transport;
  560. const isHttpsRequest = isHttps.test(options.protocol);
  561. options.agent = isHttpsRequest ? config.httpsAgent : config.httpAgent;
  562. if (isHttp2) {
  563. transport = http2Transport;
  564. } else {
  565. if (config.transport) {
  566. transport = config.transport;
  567. } else if (config.maxRedirects === 0) {
  568. transport = isHttpsRequest ? https : http;
  569. } else {
  570. if (config.maxRedirects) {
  571. options.maxRedirects = config.maxRedirects;
  572. }
  573. if (config.beforeRedirect) {
  574. options.beforeRedirects.config = config.beforeRedirect;
  575. }
  576. transport = isHttpsRequest ? httpsFollow : httpFollow;
  577. }
  578. }
  579. if (config.maxBodyLength > -1) {
  580. options.maxBodyLength = config.maxBodyLength;
  581. } else {
  582. // follow-redirects does not skip comparison, so it should always succeed for axios -1 unlimited
  583. options.maxBodyLength = Infinity;
  584. }
  585. if (config.insecureHTTPParser) {
  586. options.insecureHTTPParser = config.insecureHTTPParser;
  587. }
  588. // Create the request
  589. req = transport.request(options, function handleResponse(res) {
  590. if (req.destroyed) return;
  591. const streams = [res];
  592. const responseLength = utils.toFiniteNumber(res.headers['content-length']);
  593. if (onDownloadProgress || maxDownloadRate) {
  594. const transformStream = new AxiosTransformStream({
  595. maxRate: utils.toFiniteNumber(maxDownloadRate),
  596. });
  597. onDownloadProgress &&
  598. transformStream.on(
  599. 'progress',
  600. flushOnFinish(
  601. transformStream,
  602. progressEventDecorator(
  603. responseLength,
  604. progressEventReducer(asyncDecorator(onDownloadProgress), true, 3)
  605. )
  606. )
  607. );
  608. streams.push(transformStream);
  609. }
  610. // decompress the response body transparently if required
  611. let responseStream = res;
  612. // return the last request in case of redirects
  613. const lastRequest = res.req || req;
  614. // if decompress disabled we should not decompress
  615. if (config.decompress !== false && res.headers['content-encoding']) {
  616. // if no content, but headers still say that it is encoded,
  617. // remove the header not confuse downstream operations
  618. if (method === 'HEAD' || res.statusCode === 204) {
  619. delete res.headers['content-encoding'];
  620. }
  621. switch ((res.headers['content-encoding'] || '').toLowerCase()) {
  622. /*eslint default-case:0*/
  623. case 'gzip':
  624. case 'x-gzip':
  625. case 'compress':
  626. case 'x-compress':
  627. // add the unzipper to the body stream processing pipeline
  628. streams.push(zlib.createUnzip(zlibOptions));
  629. // remove the content-encoding in order to not confuse downstream operations
  630. delete res.headers['content-encoding'];
  631. break;
  632. case 'deflate':
  633. streams.push(new ZlibHeaderTransformStream());
  634. // add the unzipper to the body stream processing pipeline
  635. streams.push(zlib.createUnzip(zlibOptions));
  636. // remove the content-encoding in order to not confuse downstream operations
  637. delete res.headers['content-encoding'];
  638. break;
  639. case 'br':
  640. if (isBrotliSupported) {
  641. streams.push(zlib.createBrotliDecompress(brotliOptions));
  642. delete res.headers['content-encoding'];
  643. }
  644. }
  645. }
  646. responseStream = streams.length > 1 ? stream.pipeline(streams, utils.noop) : streams[0];
  647. const response = {
  648. status: res.statusCode,
  649. statusText: res.statusMessage,
  650. headers: new AxiosHeaders(res.headers),
  651. config,
  652. request: lastRequest,
  653. };
  654. if (responseType === 'stream') {
  655. response.data = responseStream;
  656. settle(resolve, reject, response);
  657. } else {
  658. const responseBuffer = [];
  659. let totalResponseBytes = 0;
  660. responseStream.on('data', function handleStreamData(chunk) {
  661. responseBuffer.push(chunk);
  662. totalResponseBytes += chunk.length;
  663. // make sure the content length is not over the maxContentLength if specified
  664. if (config.maxContentLength > -1 && totalResponseBytes > config.maxContentLength) {
  665. // stream.destroy() emit aborted event before calling reject() on Node.js v16
  666. rejected = true;
  667. responseStream.destroy();
  668. abort(
  669. new AxiosError(
  670. 'maxContentLength size of ' + config.maxContentLength + ' exceeded',
  671. AxiosError.ERR_BAD_RESPONSE,
  672. config,
  673. lastRequest
  674. )
  675. );
  676. }
  677. });
  678. responseStream.on('aborted', function handlerStreamAborted() {
  679. if (rejected) {
  680. return;
  681. }
  682. const err = new AxiosError(
  683. 'stream has been aborted',
  684. AxiosError.ERR_BAD_RESPONSE,
  685. config,
  686. lastRequest
  687. );
  688. responseStream.destroy(err);
  689. reject(err);
  690. });
  691. responseStream.on('error', function handleStreamError(err) {
  692. if (req.destroyed) return;
  693. reject(AxiosError.from(err, null, config, lastRequest));
  694. });
  695. responseStream.on('end', function handleStreamEnd() {
  696. try {
  697. let responseData =
  698. responseBuffer.length === 1 ? responseBuffer[0] : Buffer.concat(responseBuffer);
  699. if (responseType !== 'arraybuffer') {
  700. responseData = responseData.toString(responseEncoding);
  701. if (!responseEncoding || responseEncoding === 'utf8') {
  702. responseData = utils.stripBOM(responseData);
  703. }
  704. }
  705. response.data = responseData;
  706. } catch (err) {
  707. return reject(AxiosError.from(err, null, config, response.request, response));
  708. }
  709. settle(resolve, reject, response);
  710. });
  711. }
  712. abortEmitter.once('abort', (err) => {
  713. if (!responseStream.destroyed) {
  714. responseStream.emit('error', err);
  715. responseStream.destroy();
  716. }
  717. });
  718. });
  719. abortEmitter.once('abort', (err) => {
  720. if (req.close) {
  721. req.close();
  722. } else {
  723. req.destroy(err);
  724. }
  725. });
  726. // Handle errors
  727. req.on('error', function handleRequestError(err) {
  728. reject(AxiosError.from(err, null, config, req));
  729. });
  730. // set tcp keep alive to prevent drop connection by peer
  731. req.on('socket', function handleRequestSocket(socket) {
  732. // default interval of sending ack packet is 1 minute
  733. socket.setKeepAlive(true, 1000 * 60);
  734. });
  735. // Handle request timeout
  736. if (config.timeout) {
  737. // This is forcing a int timeout to avoid problems if the `req` interface doesn't handle other types.
  738. const timeout = parseInt(config.timeout, 10);
  739. if (Number.isNaN(timeout)) {
  740. abort(
  741. new AxiosError(
  742. 'error trying to parse `config.timeout` to int',
  743. AxiosError.ERR_BAD_OPTION_VALUE,
  744. config,
  745. req
  746. )
  747. );
  748. return;
  749. }
  750. // Sometime, the response will be very slow, and does not respond, the connect event will be block by event loop system.
  751. // And timer callback will be fired, and abort() will be invoked before connection, then get "socket hang up" and code ECONNRESET.
  752. // 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.
  753. // And then these socket which be hang up will devouring CPU little by little.
  754. // ClientRequest.setTimeout will be fired on the specify milliseconds, and can make sure that abort() will be fired after connect.
  755. req.setTimeout(timeout, function handleRequestTimeout() {
  756. if (isDone) return;
  757. let timeoutErrorMessage = config.timeout
  758. ? 'timeout of ' + config.timeout + 'ms exceeded'
  759. : 'timeout exceeded';
  760. const transitional = config.transitional || transitionalDefaults;
  761. if (config.timeoutErrorMessage) {
  762. timeoutErrorMessage = config.timeoutErrorMessage;
  763. }
  764. abort(
  765. new AxiosError(
  766. timeoutErrorMessage,
  767. transitional.clarifyTimeoutError ? AxiosError.ETIMEDOUT : AxiosError.ECONNABORTED,
  768. config,
  769. req
  770. )
  771. );
  772. });
  773. } else {
  774. // explicitly reset the socket timeout value for a possible `keep-alive` request
  775. req.setTimeout(0);
  776. }
  777. // Send the request
  778. if (utils.isStream(data)) {
  779. let ended = false;
  780. let errored = false;
  781. data.on('end', () => {
  782. ended = true;
  783. });
  784. data.once('error', (err) => {
  785. errored = true;
  786. req.destroy(err);
  787. });
  788. data.on('close', () => {
  789. if (!ended && !errored) {
  790. abort(new CanceledError('Request stream has been aborted', config, req));
  791. }
  792. });
  793. data.pipe(req);
  794. } else {
  795. data && req.write(data);
  796. req.end();
  797. }
  798. });
  799. };
  800. export const __setProxy = setProxy;