source: node_modules/axios/lib/adapters/http.js@ 9293100

main
Last change on this file since 9293100 was d24f17c, checked in by Aleksandar Panovski <apano77@…>, 15 months ago

Initial commit

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