Path: blob/master/node_modules/axios/lib/adapters/http.js
1126 views
'use strict';12var utils = require('./../utils');3var settle = require('./../core/settle');4var buildFullPath = require('../core/buildFullPath');5var buildURL = require('./../helpers/buildURL');6var http = require('http');7var https = require('https');8var httpFollow = require('follow-redirects').http;9var httpsFollow = require('follow-redirects').https;10var url = require('url');11var zlib = require('zlib');12var VERSION = require('./../env/data').version;13var createError = require('../core/createError');14var enhanceError = require('../core/enhanceError');15var defaults = require('../defaults');16var Cancel = require('../cancel/Cancel');1718var isHttps = /https:?/;1920/**21*22* @param {http.ClientRequestArgs} options23* @param {AxiosProxyConfig} proxy24* @param {string} location25*/26function setProxy(options, proxy, location) {27options.hostname = proxy.host;28options.host = proxy.host;29options.port = proxy.port;30options.path = location;3132// Basic proxy authorization33if (proxy.auth) {34var base64 = Buffer.from(proxy.auth.username + ':' + proxy.auth.password, 'utf8').toString('base64');35options.headers['Proxy-Authorization'] = 'Basic ' + base64;36}3738// If a proxy is used, any redirects must also pass through the proxy39options.beforeRedirect = function beforeRedirect(redirection) {40redirection.headers.host = redirection.host;41setProxy(redirection, proxy, redirection.href);42};43}4445/*eslint consistent-return:0*/46module.exports = function httpAdapter(config) {47return new Promise(function dispatchHttpRequest(resolvePromise, rejectPromise) {48var onCanceled;49function done() {50if (config.cancelToken) {51config.cancelToken.unsubscribe(onCanceled);52}5354if (config.signal) {55config.signal.removeEventListener('abort', onCanceled);56}57}58var resolve = function resolve(value) {59done();60resolvePromise(value);61};62var reject = function reject(value) {63done();64rejectPromise(value);65};66var data = config.data;67var headers = config.headers;68var headerNames = {};6970Object.keys(headers).forEach(function storeLowerName(name) {71headerNames[name.toLowerCase()] = name;72});7374// Set User-Agent (required by some servers)75// See https://github.com/axios/axios/issues/6976if ('user-agent' in headerNames) {77// User-Agent is specified; handle case where no UA header is desired78if (!headers[headerNames['user-agent']]) {79delete headers[headerNames['user-agent']];80}81// Otherwise, use specified value82} else {83// Only set header if it hasn't been set in config84headers['User-Agent'] = 'axios/' + VERSION;85}8687if (data && !utils.isStream(data)) {88if (Buffer.isBuffer(data)) {89// Nothing to do...90} else if (utils.isArrayBuffer(data)) {91data = Buffer.from(new Uint8Array(data));92} else if (utils.isString(data)) {93data = Buffer.from(data, 'utf-8');94} else {95return reject(createError(96'Data after transformation must be a string, an ArrayBuffer, a Buffer, or a Stream',97config98));99}100101// Add Content-Length header if data exists102if (!headerNames['content-length']) {103headers['Content-Length'] = data.length;104}105}106107// HTTP basic authentication108var auth = undefined;109if (config.auth) {110var username = config.auth.username || '';111var password = config.auth.password || '';112auth = username + ':' + password;113}114115// Parse url116var fullPath = buildFullPath(config.baseURL, config.url);117var parsed = url.parse(fullPath);118var protocol = parsed.protocol || 'http:';119120if (!auth && parsed.auth) {121var urlAuth = parsed.auth.split(':');122var urlUsername = urlAuth[0] || '';123var urlPassword = urlAuth[1] || '';124auth = urlUsername + ':' + urlPassword;125}126127if (auth && headerNames.authorization) {128delete headers[headerNames.authorization];129}130131var isHttpsRequest = isHttps.test(protocol);132var agent = isHttpsRequest ? config.httpsAgent : config.httpAgent;133134var options = {135path: buildURL(parsed.path, config.params, config.paramsSerializer).replace(/^\?/, ''),136method: config.method.toUpperCase(),137headers: headers,138agent: agent,139agents: { http: config.httpAgent, https: config.httpsAgent },140auth: auth141};142143if (config.socketPath) {144options.socketPath = config.socketPath;145} else {146options.hostname = parsed.hostname;147options.port = parsed.port;148}149150var proxy = config.proxy;151if (!proxy && proxy !== false) {152var proxyEnv = protocol.slice(0, -1) + '_proxy';153var proxyUrl = process.env[proxyEnv] || process.env[proxyEnv.toUpperCase()];154if (proxyUrl) {155var parsedProxyUrl = url.parse(proxyUrl);156var noProxyEnv = process.env.no_proxy || process.env.NO_PROXY;157var shouldProxy = true;158159if (noProxyEnv) {160var noProxy = noProxyEnv.split(',').map(function trim(s) {161return s.trim();162});163164shouldProxy = !noProxy.some(function proxyMatch(proxyElement) {165if (!proxyElement) {166return false;167}168if (proxyElement === '*') {169return true;170}171if (proxyElement[0] === '.' &&172parsed.hostname.substr(parsed.hostname.length - proxyElement.length) === proxyElement) {173return true;174}175176return parsed.hostname === proxyElement;177});178}179180if (shouldProxy) {181proxy = {182host: parsedProxyUrl.hostname,183port: parsedProxyUrl.port,184protocol: parsedProxyUrl.protocol185};186187if (parsedProxyUrl.auth) {188var proxyUrlAuth = parsedProxyUrl.auth.split(':');189proxy.auth = {190username: proxyUrlAuth[0],191password: proxyUrlAuth[1]192};193}194}195}196}197198if (proxy) {199options.headers.host = parsed.hostname + (parsed.port ? ':' + parsed.port : '');200setProxy(options, proxy, protocol + '//' + parsed.hostname + (parsed.port ? ':' + parsed.port : '') + options.path);201}202203var transport;204var isHttpsProxy = isHttpsRequest && (proxy ? isHttps.test(proxy.protocol) : true);205if (config.transport) {206transport = config.transport;207} else if (config.maxRedirects === 0) {208transport = isHttpsProxy ? https : http;209} else {210if (config.maxRedirects) {211options.maxRedirects = config.maxRedirects;212}213transport = isHttpsProxy ? httpsFollow : httpFollow;214}215216if (config.maxBodyLength > -1) {217options.maxBodyLength = config.maxBodyLength;218}219220if (config.insecureHTTPParser) {221options.insecureHTTPParser = config.insecureHTTPParser;222}223224// Create the request225var req = transport.request(options, function handleResponse(res) {226if (req.aborted) return;227228// uncompress the response body transparently if required229var stream = res;230231// return the last request in case of redirects232var lastRequest = res.req || req;233234235// if no content, is HEAD request or decompress disabled we should not decompress236if (res.statusCode !== 204 && lastRequest.method !== 'HEAD' && config.decompress !== false) {237switch (res.headers['content-encoding']) {238/*eslint default-case:0*/239case 'gzip':240case 'compress':241case 'deflate':242// add the unzipper to the body stream processing pipeline243stream = stream.pipe(zlib.createUnzip());244245// remove the content-encoding in order to not confuse downstream operations246delete res.headers['content-encoding'];247break;248}249}250251var response = {252status: res.statusCode,253statusText: res.statusMessage,254headers: res.headers,255config: config,256request: lastRequest257};258259if (config.responseType === 'stream') {260response.data = stream;261settle(resolve, reject, response);262} else {263var responseBuffer = [];264var totalResponseBytes = 0;265stream.on('data', function handleStreamData(chunk) {266responseBuffer.push(chunk);267totalResponseBytes += chunk.length;268269// make sure the content length is not over the maxContentLength if specified270if (config.maxContentLength > -1 && totalResponseBytes > config.maxContentLength) {271stream.destroy();272reject(createError('maxContentLength size of ' + config.maxContentLength + ' exceeded',273config, null, lastRequest));274}275});276277stream.on('error', function handleStreamError(err) {278if (req.aborted) return;279reject(enhanceError(err, config, null, lastRequest));280});281282stream.on('end', function handleStreamEnd() {283var responseData = Buffer.concat(responseBuffer);284if (config.responseType !== 'arraybuffer') {285responseData = responseData.toString(config.responseEncoding);286if (!config.responseEncoding || config.responseEncoding === 'utf8') {287responseData = utils.stripBOM(responseData);288}289}290291response.data = responseData;292settle(resolve, reject, response);293});294}295});296297// Handle errors298req.on('error', function handleRequestError(err) {299if (req.aborted && err.code !== 'ERR_FR_TOO_MANY_REDIRECTS') return;300reject(enhanceError(err, config, null, req));301});302303// Handle request timeout304if (config.timeout) {305// This is forcing a int timeout to avoid problems if the `req` interface doesn't handle other types.306var timeout = parseInt(config.timeout, 10);307308if (isNaN(timeout)) {309reject(createError(310'error trying to parse `config.timeout` to int',311config,312'ERR_PARSE_TIMEOUT',313req314));315316return;317}318319// Sometime, the response will be very slow, and does not respond, the connect event will be block by event loop system.320// And timer callback will be fired, and abort() will be invoked before connection, then get "socket hang up" and code ECONNRESET.321// 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.322// And then these socket which be hang up will devoring CPU little by little.323// ClientRequest.setTimeout will be fired on the specify milliseconds, and can make sure that abort() will be fired after connect.324req.setTimeout(timeout, function handleRequestTimeout() {325req.abort();326var transitional = config.transitional || defaults.transitional;327reject(createError(328'timeout of ' + timeout + 'ms exceeded',329config,330transitional.clarifyTimeoutError ? 'ETIMEDOUT' : 'ECONNABORTED',331req332));333});334}335336if (config.cancelToken || config.signal) {337// Handle cancellation338// eslint-disable-next-line func-names339onCanceled = function(cancel) {340if (req.aborted) return;341342req.abort();343reject(!cancel || (cancel && cancel.type) ? new Cancel('canceled') : cancel);344};345346config.cancelToken && config.cancelToken.subscribe(onCanceled);347if (config.signal) {348config.signal.aborted ? onCanceled() : config.signal.addEventListener('abort', onCanceled);349}350}351352353// Send the request354if (utils.isStream(data)) {355data.on('error', function handleStreamError(err) {356reject(enhanceError(err, config, null, req));357}).pipe(req);358} else {359req.end(data);360}361});362};363364365