http.js 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. 'use strict';
  2. var utils = require('./../utils');
  3. var settle = require('./../core/settle');
  4. var buildFullPath = require('../core/buildFullPath');
  5. var buildURL = require('./../helpers/buildURL');
  6. var http = require('http');
  7. var https = require('https');
  8. var httpFollow = require('follow-redirects').http;
  9. var httpsFollow = require('follow-redirects').https;
  10. var url = require('url');
  11. var zlib = require('zlib');
  12. var pkg = require('./../../package.json');
  13. var createError = require('../core/createError');
  14. var enhanceError = require('../core/enhanceError');
  15. var isHttps = /https:?/;
  16. /**
  17. *
  18. * @param {http.ClientRequestArgs} options
  19. * @param {AxiosProxyConfig} proxy
  20. * @param {string} location
  21. */
  22. function setProxy(options, proxy, location) {
  23. options.hostname = proxy.host;
  24. options.host = proxy.host;
  25. options.port = proxy.port;
  26. options.path = location;
  27. // Basic proxy authorization
  28. if (proxy.auth) {
  29. var base64 = Buffer.from(proxy.auth.username + ':' + proxy.auth.password, 'utf8').toString('base64');
  30. options.headers['Proxy-Authorization'] = 'Basic ' + base64;
  31. }
  32. // If a proxy is used, any redirects must also pass through the proxy
  33. options.beforeRedirect = function beforeRedirect(redirection) {
  34. redirection.headers.host = redirection.host;
  35. setProxy(redirection, proxy, redirection.href);
  36. };
  37. }
  38. /*eslint consistent-return:0*/
  39. module.exports = function httpAdapter(config) {
  40. return new Promise(function dispatchHttpRequest(resolvePromise, rejectPromise) {
  41. var resolve = function resolve(value) {
  42. resolvePromise(value);
  43. };
  44. var reject = function reject(value) {
  45. rejectPromise(value);
  46. };
  47. var data = config.data;
  48. var headers = config.headers;
  49. // Set User-Agent (required by some servers)
  50. // Only set header if it hasn't been set in config
  51. // See https://github.com/axios/axios/issues/69
  52. if (!headers['User-Agent'] && !headers['user-agent']) {
  53. headers['User-Agent'] = 'axios/' + pkg.version;
  54. }
  55. if (data && !utils.isStream(data)) {
  56. if (Buffer.isBuffer(data)) {
  57. // Nothing to do...
  58. } else if (utils.isArrayBuffer(data)) {
  59. data = Buffer.from(new Uint8Array(data));
  60. } else if (utils.isString(data)) {
  61. data = Buffer.from(data, 'utf-8');
  62. } else {
  63. return reject(createError(
  64. 'Data after transformation must be a string, an ArrayBuffer, a Buffer, or a Stream',
  65. config
  66. ));
  67. }
  68. // Add Content-Length header if data exists
  69. headers['Content-Length'] = data.length;
  70. }
  71. // HTTP basic authentication
  72. var auth = undefined;
  73. if (config.auth) {
  74. var username = config.auth.username || '';
  75. var password = config.auth.password || '';
  76. auth = username + ':' + password;
  77. }
  78. // Parse url
  79. var fullPath = buildFullPath(config.baseURL, config.url);
  80. var parsed = url.parse(fullPath);
  81. var protocol = parsed.protocol || 'http:';
  82. if (!auth && parsed.auth) {
  83. var urlAuth = parsed.auth.split(':');
  84. var urlUsername = urlAuth[0] || '';
  85. var urlPassword = urlAuth[1] || '';
  86. auth = urlUsername + ':' + urlPassword;
  87. }
  88. if (auth) {
  89. delete headers.Authorization;
  90. }
  91. var isHttpsRequest = isHttps.test(protocol);
  92. var agent = isHttpsRequest ? config.httpsAgent : config.httpAgent;
  93. var options = {
  94. path: buildURL(parsed.path, config.params, config.paramsSerializer).replace(/^\?/, ''),
  95. method: config.method.toUpperCase(),
  96. headers: headers,
  97. agent: agent,
  98. agents: { http: config.httpAgent, https: config.httpsAgent },
  99. auth: auth
  100. };
  101. if (config.socketPath) {
  102. options.socketPath = config.socketPath;
  103. } else {
  104. options.hostname = parsed.hostname;
  105. options.port = parsed.port;
  106. }
  107. var proxy = config.proxy;
  108. if (!proxy && proxy !== false) {
  109. var proxyEnv = protocol.slice(0, -1) + '_proxy';
  110. var proxyUrl = process.env[proxyEnv] || process.env[proxyEnv.toUpperCase()];
  111. if (proxyUrl) {
  112. var parsedProxyUrl = url.parse(proxyUrl);
  113. var noProxyEnv = process.env.no_proxy || process.env.NO_PROXY;
  114. var shouldProxy = true;
  115. if (noProxyEnv) {
  116. var noProxy = noProxyEnv.split(',').map(function trim(s) {
  117. return s.trim();
  118. });
  119. shouldProxy = !noProxy.some(function proxyMatch(proxyElement) {
  120. if (!proxyElement) {
  121. return false;
  122. }
  123. if (proxyElement === '*') {
  124. return true;
  125. }
  126. if (proxyElement[0] === '.' &&
  127. parsed.hostname.substr(parsed.hostname.length - proxyElement.length) === proxyElement) {
  128. return true;
  129. }
  130. return parsed.hostname === proxyElement;
  131. });
  132. }
  133. if (shouldProxy) {
  134. proxy = {
  135. host: parsedProxyUrl.hostname,
  136. port: parsedProxyUrl.port,
  137. protocol: parsedProxyUrl.protocol
  138. };
  139. if (parsedProxyUrl.auth) {
  140. var proxyUrlAuth = parsedProxyUrl.auth.split(':');
  141. proxy.auth = {
  142. username: proxyUrlAuth[0],
  143. password: proxyUrlAuth[1]
  144. };
  145. }
  146. }
  147. }
  148. }
  149. if (proxy) {
  150. options.headers.host = parsed.hostname + (parsed.port ? ':' + parsed.port : '');
  151. setProxy(options, proxy, protocol + '//' + parsed.hostname + (parsed.port ? ':' + parsed.port : '') + options.path);
  152. }
  153. var transport;
  154. var isHttpsProxy = isHttpsRequest && (proxy ? isHttps.test(proxy.protocol) : true);
  155. if (config.transport) {
  156. transport = config.transport;
  157. } else if (config.maxRedirects === 0) {
  158. transport = isHttpsProxy ? https : http;
  159. } else {
  160. if (config.maxRedirects) {
  161. options.maxRedirects = config.maxRedirects;
  162. }
  163. transport = isHttpsProxy ? httpsFollow : httpFollow;
  164. }
  165. if (config.maxBodyLength > -1) {
  166. options.maxBodyLength = config.maxBodyLength;
  167. }
  168. // Create the request
  169. var req = transport.request(options, function handleResponse(res) {
  170. if (req.aborted) return;
  171. // uncompress the response body transparently if required
  172. var stream = res;
  173. // return the last request in case of redirects
  174. var lastRequest = res.req || req;
  175. // if no content, is HEAD request or decompress disabled we should not decompress
  176. if (res.statusCode !== 204 && lastRequest.method !== 'HEAD' && config.decompress !== false) {
  177. switch (res.headers['content-encoding']) {
  178. /*eslint default-case:0*/
  179. case 'gzip':
  180. case 'compress':
  181. case 'deflate':
  182. // add the unzipper to the body stream processing pipeline
  183. stream = stream.pipe(zlib.createUnzip());
  184. // remove the content-encoding in order to not confuse downstream operations
  185. delete res.headers['content-encoding'];
  186. break;
  187. }
  188. }
  189. var response = {
  190. status: res.statusCode,
  191. statusText: res.statusMessage,
  192. headers: res.headers,
  193. config: config,
  194. request: lastRequest
  195. };
  196. if (config.responseType === 'stream') {
  197. response.data = stream;
  198. settle(resolve, reject, response);
  199. } else {
  200. var responseBuffer = [];
  201. stream.on('data', function handleStreamData(chunk) {
  202. responseBuffer.push(chunk);
  203. // make sure the content length is not over the maxContentLength if specified
  204. if (config.maxContentLength > -1 && Buffer.concat(responseBuffer).length > config.maxContentLength) {
  205. stream.destroy();
  206. reject(createError('maxContentLength size of ' + config.maxContentLength + ' exceeded',
  207. config, null, lastRequest));
  208. }
  209. });
  210. stream.on('error', function handleStreamError(err) {
  211. if (req.aborted) return;
  212. reject(enhanceError(err, config, null, lastRequest));
  213. });
  214. stream.on('end', function handleStreamEnd() {
  215. var responseData = Buffer.concat(responseBuffer);
  216. if (config.responseType !== 'arraybuffer') {
  217. responseData = responseData.toString(config.responseEncoding);
  218. if (!config.responseEncoding || config.responseEncoding === 'utf8') {
  219. responseData = utils.stripBOM(responseData);
  220. }
  221. }
  222. response.data = responseData;
  223. settle(resolve, reject, response);
  224. });
  225. }
  226. });
  227. // Handle errors
  228. req.on('error', function handleRequestError(err) {
  229. if (req.aborted && err.code !== 'ERR_FR_TOO_MANY_REDIRECTS') return;
  230. reject(enhanceError(err, config, null, req));
  231. });
  232. // Handle request timeout
  233. if (config.timeout) {
  234. // Sometime, the response will be very slow, and does not respond, the connect event will be block by event loop system.
  235. // And timer callback will be fired, and abort() will be invoked before connection, then get "socket hang up" and code ECONNRESET.
  236. // 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.
  237. // And then these socket which be hang up will devoring CPU little by little.
  238. // ClientRequest.setTimeout will be fired on the specify milliseconds, and can make sure that abort() will be fired after connect.
  239. req.setTimeout(config.timeout, function handleRequestTimeout() {
  240. req.abort();
  241. reject(createError('timeout of ' + config.timeout + 'ms exceeded', config, 'ECONNABORTED', req));
  242. });
  243. }
  244. if (config.cancelToken) {
  245. // Handle cancellation
  246. config.cancelToken.promise.then(function onCanceled(cancel) {
  247. if (req.aborted) return;
  248. req.abort();
  249. reject(cancel);
  250. });
  251. }
  252. // Send the request
  253. if (utils.isStream(data)) {
  254. data.on('error', function handleStreamError(err) {
  255. reject(enhanceError(err, config, null, req));
  256. }).pipe(req);
  257. } else {
  258. req.end(data);
  259. }
  260. });
  261. };