|
|
'use strict';
var utils = require('./../utils'); var settle = require('./../core/settle'); var buildFullPath = require('../core/buildFullPath'); var buildURL = require('./../helpers/buildURL'); var http = require('http'); var https = require('https'); var httpFollow = require('follow-redirects').http; var httpsFollow = require('follow-redirects').https; var url = require('url'); var zlib = require('zlib'); var pkg = require('./../../package.json'); var createError = require('../core/createError'); var enhanceError = require('../core/enhanceError');
var isHttps = /https:?/;
/** * * @param {http.ClientRequestArgs} options * @param {AxiosProxyConfig} proxy * @param {string} location */ function setProxy(options, proxy, location) { options.hostname = proxy.host; options.host = proxy.host; options.port = proxy.port; options.path = location;
// Basic proxy authorization
if (proxy.auth) { var base64 = Buffer.from(proxy.auth.username + ':' + proxy.auth.password, 'utf8').toString('base64'); options.headers['Proxy-Authorization'] = 'Basic ' + base64; }
// If a proxy is used, any redirects must also pass through the proxy
options.beforeRedirect = function beforeRedirect(redirection) { redirection.headers.host = redirection.host; setProxy(redirection, proxy, redirection.href); }; }
/*eslint consistent-return:0*/ module.exports = function httpAdapter(config) { return new Promise(function dispatchHttpRequest(resolvePromise, rejectPromise) { var resolve = function resolve(value) { resolvePromise(value); }; var reject = function reject(value) { rejectPromise(value); }; var data = config.data; var headers = config.headers;
// Set User-Agent (required by some servers)
// Only set header if it hasn't been set in config
// See https://github.com/axios/axios/issues/69
if (!headers['User-Agent'] && !headers['user-agent']) { headers['User-Agent'] = 'axios/' + pkg.version; }
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(createError( 'Data after transformation must be a string, an ArrayBuffer, a Buffer, or a Stream', config )); }
// Add Content-Length header if data exists
headers['Content-Length'] = data.length; }
// HTTP basic authentication
var auth = undefined; if (config.auth) { var username = config.auth.username || ''; var password = config.auth.password || ''; auth = username + ':' + password; }
// Parse url
var fullPath = buildFullPath(config.baseURL, config.url); var parsed = url.parse(fullPath); var protocol = parsed.protocol || 'http:';
if (!auth && parsed.auth) { var urlAuth = parsed.auth.split(':'); var urlUsername = urlAuth[0] || ''; var urlPassword = urlAuth[1] || ''; auth = urlUsername + ':' + urlPassword; }
if (auth) { delete headers.Authorization; }
var isHttpsRequest = isHttps.test(protocol); var agent = isHttpsRequest ? config.httpsAgent : config.httpAgent;
var options = { path: buildURL(parsed.path, config.params, config.paramsSerializer).replace(/^\?/, ''), method: config.method.toUpperCase(), headers: headers, agent: agent, agents: { http: config.httpAgent, https: config.httpsAgent }, auth: auth };
if (config.socketPath) { options.socketPath = config.socketPath; } else { options.hostname = parsed.hostname; options.port = parsed.port; }
var proxy = config.proxy; if (!proxy && proxy !== false) { var proxyEnv = protocol.slice(0, -1) + '_proxy'; var proxyUrl = process.env[proxyEnv] || process.env[proxyEnv.toUpperCase()]; if (proxyUrl) { var parsedProxyUrl = url.parse(proxyUrl); var noProxyEnv = process.env.no_proxy || process.env.NO_PROXY; var shouldProxy = true;
if (noProxyEnv) { var noProxy = noProxyEnv.split(',').map(function trim(s) { return s.trim(); });
shouldProxy = !noProxy.some(function proxyMatch(proxyElement) { if (!proxyElement) { return false; } if (proxyElement === '*') { return true; } if (proxyElement[0] === '.' && parsed.hostname.substr(parsed.hostname.length - proxyElement.length) === proxyElement) { return true; }
return parsed.hostname === proxyElement; }); }
if (shouldProxy) { proxy = { host: parsedProxyUrl.hostname, port: parsedProxyUrl.port, protocol: parsedProxyUrl.protocol };
if (parsedProxyUrl.auth) { var proxyUrlAuth = parsedProxyUrl.auth.split(':'); proxy.auth = { username: proxyUrlAuth[0], password: proxyUrlAuth[1] }; } } } }
if (proxy) { options.headers.host = parsed.hostname + (parsed.port ? ':' + parsed.port : ''); setProxy(options, proxy, protocol + '//' + parsed.hostname + (parsed.port ? ':' + parsed.port : '') + options.path); }
var transport; var isHttpsProxy = isHttpsRequest && (proxy ? isHttps.test(proxy.protocol) : true); if (config.transport) { transport = config.transport; } else if (config.maxRedirects === 0) { transport = isHttpsProxy ? https : http; } else { if (config.maxRedirects) { options.maxRedirects = config.maxRedirects; } transport = isHttpsProxy ? httpsFollow : httpFollow; }
if (config.maxBodyLength > -1) { options.maxBodyLength = config.maxBodyLength; }
// Create the request
var req = transport.request(options, function handleResponse(res) { if (req.aborted) return;
// uncompress the response body transparently if required
var stream = res;
// return the last request in case of redirects
var lastRequest = res.req || req;
// if no content, is HEAD request or decompress disabled we should not decompress
if (res.statusCode !== 204 && lastRequest.method !== 'HEAD' && config.decompress !== false) { switch (res.headers['content-encoding']) { /*eslint default-case:0*/ case 'gzip': case 'compress': case 'deflate': // add the unzipper to the body stream processing pipeline
stream = stream.pipe(zlib.createUnzip());
// remove the content-encoding in order to not confuse downstream operations
delete res.headers['content-encoding']; break; } }
var response = { status: res.statusCode, statusText: res.statusMessage, headers: res.headers, config: config, request: lastRequest };
if (config.responseType === 'stream') { response.data = stream; settle(resolve, reject, response); } else { var responseBuffer = []; stream.on('data', function handleStreamData(chunk) { responseBuffer.push(chunk);
// make sure the content length is not over the maxContentLength if specified
if (config.maxContentLength > -1 && Buffer.concat(responseBuffer).length > config.maxContentLength) { stream.destroy(); reject(createError('maxContentLength size of ' + config.maxContentLength + ' exceeded', config, null, lastRequest)); } });
stream.on('error', function handleStreamError(err) { if (req.aborted) return; reject(enhanceError(err, config, null, lastRequest)); });
stream.on('end', function handleStreamEnd() { var responseData = Buffer.concat(responseBuffer); if (config.responseType !== 'arraybuffer') { responseData = responseData.toString(config.responseEncoding); if (!config.responseEncoding || config.responseEncoding === 'utf8') { responseData = utils.stripBOM(responseData); } }
response.data = responseData; settle(resolve, reject, response); }); } });
// Handle errors
req.on('error', function handleRequestError(err) { if (req.aborted && err.code !== 'ERR_FR_TOO_MANY_REDIRECTS') return; reject(enhanceError(err, config, null, req)); });
// Handle request timeout
if (config.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 devoring 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(config.timeout, function handleRequestTimeout() { req.abort(); reject(createError('timeout of ' + config.timeout + 'ms exceeded', config, 'ECONNABORTED', req)); }); }
if (config.cancelToken) { // Handle cancellation
config.cancelToken.promise.then(function onCanceled(cancel) { if (req.aborted) return;
req.abort(); reject(cancel); }); }
// Send the request
if (utils.isStream(data)) { data.on('error', function handleStreamError(err) { reject(enhanceError(err, config, null, req)); }).pipe(req); } else { req.end(data); } }); };
|