source: imaps-frontend/node_modules/axios/lib/adapters/http.js

main
Last change on this file was d565449, checked in by stefan toskovski <stefantoska84@…>, 4 weeks ago

Update repo after prototype presentation

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