urllib.js 42 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317
  1. 'use strict';
  2. var debug = require('debug')('urllib');
  3. var path = require('path');
  4. var dns = require('dns');
  5. var http = require('http');
  6. var https = require('https');
  7. var urlutil = require('url');
  8. var URL = urlutil.URL;
  9. var util = require('util');
  10. var qs = require('qs');
  11. var ip = require('ip');
  12. var querystring = require('querystring');
  13. var zlib = require('zlib');
  14. var ua = require('default-user-agent');
  15. var digestAuthHeader = require('digest-header');
  16. var ms = require('humanize-ms');
  17. var statuses = require('statuses');
  18. var contentTypeParser = require('content-type');
  19. var first = require('ee-first');
  20. var pump = require('pump');
  21. var utility = require('utility');
  22. var FormStream = require('formstream');
  23. var detectProxyAgent = require('./detect_proxy_agent');
  24. var _Promise;
  25. var _iconv;
  26. var pkg = require('../package.json');
  27. var USER_AGENT = exports.USER_AGENT = ua('node-urllib', pkg.version);
  28. var NODE_MAJOR_VERSION = parseInt(process.versions.node.split('.')[0]);
  29. // change Agent.maxSockets to 1000
  30. exports.agent = new http.Agent();
  31. exports.agent.maxSockets = 1000;
  32. exports.httpsAgent = new https.Agent();
  33. exports.httpsAgent.maxSockets = 1000;
  34. var LONG_STACK_DELIMITER = '\n --------------------\n';
  35. /**
  36. * The default request timeout(in milliseconds).
  37. * @type {Number}
  38. * @const
  39. */
  40. exports.TIMEOUT = ms('5s');
  41. exports.TIMEOUTS = [ms('5s'), ms('5s')];
  42. var REQUEST_ID = 0;
  43. var MAX_VALUE = Math.pow(2, 31) - 10;
  44. var isNode010 = /^v0\.10\.\d+$/.test(process.version);
  45. var isNode012 = /^v0\.12\.\d+$/.test(process.version);
  46. /**
  47. * support data types
  48. * will auto decode response body
  49. * @type {Array}
  50. */
  51. var TEXT_DATA_TYPES = [
  52. 'json',
  53. 'text'
  54. ];
  55. var PROTO_RE = /^https?:\/\//i;
  56. // Keep-Alive: timeout=5, max=100
  57. var KEEP_ALIVE_RE = /^timeout=(\d+)/i;
  58. var SOCKET_REQUEST_COUNT = '_URLLIB_SOCKET_REQUEST_COUNT';
  59. var SOCKET_RESPONSE_COUNT = '_URLLIB_SOCKET_RESPONSE_COUNT';
  60. /**
  61. * Handle all http request, both http and https support well.
  62. *
  63. * @example
  64. *
  65. * ```js
  66. * // GET https://nodejs.org
  67. * urllib.request('https://nodejs.org', function(err, data, res) {});
  68. * // POST https://nodejs.org
  69. * var args = { type: 'post', data: { foo: 'bar' } };
  70. * urllib.request('https://nodejs.org', args, function(err, data, res) {});
  71. * ```
  72. *
  73. * @param {String|Object} url: the request full URL.
  74. * @param {Object} [args]: optional
  75. * - {Object} [data]: request data, will auto be query stringify.
  76. * - {Boolean} [dataAsQueryString]: force convert `data` to query string.
  77. * - {String|Buffer} [content]: optional, if set content, `data` will ignore.
  78. * - {ReadStream} [stream]: read stream to sent.
  79. * - {WriteStream} [writeStream]: writable stream to save response data.
  80. * If you use this, callback's data should be null.
  81. * We will just `pipe(ws, {end: true})`.
  82. * - {consumeWriteStream} [true]: consume the writeStream, invoke the callback after writeStream close.
  83. * - {Array<ReadStream|Buffer|String>|Object|ReadStream|Buffer|String} [files]: optional,
  84. * The files will send with `multipart/form-data` format, base on `formstream`.
  85. * If `method` not set, will use `POST` method by default.
  86. * - {String} [method]: optional, could be GET | POST | DELETE | PUT, default is GET
  87. * - {String} [contentType]: optional, request data type, could be `json`, default is undefined
  88. * - {String} [dataType]: optional, response data type, could be `text` or `json`, default is buffer
  89. * - {Boolean|Function} [fixJSONCtlChars]: optional, fix the control characters (U+0000 through U+001F)
  90. * before JSON parse response. Default is `false`.
  91. * `fixJSONCtlChars` can be a function, will pass data to the first argument. e.g.: `data = fixJSONCtlChars(data)`
  92. * - {Object} [headers]: optional, request headers
  93. * - {Boolean} [keepHeaderCase]: optional, by default will convert header keys to lowercase
  94. * - {Number|Array} [timeout]: request timeout(in milliseconds), default is `exports.TIMEOUTS containing connect timeout and response timeout`
  95. * - {Agent} [agent]: optional, http agent. Set `false` if you does not use agent.
  96. * - {Agent} [httpsAgent]: optional, https agent. Set `false` if you does not use agent.
  97. * - {String} [auth]: Basic authentication i.e. 'user:password' to compute an Authorization header.
  98. * - {String} [digestAuth]: Digest authentication i.e. 'user:password' to compute an Authorization header.
  99. * - {String|Buffer|Array} [ca]: An array of strings or Buffers of trusted certificates.
  100. * If this is omitted several well known "root" CAs will be used, like VeriSign.
  101. * These are used to authorize connections.
  102. * Notes: This is necessary only if the server uses the self-signed certificate
  103. * - {Boolean} [rejectUnauthorized]: If true, the server certificate is verified against the list of supplied CAs.
  104. * An 'error' event is emitted if verification fails. Default: true.
  105. * - {String|Buffer} [pfx]: A string or Buffer containing the private key,
  106. * certificate and CA certs of the server in PFX or PKCS12 format.
  107. * - {String|Buffer} [key]: A string or Buffer containing the private key of the client in PEM format.
  108. * Notes: This is necessary only if using the client certificate authentication
  109. * - {String|Buffer} [cert]: A string or Buffer containing the certificate key of the client in PEM format.
  110. * Notes: This is necessary only if using the client certificate authentication
  111. * - {String} [passphrase]: A string of passphrase for the private key or pfx.
  112. * - {String} [ciphers]: A string describing the ciphers to use or exclude.
  113. * - {String} [secureProtocol]: The SSL method to use, e.g. SSLv3_method to force SSL version 3.
  114. * The possible values depend on your installation of OpenSSL and are defined in the constant SSL_METHODS.
  115. * - {Boolean} [followRedirect]: Follow HTTP 3xx responses as redirects. defaults to false.
  116. * - {Number} [maxRedirects]: The maximum number of redirects to follow, defaults to 10.
  117. * - {Function(from, to)} [formatRedirectUrl]: Format the redirect url by your self. Default is `url.resolve(from, to)`
  118. * - {Function(options)} [beforeRequest]: Before request hook, you can change every thing here.
  119. * - {Boolean} [streaming]: let you get the res object when request connected, default is `false`. alias `customResponse`
  120. * - {Boolean} [gzip]: Accept gzip response content and auto decode it, default is `false`.
  121. * - {Boolean} [timing]: Enable timing or not, default is `false`.
  122. * - {Function} [lookup]: Custom DNS lookup function, default is `dns.lookup`.
  123. * Require node >= 4.0.0 and only work on `http` protocol.
  124. * - {Boolean} [enableProxy]: optional, enable proxy request. Default is `false`.
  125. * - {String|Object} [proxy]: optional proxy agent uri or options. Default is `null`.
  126. * - {String} [socketPath]: optional, unix domain socket file path.
  127. * - {Function} checkAddress: optional, check request address to protect from SSRF and similar attacks.
  128. * @param {Function} [callback]: callback(error, data, res). If missing callback, will return a promise object.
  129. * @return {HttpRequest} req object.
  130. * @api public
  131. */
  132. exports.request = function request(url, args, callback) {
  133. // request(url, callback)
  134. if (arguments.length === 2 && typeof args === 'function') {
  135. callback = args;
  136. args = null;
  137. }
  138. if (typeof callback === 'function') {
  139. return exports.requestWithCallback(url, args, callback);
  140. }
  141. // Promise
  142. if (!_Promise) {
  143. _Promise = require('any-promise');
  144. }
  145. return new _Promise(function (resolve, reject) {
  146. exports.requestWithCallback(url, args, makeCallback(resolve, reject));
  147. });
  148. };
  149. // alias to curl
  150. exports.curl = exports.request;
  151. function makeCallback(resolve, reject) {
  152. return function (err, data, res) {
  153. if (err) {
  154. return reject(err);
  155. }
  156. resolve({
  157. data: data,
  158. status: res.statusCode,
  159. headers: res.headers,
  160. res: res
  161. });
  162. };
  163. }
  164. // yield urllib.requestThunk(url, args)
  165. exports.requestThunk = function requestThunk(url, args) {
  166. return function (callback) {
  167. exports.requestWithCallback(url, args, function (err, data, res) {
  168. if (err) {
  169. return callback(err);
  170. }
  171. callback(null, {
  172. data: data,
  173. status: res.statusCode,
  174. headers: res.headers,
  175. res: res
  176. });
  177. });
  178. };
  179. };
  180. function requestWithCallback(url, args, callback) {
  181. var req;
  182. // requestWithCallback(url, callback)
  183. if (!url || (typeof url !== 'string' && typeof url !== 'object')) {
  184. var msg = util.format('expect request url to be a string or a http request options, but got %j', url);
  185. throw new Error(msg);
  186. }
  187. if (arguments.length === 2 && typeof args === 'function') {
  188. callback = args;
  189. args = null;
  190. }
  191. args = args || {};
  192. if (REQUEST_ID >= MAX_VALUE) {
  193. REQUEST_ID = 0;
  194. }
  195. var reqId = ++REQUEST_ID;
  196. args.requestUrls = args.requestUrls || [];
  197. args.timeout = args.timeout || exports.TIMEOUTS;
  198. args.maxRedirects = args.maxRedirects || 10;
  199. args.streaming = args.streaming || args.customResponse;
  200. var requestStartTime = Date.now();
  201. var parsedUrl;
  202. if (typeof url === 'string') {
  203. if (!PROTO_RE.test(url)) {
  204. // Support `request('www.server.com')`
  205. url = 'http://' + url;
  206. }
  207. if (URL) {
  208. parsedUrl = urlutil.parse(new URL(url).href);
  209. } else {
  210. parsedUrl = urlutil.parse(url);
  211. }
  212. } else {
  213. parsedUrl = url;
  214. }
  215. var reqMeta = {
  216. requestId: reqId,
  217. url: parsedUrl.href,
  218. args: args,
  219. ctx: args.ctx,
  220. };
  221. if (args.emitter) {
  222. args.emitter.emit('request', reqMeta);
  223. }
  224. var method = (args.type || args.method || parsedUrl.method || 'GET').toUpperCase();
  225. var port = parsedUrl.port || 80;
  226. var httplib = http;
  227. var agent = getAgent(args.agent, exports.agent);
  228. var fixJSONCtlChars = args.fixJSONCtlChars;
  229. if (parsedUrl.protocol === 'https:') {
  230. httplib = https;
  231. agent = getAgent(args.httpsAgent, exports.httpsAgent);
  232. if (!parsedUrl.port) {
  233. port = 443;
  234. }
  235. }
  236. // request through proxy tunnel
  237. var proxyTunnelAgent = detectProxyAgent(parsedUrl, args);
  238. if (proxyTunnelAgent) {
  239. agent = proxyTunnelAgent;
  240. }
  241. var lookup = args.lookup;
  242. // check address to protect from SSRF and similar attacks
  243. if (args.checkAddress) {
  244. var _lookup = lookup || dns.lookup;
  245. lookup = function(host, dnsopts, callback) {
  246. _lookup(host, dnsopts, function emitLookup(err, ip, family) {
  247. // add check address logic in custom dns lookup
  248. if (!err && !args.checkAddress(ip, family)) {
  249. err = new Error('illegal address');
  250. err.name = 'IllegalAddressError';
  251. err.hostname = host;
  252. err.ip = ip;
  253. err.family = family;
  254. }
  255. callback(err, ip, family);
  256. });
  257. };
  258. }
  259. var requestSize = 0;
  260. var options = {
  261. host: parsedUrl.hostname || parsedUrl.host || 'localhost',
  262. path: parsedUrl.path || '/',
  263. method: method,
  264. port: port,
  265. agent: agent,
  266. headers: {},
  267. // default is dns.lookup
  268. // https://github.com/nodejs/node/blob/master/lib/net.js#L986
  269. // custom dnslookup require node >= 4.0.0 (for http), node >=8 (for https)
  270. // https://github.com/nodejs/node/blob/archived-io.js-v0.12/lib/net.js#L952
  271. lookup: lookup,
  272. };
  273. var originHeaderKeys = {};
  274. if (args.headers) {
  275. // only allow enumerable and ownProperty value of args.headers
  276. var names = utility.getOwnEnumerables(args.headers, true);
  277. for (var i = 0; i < names.length; i++) {
  278. var name = names[i];
  279. var key = name.toLowerCase();
  280. if (key !== name) {
  281. originHeaderKeys[key] = name;
  282. }
  283. options.headers[key] = args.headers[name];
  284. }
  285. }
  286. if (args.socketPath) {
  287. options.socketPath = args.socketPath;
  288. }
  289. var sslNames = [
  290. 'pfx',
  291. 'key',
  292. 'passphrase',
  293. 'cert',
  294. 'ca',
  295. 'ciphers',
  296. 'rejectUnauthorized',
  297. 'secureProtocol',
  298. 'secureOptions',
  299. ];
  300. for (var i = 0; i < sslNames.length; i++) {
  301. var name = sslNames[i];
  302. if (args.hasOwnProperty(name)) {
  303. options[name] = args[name];
  304. }
  305. }
  306. // fix rejectUnauthorized when major version < 12
  307. if (NODE_MAJOR_VERSION < 12) {
  308. if (options.rejectUnauthorized === false && !options.hasOwnProperty('secureOptions')) {
  309. options.secureOptions = require('constants').SSL_OP_NO_TLSv1_2;
  310. }
  311. }
  312. var auth = args.auth || parsedUrl.auth;
  313. if (auth) {
  314. options.auth = auth;
  315. }
  316. var body = null;
  317. var dataAsQueryString = false;
  318. if (args.files) {
  319. if (!options.method || options.method === 'GET' || options.method === 'HEAD') {
  320. options.method = 'POST';
  321. }
  322. var files = args.files;
  323. var uploadFiles = [];
  324. if (Array.isArray(files)) {
  325. for (var i = 0; i < files.length; i++) {
  326. var field = 'file' + (i === 0 ? '' : i);
  327. uploadFiles.push([ field, files[i] ]);
  328. }
  329. } else {
  330. if (Buffer.isBuffer(files) || typeof files.pipe === 'function' || typeof files === 'string') {
  331. uploadFiles.push([ 'file', files ]);
  332. } else if (typeof files === 'object') {
  333. for (var field in files) {
  334. uploadFiles.push([ field, files[field] ]);
  335. }
  336. }
  337. }
  338. var form = new FormStream();
  339. // set normal fields first
  340. if (args.data) {
  341. for (var fieldName in args.data) {
  342. form.field(fieldName, args.data[fieldName]);
  343. }
  344. }
  345. for (var i = 0; i < uploadFiles.length; i++) {
  346. var item = uploadFiles[i];
  347. if (Buffer.isBuffer(item[1])) {
  348. form.buffer(item[0], item[1], 'bufferfile' + i);
  349. } else if (typeof item[1].pipe === 'function') {
  350. var filename = item[1].path || ('streamfile' + i);
  351. filename = path.basename(filename);
  352. form.stream(item[0], item[1], filename);
  353. } else {
  354. form.file(item[0], item[1]);
  355. }
  356. }
  357. var formHeaders = form.headers();
  358. var formHeaderNames = utility.getOwnEnumerables(formHeaders, true);
  359. for (var i = 0; i < formHeaderNames.length; i++) {
  360. var name = formHeaderNames[i];
  361. options.headers[name.toLowerCase()] = formHeaders[name];
  362. }
  363. debug('set multipart headers: %j, method: %s', formHeaders, options.method);
  364. args.stream = form;
  365. } else {
  366. body = args.content || args.data;
  367. dataAsQueryString = method === 'GET' || method === 'HEAD' || args.dataAsQueryString;
  368. if (!args.content) {
  369. if (body && !(typeof body === 'string' || Buffer.isBuffer(body))) {
  370. if (dataAsQueryString) {
  371. // read: GET, HEAD, use query string
  372. body = args.nestedQuerystring ? qs.stringify(body) : querystring.stringify(body);
  373. } else {
  374. var contentType = options.headers['content-type'];
  375. // auto add application/x-www-form-urlencoded when using urlencode form request
  376. if (!contentType) {
  377. if (args.contentType === 'json') {
  378. contentType = 'application/json';
  379. } else {
  380. contentType = 'application/x-www-form-urlencoded';
  381. }
  382. options.headers['content-type'] = contentType;
  383. }
  384. if (parseContentType(contentType).type === 'application/json') {
  385. body = JSON.stringify(body);
  386. } else {
  387. // 'application/x-www-form-urlencoded'
  388. body = args.nestedQuerystring ? qs.stringify(body) : querystring.stringify(body);
  389. }
  390. }
  391. }
  392. }
  393. }
  394. if (body) {
  395. // if it's a GET or HEAD request, data should be sent as query string
  396. if (dataAsQueryString) {
  397. options.path += (parsedUrl.query ? '&' : '?') + body;
  398. body = null;
  399. }
  400. if (body) {
  401. var length = body.length;
  402. if (!Buffer.isBuffer(body)) {
  403. length = Buffer.byteLength(body);
  404. }
  405. requestSize = length;
  406. options.headers['content-length'] = length.toString();
  407. }
  408. }
  409. if (args.dataType === 'json') {
  410. if (!options.headers.accept) {
  411. options.headers.accept = 'application/json';
  412. }
  413. }
  414. if (typeof args.beforeRequest === 'function') {
  415. // you can use this hook to change every thing.
  416. args.beforeRequest(options);
  417. }
  418. var connectTimer = null;
  419. var responseTimer = null;
  420. var __err = null;
  421. var connected = false; // socket connected or not
  422. var keepAliveSocket = false; // request with keepalive socket
  423. var socketHandledRequests = 0; // socket already handled request count
  424. var socketHandledResponses = 0; // socket already handled response count
  425. var responseSize = 0;
  426. var statusCode = -1;
  427. var statusMessage = null;
  428. var responseAborted = false;
  429. var remoteAddress = '';
  430. var remotePort = '';
  431. var timing = null;
  432. if (args.timing) {
  433. timing = {
  434. // socket assigned
  435. queuing: 0,
  436. // dns lookup time
  437. dnslookup: 0,
  438. // socket connected
  439. connected: 0,
  440. // request sent
  441. requestSent: 0,
  442. // Time to first byte (TTFB)
  443. waiting: 0,
  444. contentDownload: 0,
  445. };
  446. }
  447. function cancelConnectTimer() {
  448. if (connectTimer) {
  449. clearTimeout(connectTimer);
  450. connectTimer = null;
  451. debug('Request#%d connect timer canceled', reqId);
  452. }
  453. }
  454. function cancelResponseTimer() {
  455. if (responseTimer) {
  456. clearTimeout(responseTimer);
  457. responseTimer = null;
  458. debug('Request#%d response timer canceled', reqId);
  459. }
  460. }
  461. function done(err, data, res) {
  462. cancelConnectTimer();
  463. cancelResponseTimer();
  464. if (!callback) {
  465. console.warn('[urllib:warn] [%s] [%s] [worker:%s] %s %s callback twice!!!',
  466. Date(), reqId, process.pid, options.method, url);
  467. // https://github.com/node-modules/urllib/pull/30
  468. if (err) {
  469. console.warn('[urllib:warn] [%s] [%s] [worker:%s] %s: %s\nstack: %s',
  470. Date(), reqId, process.pid, err.name, err.message, err.stack);
  471. }
  472. return;
  473. }
  474. var cb = callback;
  475. callback = null;
  476. var headers = {};
  477. if (res) {
  478. statusCode = res.statusCode;
  479. statusMessage = res.statusMessage;
  480. headers = res.headers;
  481. }
  482. if (handleDigestAuth(res, cb)) {
  483. return;
  484. }
  485. var response = createCallbackResponse(data, res);
  486. debug('[%sms] done, %s bytes HTTP %s %s %s %s, keepAliveSocket: %s, timing: %j, socketHandledRequests: %s, socketHandledResponses: %s',
  487. response.requestUseTime, responseSize, statusCode, options.method, options.host, options.path,
  488. keepAliveSocket, timing, socketHandledRequests, socketHandledResponses);
  489. if (err) {
  490. var agentStatus = '';
  491. if (agent && typeof agent.getCurrentStatus === 'function') {
  492. // add current agent status to error message for logging and debug
  493. agentStatus = ', agent status: ' + JSON.stringify(agent.getCurrentStatus());
  494. }
  495. err.message += ', ' + options.method + ' ' + url + ' ' + statusCode
  496. + ' (connected: ' + connected + ', keepalive socket: ' + keepAliveSocket + agentStatus
  497. + ', socketHandledRequests: ' + socketHandledRequests
  498. + ', socketHandledResponses: ' + socketHandledResponses + ')'
  499. + '\nheaders: ' + JSON.stringify(headers);
  500. err.data = data;
  501. err.path = options.path;
  502. err.status = statusCode;
  503. err.headers = headers;
  504. err.res = response;
  505. addLongStackTrace(err, req);
  506. }
  507. // only support agentkeepalive module for now
  508. // agentkeepalive@4: agent.options.freeSocketTimeout
  509. // agentkeepalive@3: agent.freeSocketKeepAliveTimeout
  510. var freeSocketTimeout = agent && (agent.options && agent.options.freeSocketTimeout || agent.freeSocketKeepAliveTimeout);
  511. if (agent && agent.keepAlive && freeSocketTimeout > 0 &&
  512. statusCode >= 200 && headers.connection === 'keep-alive' && headers['keep-alive']) {
  513. // adjust freeSocketTimeout on the socket
  514. var m = KEEP_ALIVE_RE.exec(headers['keep-alive']);
  515. if (m) {
  516. var seconds = parseInt(m[1]);
  517. if (seconds > 0) {
  518. // network delay 500ms
  519. var serverSocketTimeout = seconds * 1000 - 500;
  520. if (serverSocketTimeout < freeSocketTimeout) {
  521. // https://github.com/node-modules/agentkeepalive/blob/master/lib/agent.js#L127
  522. // agentkeepalive@4
  523. var socket = res.socket || (req && req.socket);
  524. if (agent.options && agent.options.freeSocketTimeout) {
  525. socket.freeSocketTimeout = serverSocketTimeout;
  526. } else {
  527. socket.freeSocketKeepAliveTimeout = serverSocketTimeout;
  528. }
  529. }
  530. }
  531. }
  532. }
  533. cb(err, data, args.streaming ? res : response);
  534. emitResponseEvent(err, response);
  535. }
  536. function createAndEmitResponseEvent(data, res) {
  537. var response = createCallbackResponse(data, res);
  538. emitResponseEvent(null, response);
  539. }
  540. function createCallbackResponse(data, res) {
  541. var requestUseTime = Date.now() - requestStartTime;
  542. if (timing) {
  543. timing.contentDownload = requestUseTime;
  544. }
  545. var headers = res && res.headers || {};
  546. var resStatusCode = res && res.statusCode || statusCode;
  547. var resStatusMessage = res && res.statusMessage || statusMessage;
  548. return {
  549. status: resStatusCode,
  550. statusCode: resStatusCode,
  551. statusMessage: resStatusMessage,
  552. headers: headers,
  553. size: responseSize,
  554. aborted: responseAborted,
  555. rt: requestUseTime,
  556. keepAliveSocket: keepAliveSocket,
  557. data: data,
  558. requestUrls: args.requestUrls,
  559. timing: timing,
  560. remoteAddress: remoteAddress,
  561. remotePort: remotePort,
  562. socketHandledRequests: socketHandledRequests,
  563. socketHandledResponses: socketHandledResponses,
  564. };
  565. }
  566. function emitResponseEvent(err, response) {
  567. if (args.emitter) {
  568. // keep to use the same reqMeta object on request event before
  569. reqMeta.url = parsedUrl.href;
  570. reqMeta.socket = req && req.connection;
  571. reqMeta.options = options;
  572. reqMeta.size = requestSize;
  573. args.emitter.emit('response', {
  574. requestId: reqId,
  575. error: err,
  576. ctx: args.ctx,
  577. req: reqMeta,
  578. res: response,
  579. });
  580. }
  581. }
  582. function handleDigestAuth(res, cb) {
  583. var headers = {};
  584. if (res && res.headers) {
  585. headers = res.headers;
  586. }
  587. // handle digest auth
  588. if (statusCode === 401 && headers['www-authenticate']
  589. && !options.headers.authorization && args.digestAuth) {
  590. var authenticate = headers['www-authenticate'];
  591. if (authenticate.indexOf('Digest ') >= 0) {
  592. debug('Request#%d %s: got digest auth header WWW-Authenticate: %s', reqId, url, authenticate);
  593. options.headers.authorization = digestAuthHeader(options.method, options.path, authenticate, args.digestAuth);
  594. debug('Request#%d %s: auth with digest header: %s', reqId, url, options.headers.authorization);
  595. if (res.headers['set-cookie']) {
  596. options.headers.cookie = res.headers['set-cookie'].join(';');
  597. }
  598. args.headers = options.headers;
  599. exports.requestWithCallback(url, args, cb);
  600. return true;
  601. }
  602. }
  603. return false;
  604. }
  605. function handleRedirect(res) {
  606. var err = null;
  607. if (args.followRedirect && statuses.redirect[res.statusCode]) { // handle redirect
  608. args._followRedirectCount = (args._followRedirectCount || 0) + 1;
  609. var location = res.headers.location;
  610. if (!location) {
  611. err = new Error('Got statusCode ' + res.statusCode + ' but cannot resolve next location from headers');
  612. err.name = 'FollowRedirectError';
  613. } else if (args._followRedirectCount > args.maxRedirects) {
  614. err = new Error('Exceeded maxRedirects. Probably stuck in a redirect loop ' + url);
  615. err.name = 'MaxRedirectError';
  616. } else {
  617. var newUrl = args.formatRedirectUrl ? args.formatRedirectUrl(url, location) : urlutil.resolve(url, location);
  618. debug('Request#%d %s: `redirected` from %s to %s', reqId, options.path, url, newUrl);
  619. // make sure timer stop
  620. cancelResponseTimer();
  621. // should clean up headers.host on `location: http://other-domain/url`
  622. if (options.headers.host && PROTO_RE.test(location)) {
  623. options.headers.host = null;
  624. args.headers = options.headers;
  625. }
  626. // avoid done will be execute in the future change.
  627. var cb = callback;
  628. callback = null;
  629. exports.requestWithCallback(newUrl, args, cb);
  630. return {
  631. redirect: true,
  632. error: null
  633. };
  634. }
  635. }
  636. return {
  637. redirect: false,
  638. error: err
  639. };
  640. }
  641. // don't set user-agent
  642. if (args.headers && (args.headers['User-Agent'] === null || args.headers['user-agent'] === null)) {
  643. if (options.headers['user-agent']) {
  644. delete options.headers['user-agent'];
  645. }
  646. } else {
  647. // need to set user-agent
  648. var hasAgentHeader = options.headers['user-agent'];
  649. if (!hasAgentHeader) {
  650. options.headers['user-agent'] = USER_AGENT;
  651. }
  652. }
  653. if (args.gzip) {
  654. var isAcceptEncodingNull = (args.headers && (args.headers['Accept-Encoding'] === null || args.headers['accept-encoding'] === null));
  655. if (!isAcceptEncodingNull) {
  656. var hasAcceptEncodingHeader = options.headers['accept-encoding'];
  657. if (!hasAcceptEncodingHeader) {
  658. options.headers['accept-encoding'] = 'gzip, deflate';
  659. }
  660. }
  661. }
  662. function decodeContent(res, body, cb) {
  663. if (responseAborted) {
  664. // err = new Error('Remote socket was terminated before `response.end()` was called');
  665. // err.name = 'RemoteSocketClosedError';
  666. debug('Request#%d %s: Remote socket was terminated before `response.end()` was called', reqId, url);
  667. var err = responseError || new Error('Remote socket was terminated before `response.end()` was called');
  668. return cb(err);
  669. }
  670. var encoding = res.headers['content-encoding'];
  671. if (body.length === 0 || !encoding) {
  672. return cb(null, body, encoding);
  673. }
  674. encoding = encoding.toLowerCase();
  675. switch (encoding) {
  676. case 'gzip':
  677. case 'deflate':
  678. debug('unzip %d length body', body.length);
  679. zlib.unzip(body, function(err, data) {
  680. if (err && err.name === 'Error') {
  681. err.name = 'UnzipError';
  682. }
  683. cb(err, data);
  684. });
  685. break;
  686. default:
  687. cb(null, body, encoding);
  688. }
  689. }
  690. var writeStream = args.writeStream;
  691. var isWriteStreamClose = false;
  692. debug('Request#%d %s %s with headers %j, options.path: %s',
  693. reqId, method, url, options.headers, options.path);
  694. args.requestUrls.push(parsedUrl.href);
  695. var hasResponse = false;
  696. var responseError;
  697. function onResponse(res) {
  698. hasResponse = true;
  699. socketHandledResponses = res.socket[SOCKET_RESPONSE_COUNT] = (res.socket[SOCKET_RESPONSE_COUNT] || 0) + 1;
  700. if (timing) {
  701. timing.waiting = Date.now() - requestStartTime;
  702. }
  703. debug('Request#%d %s `req response` event emit: status %d, headers: %j',
  704. reqId, url, res.statusCode, res.headers);
  705. if (args.streaming) {
  706. var result = handleRedirect(res);
  707. if (result.redirect) {
  708. res.resume();
  709. createAndEmitResponseEvent(null, res);
  710. return;
  711. }
  712. if (result.error) {
  713. res.resume();
  714. return done(result.error, null, res);
  715. }
  716. return done(null, null, res);
  717. }
  718. res.on('error', function (err) {
  719. responseError = err;
  720. debug('Request#%d %s: `res error` event emit, total size %d, socket handled %s requests and %s responses',
  721. reqId, url, responseSize, socketHandledRequests, socketHandledResponses);
  722. });
  723. res.on('aborted', function () {
  724. responseAborted = true;
  725. debug('Request#%d %s: `res aborted` event emit, total size %d',
  726. reqId, url, responseSize);
  727. });
  728. if (writeStream) {
  729. // If there's a writable stream to recieve the response data, just pipe the
  730. // response stream to that writable stream and call the callback when it has
  731. // finished writing.
  732. //
  733. // NOTE that when the response stream `res` emits an 'end' event it just
  734. // means that it has finished piping data to another stream. In the
  735. // meanwhile that writable stream may still writing data to the disk until
  736. // it emits a 'close' event.
  737. //
  738. // That means that we should not apply callback until the 'close' of the
  739. // writable stream is emited.
  740. //
  741. // See also:
  742. // - https://github.com/TBEDP/urllib/commit/959ac3365821e0e028c231a5e8efca6af410eabb
  743. // - http://nodejs.org/api/stream.html#stream_event_end
  744. // - http://nodejs.org/api/stream.html#stream_event_close_1
  745. var result = handleRedirect(res);
  746. if (result.redirect) {
  747. res.resume();
  748. createAndEmitResponseEvent(null, res);
  749. return;
  750. }
  751. if (result.error) {
  752. res.resume();
  753. // end ths stream first
  754. writeStream.end();
  755. done(result.error, null, res);
  756. return;
  757. }
  758. // you can set consumeWriteStream false that only wait response end
  759. if (args.consumeWriteStream === false) {
  760. res.on('end', done.bind(null, null, null, res));
  761. pump(res, writeStream, function(err) {
  762. if (isWriteStreamClose) {
  763. return;
  764. }
  765. isWriteStreamClose = true;
  766. debug('Request#%d %s: writeStream close, error: %s', reqId, url, err);
  767. });
  768. return;
  769. }
  770. // node 0.10, 0.12: only emit res aborted, writeStream close not fired
  771. if (isNode010 || isNode012) {
  772. first([
  773. [ writeStream, 'close' ],
  774. [ res, 'aborted' ],
  775. ], function(_, stream, event) {
  776. debug('Request#%d %s: writeStream or res %s event emitted', reqId, url, event);
  777. done(__err || null, null, res);
  778. });
  779. res.pipe(writeStream);
  780. return;
  781. }
  782. debug('Request#%d %s: pump res to writeStream', reqId, url);
  783. pump(res, writeStream, function(err) {
  784. debug('Request#%d %s: writeStream close event emitted, error: %s, isWriteStreamClose: %s',
  785. reqId, url, err, isWriteStreamClose);
  786. if (isWriteStreamClose) {
  787. return;
  788. }
  789. isWriteStreamClose = true;
  790. done(__err || err, null, res);
  791. });
  792. return;
  793. }
  794. // Otherwise, just concat those buffers.
  795. //
  796. // NOTE that the `chunk` is not a String but a Buffer. It means that if
  797. // you simply concat two chunk with `+` you're actually converting both
  798. // Buffers into Strings before concating them. It'll cause problems when
  799. // dealing with multi-byte characters.
  800. //
  801. // The solution is to store each chunk in an array and concat them with
  802. // 'buffer-concat' when all chunks is recieved.
  803. //
  804. // See also:
  805. // http://cnodejs.org/topic/4faf65852e8fb5bc65113403
  806. var chunks = [];
  807. res.on('data', function (chunk) {
  808. debug('Request#%d %s: `res data` event emit, size %d', reqId, url, chunk.length);
  809. responseSize += chunk.length;
  810. chunks.push(chunk);
  811. });
  812. var isEmitted = false;
  813. function handleResponseCloseAndEnd(event) {
  814. debug('Request#%d %s: `res %s` event emit, total size %d, socket handled %s requests and %s responses',
  815. reqId, url, event, responseSize, socketHandledRequests, socketHandledResponses);
  816. if (isEmitted) {
  817. return;
  818. }
  819. isEmitted = true;
  820. var body = Buffer.concat(chunks, responseSize);
  821. debug('Request#%d %s: _dumped: %s',
  822. reqId, url, res._dumped);
  823. if (__err) {
  824. // req.abort() after `res data` event emit.
  825. return done(__err, body, res);
  826. }
  827. var result = handleRedirect(res);
  828. if (result.error) {
  829. return done(result.error, body, res);
  830. }
  831. if (result.redirect) {
  832. createAndEmitResponseEvent(null, res);
  833. return;
  834. }
  835. decodeContent(res, body, function (err, data, encoding) {
  836. if (err) {
  837. return done(err, body, res);
  838. }
  839. // if body not decode, dont touch it
  840. if (!encoding && TEXT_DATA_TYPES.indexOf(args.dataType) >= 0) {
  841. // try to decode charset
  842. try {
  843. data = decodeBodyByCharset(data, res);
  844. } catch (e) {
  845. debug('decodeBodyByCharset error: %s', e);
  846. // if error, dont touch it
  847. return done(null, data, res);
  848. }
  849. if (args.dataType === 'json') {
  850. if (responseSize === 0) {
  851. data = null;
  852. } else {
  853. var r = parseJSON(data, fixJSONCtlChars);
  854. if (r.error) {
  855. err = r.error;
  856. } else {
  857. data = r.data;
  858. }
  859. }
  860. }
  861. }
  862. done(err, data, res);
  863. });
  864. }
  865. // node >= 14 only emit close if req abort
  866. res.on('close', function () {
  867. handleResponseCloseAndEnd('close');
  868. });
  869. res.on('end', function () {
  870. handleResponseCloseAndEnd('end');
  871. });
  872. }
  873. var connectTimeout, responseTimeout;
  874. if (Array.isArray(args.timeout)) {
  875. connectTimeout = ms(args.timeout[0]);
  876. responseTimeout = ms(args.timeout[1]);
  877. } else { // set both timeout equal
  878. connectTimeout = responseTimeout = ms(args.timeout);
  879. }
  880. debug('ConnectTimeout: %d, ResponseTimeout: %d', connectTimeout, responseTimeout);
  881. function startConnectTimer() {
  882. debug('Connect timer ticking, timeout: %d', connectTimeout);
  883. connectTimer = setTimeout(function () {
  884. connectTimer = null;
  885. if (statusCode === -1) {
  886. statusCode = -2;
  887. }
  888. var msg = 'Connect timeout for ' + connectTimeout + 'ms';
  889. var errorName = 'ConnectionTimeoutError';
  890. if (!req.socket) {
  891. errorName = 'SocketAssignTimeoutError';
  892. msg += ', working sockets is full';
  893. }
  894. __err = new Error(msg);
  895. __err.name = errorName;
  896. __err.requestId = reqId;
  897. debug('ConnectTimeout: Request#%d %s %s: %s, connected: %s', reqId, url, __err.name, msg, connected);
  898. abortRequest();
  899. }, connectTimeout);
  900. }
  901. function startResposneTimer() {
  902. debug('Response timer ticking, timeout: %d', responseTimeout);
  903. responseTimer = setTimeout(function () {
  904. responseTimer = null;
  905. var msg = 'Response timeout for ' + responseTimeout + 'ms';
  906. var errorName = 'ResponseTimeoutError';
  907. __err = new Error(msg);
  908. __err.name = errorName;
  909. __err.requestId = reqId;
  910. debug('ResponseTimeout: Request#%d %s %s: %s, connected: %s', reqId, url, __err.name, msg, connected);
  911. abortRequest();
  912. }, responseTimeout);
  913. }
  914. if (args.checkAddress) {
  915. var hostname = parsedUrl.hostname;
  916. // if request hostname is ip, custom lookup wont excute
  917. var family = null;
  918. if (ip.isV4Format(hostname)) {
  919. family = 4;
  920. } else if (ip.isV6Format(hostname)) {
  921. family = 6;
  922. }
  923. if (family) {
  924. if (!args.checkAddress(hostname, family)) {
  925. var err = new Error('illegal address');
  926. err.name = 'IllegalAddressError';
  927. err.hostname = hostname;
  928. err.ip = hostname;
  929. err.family = family;
  930. return done(err);
  931. }
  932. }
  933. }
  934. // request headers checker will throw error
  935. try {
  936. var finalOptions = options;
  937. // restore origin header key
  938. if (args.keepHeaderCase) {
  939. var originKeys = Object.keys(originHeaderKeys);
  940. if (originKeys.length) {
  941. var finalHeaders = {};
  942. var names = utility.getOwnEnumerables(options.headers, true);
  943. for (var i = 0; i < names.length; i++) {
  944. var name = names[i];
  945. finalHeaders[originHeaderKeys[name] || name] = options.headers[name];
  946. }
  947. finalOptions = Object.assign({}, options);
  948. finalOptions.headers = finalHeaders;
  949. }
  950. }
  951. req = httplib.request(finalOptions, onResponse);
  952. if (args.trace) {
  953. req._callSite = {};
  954. Error.captureStackTrace(req._callSite, requestWithCallback);
  955. }
  956. } catch (err) {
  957. return done(err);
  958. }
  959. // environment detection: browser or nodejs
  960. if (typeof(window) === 'undefined') {
  961. // start connect timer just after `request` return, and just in nodejs environment
  962. startConnectTimer();
  963. }
  964. var isRequestAborted = false;
  965. function abortRequest() {
  966. if (isRequestAborted) {
  967. return;
  968. }
  969. isRequestAborted = true;
  970. debug('Request#%d %s abort, connected: %s', reqId, url, connected);
  971. // it wont case error event when req haven't been assigned a socket yet.
  972. if (!req.socket) {
  973. __err.noSocket = true;
  974. done(__err);
  975. }
  976. req.abort();
  977. }
  978. if (timing) {
  979. // request sent
  980. req.on('finish', function() {
  981. timing.requestSent = Date.now() - requestStartTime;
  982. });
  983. }
  984. req.once('socket', function (socket) {
  985. if (timing) {
  986. // socket queuing time
  987. timing.queuing = Date.now() - requestStartTime;
  988. }
  989. // https://github.com/nodejs/node/blob/master/lib/net.js#L377
  990. // https://github.com/nodejs/node/blob/v0.10.40-release/lib/net.js#L352
  991. // should use socket.socket on 0.10.x
  992. if (isNode010 && socket.socket) {
  993. socket = socket.socket;
  994. }
  995. var orginalSocketTimeout = getSocketTimeout(socket);
  996. if (orginalSocketTimeout && orginalSocketTimeout < responseTimeout) {
  997. // make sure socket live longer than the response timer
  998. var socketTimeout = responseTimeout + 500;
  999. debug('Request#%d socket.timeout(%s) < responseTimeout(%s), reset socket timeout to %s',
  1000. reqId, orginalSocketTimeout, responseTimeout, socketTimeout);
  1001. socket.setTimeout(socketTimeout);
  1002. }
  1003. socketHandledRequests = socket[SOCKET_REQUEST_COUNT] = (socket[SOCKET_REQUEST_COUNT] || 0) + 1;
  1004. if (socket[SOCKET_RESPONSE_COUNT]) {
  1005. socketHandledResponses = socket[SOCKET_RESPONSE_COUNT];
  1006. }
  1007. var readyState = socket.readyState;
  1008. if (readyState === 'opening') {
  1009. socket.once('lookup', function(err, ip, addressType) {
  1010. debug('Request#%d %s lookup: %s, %s, %s', reqId, url, err, ip, addressType);
  1011. if (timing) {
  1012. timing.dnslookup = Date.now() - requestStartTime;
  1013. }
  1014. if (ip) {
  1015. remoteAddress = ip;
  1016. }
  1017. });
  1018. socket.once('connect', function() {
  1019. if (timing) {
  1020. // socket connected
  1021. timing.connected = Date.now() - requestStartTime;
  1022. }
  1023. // cancel socket timer at first and start tick for TTFB
  1024. cancelConnectTimer();
  1025. startResposneTimer();
  1026. debug('Request#%d %s new socket connected', reqId, url);
  1027. connected = true;
  1028. if (!remoteAddress) {
  1029. remoteAddress = socket.remoteAddress;
  1030. }
  1031. remotePort = socket.remotePort;
  1032. });
  1033. return;
  1034. }
  1035. debug('Request#%d %s reuse socket connected, readyState: %s', reqId, url, readyState);
  1036. connected = true;
  1037. keepAliveSocket = true;
  1038. if (!remoteAddress) {
  1039. remoteAddress = socket.remoteAddress;
  1040. }
  1041. remotePort = socket.remotePort;
  1042. // reuse socket, timer should be canceled.
  1043. cancelConnectTimer();
  1044. startResposneTimer();
  1045. });
  1046. if (writeStream) {
  1047. writeStream.once('error', function(err) {
  1048. err.message += ' (writeStream "error")';
  1049. __err = err;
  1050. debug('Request#%d %s `writeStream error` event emit, %s: %s', reqId, url, err.name, err.message);
  1051. abortRequest();
  1052. });
  1053. }
  1054. var isRequestDone = false;
  1055. function handleRequestError(err) {
  1056. if (!err) {
  1057. return;
  1058. }
  1059. // only ignore request error if response has been received
  1060. // if response has not received, socket error will emit on req
  1061. if (isRequestDone && hasResponse) {
  1062. return;
  1063. }
  1064. isRequestDone = true;
  1065. if (err.name === 'Error') {
  1066. err.name = connected ? 'ResponseError' : 'RequestError';
  1067. }
  1068. debug('Request#%d %s `req error` event emit, %s: %s', reqId, url, err.name, err.message);
  1069. done(__err || err);
  1070. }
  1071. if (args.stream) {
  1072. debug('Request#%d pump args.stream to req', reqId);
  1073. pump(args.stream, req, handleRequestError);
  1074. } else {
  1075. req.end(body, function () {
  1076. isRequestDone = true;
  1077. });
  1078. }
  1079. // when stream already consumed, req's `finish` event is emitted and pump will ignore error after pipe finished
  1080. // but if server response timeout later, we will abort the request and emit an error in req
  1081. // so we must always manually listen to req's `error` event here to ensure this error is handled
  1082. req.on('error', handleRequestError);
  1083. req.requestId = reqId;
  1084. return req;
  1085. }
  1086. exports.requestWithCallback = requestWithCallback;
  1087. var JSONCtlCharsMap = {
  1088. '"': '\\"', // \u0022
  1089. '\\': '\\\\', // \u005c
  1090. '\b': '\\b', // \u0008
  1091. '\f': '\\f', // \u000c
  1092. '\n': '\\n', // \u000a
  1093. '\r': '\\r', // \u000d
  1094. '\t': '\\t' // \u0009
  1095. };
  1096. var JSONCtlCharsRE = /[\u0000-\u001F\u005C]/g;
  1097. function _replaceOneChar(c) {
  1098. return JSONCtlCharsMap[c] || '\\u' + (c.charCodeAt(0) + 0x10000).toString(16).substr(1);
  1099. }
  1100. function replaceJSONCtlChars(str) {
  1101. return str.replace(JSONCtlCharsRE, _replaceOneChar);
  1102. }
  1103. function parseJSON(data, fixJSONCtlChars) {
  1104. var result = {
  1105. error: null,
  1106. data: null
  1107. };
  1108. if (fixJSONCtlChars) {
  1109. if (typeof fixJSONCtlChars === 'function') {
  1110. data = fixJSONCtlChars(data);
  1111. } else {
  1112. // https://github.com/node-modules/urllib/pull/77
  1113. // remote the control characters (U+0000 through U+001F)
  1114. data = replaceJSONCtlChars(data);
  1115. }
  1116. }
  1117. try {
  1118. result.data = JSON.parse(data);
  1119. } catch (err) {
  1120. if (err.name === 'SyntaxError') {
  1121. err.name = 'JSONResponseFormatError';
  1122. }
  1123. if (data.length > 1024) {
  1124. // show 0~512 ... -512~end data
  1125. err.message += ' (data json format: ' +
  1126. JSON.stringify(data.slice(0, 512)) + ' ...skip... ' + JSON.stringify(data.slice(data.length - 512)) + ')';
  1127. } else {
  1128. err.message += ' (data json format: ' + JSON.stringify(data) + ')';
  1129. }
  1130. result.error = err;
  1131. }
  1132. return result;
  1133. }
  1134. /**
  1135. * decode response body by parse `content-type`'s charset
  1136. * @param {Buffer} data
  1137. * @param {Http(s)Response} res
  1138. * @return {String}
  1139. */
  1140. function decodeBodyByCharset(data, res) {
  1141. var type = res.headers['content-type'];
  1142. if (!type) {
  1143. return data.toString();
  1144. }
  1145. var type = parseContentType(type);
  1146. var charset = type.parameters.charset || 'utf-8';
  1147. if (!Buffer.isEncoding(charset)) {
  1148. if (!_iconv) {
  1149. _iconv = require('iconv-lite');
  1150. }
  1151. return _iconv.decode(data, charset);
  1152. }
  1153. return data.toString(charset);
  1154. }
  1155. function getAgent(agent, defaultAgent) {
  1156. return agent === undefined ? defaultAgent : agent;
  1157. }
  1158. function parseContentType(str) {
  1159. try {
  1160. return contentTypeParser.parse(str);
  1161. } catch (err) {
  1162. // ignore content-type error, tread as default
  1163. return { parameters: {} };
  1164. }
  1165. }
  1166. function addLongStackTrace(err, req) {
  1167. if (!req) {
  1168. return;
  1169. }
  1170. var callSiteStack = req._callSite && req._callSite.stack;
  1171. if (!callSiteStack || typeof callSiteStack !== 'string') {
  1172. return;
  1173. }
  1174. if (err._longStack) {
  1175. return;
  1176. }
  1177. var index = callSiteStack.indexOf('\n');
  1178. if (index !== -1) {
  1179. err._longStack = true;
  1180. err.stack += LONG_STACK_DELIMITER + callSiteStack.substr(index + 1);
  1181. }
  1182. }
  1183. // node 8 don't has timeout attribute on socket
  1184. // https://github.com/nodejs/node/pull/21204/files#diff-e6ef024c3775d787c38487a6309e491dR408
  1185. function getSocketTimeout(socket) {
  1186. return socket.timeout || socket._idleTimeout;
  1187. }