You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

287 lines
9.4 KiB

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