xhr.js 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776
  1. 'use strict';
  2. const util = require('util');
  3. const urlutil = require('url');
  4. const http = require('http');
  5. const https = require('https');
  6. const debug = require('debug')('urllib');
  7. const ms = require('humanize-ms');
  8. let REQUEST_ID = 0;
  9. const MAX_VALUE = Math.pow(2, 31) - 10;
  10. const PROTO_RE = /^https?:\/\//i;
  11. function getAgent(agent, defaultAgent) {
  12. return agent === undefined ? defaultAgent : agent;
  13. }
  14. function parseContentType(str) {
  15. if (!str) {
  16. return '';
  17. }
  18. return str.split(';')[0].trim().toLowerCase();
  19. }
  20. function makeCallback(resolve, reject) {
  21. return function (err, data, res) {
  22. if (err) {
  23. return reject(err);
  24. }
  25. resolve({
  26. data: data,
  27. status: res.statusCode,
  28. headers: res.headers,
  29. res: res
  30. });
  31. };
  32. }
  33. // exports.TIMEOUT = ms('5s');
  34. exports.TIMEOUTS = [ms('300s'), ms('300s')];
  35. const TEXT_DATA_TYPES = [
  36. 'json',
  37. 'text'
  38. ];
  39. exports.request = function request(url, args, callback) {
  40. // request(url, callback)
  41. if (arguments.length === 2 && typeof args === 'function') {
  42. callback = args;
  43. args = null;
  44. }
  45. if (typeof callback === 'function') {
  46. return exports.requestWithCallback(url, args, callback);
  47. }
  48. return new Promise(function (resolve, reject) {
  49. exports.requestWithCallback(url, args, makeCallback(resolve, reject));
  50. });
  51. };
  52. exports.requestWithCallback = function requestWithCallback(url, args, callback) {
  53. if (!url || (typeof url !== 'string' && typeof url !== 'object')) {
  54. const msg = util.format('expect request url to be a string or a http request options, but got' +
  55. ' %j', url);
  56. throw new Error(msg);
  57. }
  58. if (arguments.length === 2 && typeof args === 'function') {
  59. callback = args;
  60. args = null;
  61. }
  62. args = args || {};
  63. if (REQUEST_ID >= MAX_VALUE) {
  64. REQUEST_ID = 0;
  65. }
  66. const reqId = ++REQUEST_ID;
  67. args.requestUrls = args.requestUrls || [];
  68. const reqMeta = {
  69. requestId: reqId,
  70. url: url,
  71. args: args,
  72. ctx: args.ctx,
  73. };
  74. if (args.emitter) {
  75. args.emitter.emit('request', reqMeta);
  76. }
  77. args.timeout = args.timeout || exports.TIMEOUTS;
  78. args.maxRedirects = args.maxRedirects || 10;
  79. args.streaming = args.streaming || args.customResponse;
  80. const requestStartTime = Date.now();
  81. let parsedUrl;
  82. if (typeof url === 'string') {
  83. if (!PROTO_RE.test(url)) {
  84. // Support `request('www.server.com')`
  85. url = 'https://' + url;
  86. }
  87. parsedUrl = urlutil.parse(url);
  88. } else {
  89. parsedUrl = url;
  90. }
  91. const method = (args.type || args.method || parsedUrl.method || 'GET').toUpperCase();
  92. let port = parsedUrl.port || 80;
  93. let httplib = http;
  94. let agent = getAgent(args.agent, exports.agent);
  95. const fixJSONCtlChars = args.fixJSONCtlChars;
  96. if (parsedUrl.protocol === 'https:') {
  97. httplib = https;
  98. agent = getAgent(args.httpsAgent, exports.httpsAgent);
  99. if (!parsedUrl.port) {
  100. port = 443;
  101. }
  102. }
  103. // request through proxy tunnel
  104. // var proxyTunnelAgent = detectProxyAgent(parsedUrl, args);
  105. // if (proxyTunnelAgent) {
  106. // agent = proxyTunnelAgent;
  107. // }
  108. const options = {
  109. host: parsedUrl.hostname || parsedUrl.host || 'localhost',
  110. path: parsedUrl.path || '/',
  111. method: method,
  112. port: port,
  113. agent: agent,
  114. headers: args.headers || {},
  115. // default is dns.lookup
  116. // https://github.com/nodejs/node/blob/master/lib/net.js#L986
  117. // custom dnslookup require node >= 4.0.0
  118. // https://github.com/nodejs/node/blob/archived-io.js-v0.12/lib/net.js#L952
  119. lookup: args.lookup,
  120. };
  121. if (Array.isArray(args.timeout)) {
  122. options.requestTimeout = args.timeout[args.timeout.length - 1];
  123. } else if (typeof args.timeout !== 'undefined') {
  124. options.requestTimeout = args.timeout;
  125. }
  126. // const sslNames = [
  127. // 'pfx',
  128. // 'key',
  129. // 'passphrase',
  130. // 'cert',
  131. // 'ca',
  132. // 'ciphers',
  133. // 'rejectUnauthorized',
  134. // 'secureProtocol',
  135. // 'secureOptions',
  136. // ];
  137. // for (let i = 0; i < sslNames.length; i++) {
  138. // const name = sslNames[i];
  139. // if (args.hasOwnProperty(name)) {
  140. // options[name] = args[name];
  141. // }
  142. // }
  143. // don't check ssl
  144. // if (options.rejectUnauthorized === false && !options.hasOwnProperty('secureOptions')) {
  145. // options.secureOptions = require('constants').SSL_OP_NO_TLSv1_2;
  146. // }
  147. const auth = args.auth || parsedUrl.auth;
  148. if (auth) {
  149. options.auth = auth;
  150. }
  151. // content undefined data 有值
  152. let body = args.content || args.data;
  153. const dataAsQueryString = method === 'GET' || method === 'HEAD' || args.dataAsQueryString;
  154. if (!args.content) {
  155. if (body && !(typeof body === 'string' || Buffer.isBuffer(body))) {
  156. if (dataAsQueryString) {
  157. // read: GET, HEAD, use query string
  158. body = args.nestedQuerystring ? qs.stringify(body) : querystring.stringify(body);
  159. } else {
  160. let contentType = options.headers['Content-Type'] || options.headers['content-type'];
  161. // auto add application/x-www-form-urlencoded when using urlencode form request
  162. if (!contentType) {
  163. if (args.contentType === 'json') {
  164. contentType = 'application/json';
  165. } else {
  166. contentType = 'application/x-www-form-urlencoded';
  167. }
  168. options.headers['Content-Type'] = contentType;
  169. }
  170. if (parseContentType(contentType) === 'application/json') {
  171. body = JSON.stringify(body);
  172. } else {
  173. // 'application/x-www-form-urlencoded'
  174. body = args.nestedQuerystring ? qs.stringify(body) : querystring.stringify(body);
  175. }
  176. }
  177. }
  178. }
  179. // if it's a GET or HEAD request, data should be sent as query string
  180. if (dataAsQueryString && body) {
  181. options.path += (parsedUrl.query ? '&' : '?') + body;
  182. body = null;
  183. }
  184. let requestSize = 0;
  185. if (body) {
  186. let length = body.length;
  187. if (!Buffer.isBuffer(body)) {
  188. length = Buffer.byteLength(body);
  189. }
  190. requestSize = options.headers['Content-Length'] = length;
  191. }
  192. if (args.dataType === 'json') {
  193. options.headers.Accept = 'application/json';
  194. }
  195. if (typeof args.beforeRequest === 'function') {
  196. // you can use this hook to change every thing.
  197. args.beforeRequest(options);
  198. }
  199. let connectTimer = null;
  200. let responseTimer = null;
  201. let __err = null;
  202. let connected = false; // socket connected or not
  203. let keepAliveSocket = false; // request with keepalive socket
  204. let responseSize = 0;
  205. let statusCode = -1;
  206. let responseAborted = false;
  207. let remoteAddress = '';
  208. let remotePort = '';
  209. let timing = null;
  210. if (args.timing) {
  211. timing = {
  212. // socket assigned
  213. queuing: 0,
  214. // dns lookup time
  215. dnslookup: 0,
  216. // socket connected
  217. connected: 0,
  218. // request sent
  219. requestSent: 0,
  220. // Time to first byte (TTFB)
  221. waiting: 0,
  222. contentDownload: 0,
  223. };
  224. }
  225. function cancelConnectTimer() {
  226. if (connectTimer) {
  227. clearTimeout(connectTimer);
  228. connectTimer = null;
  229. }
  230. }
  231. function cancelResponseTimer() {
  232. if (responseTimer) {
  233. clearTimeout(responseTimer);
  234. responseTimer = null;
  235. }
  236. }
  237. function done(err, data, res) {
  238. cancelResponseTimer();
  239. if (!callback) {
  240. console.warn('[urllib:warn] [%s] [%s] [worker:%s] %s %s callback twice!!!',
  241. Date(), reqId, process.pid, options.method, url);
  242. // https://github.com/node-modules/urllib/pull/30
  243. if (err) {
  244. console.warn('[urllib:warn] [%s] [%s] [worker:%s] %s: %s\nstack: %s',
  245. Date(), reqId, process.pid, err.name, err.message, err.stack);
  246. }
  247. return;
  248. }
  249. const cb = callback;
  250. callback = null;
  251. let headers = {};
  252. if (res) {
  253. statusCode = res.statusCode;
  254. headers = res.headers;
  255. }
  256. // handle digest auth
  257. // if (statusCode === 401 && headers['www-authenticate']
  258. // && (!args.headers || !args.headers.Authorization) && args.digestAuth) {
  259. // const authenticate = headers['www-authenticate'];
  260. // if (authenticate.indexOf('Digest ') >= 0) {
  261. // debug('Request#%d %s: got digest auth header WWW-Authenticate: %s', reqId, url, authenticate);
  262. // args.headers = args.headers || {};
  263. // args.headers.Authorization = digestAuthHeader(options.method, options.path, authenticate, args.digestAuth);
  264. // debug('Request#%d %s: auth with digest header: %s', reqId, url, args.headers.Authorization);
  265. // if (res.headers['set-cookie']) {
  266. // args.headers.Cookie = res.headers['set-cookie'].join(';');
  267. // }
  268. // return exports.requestWithCallback(url, args, cb);
  269. // }
  270. // }
  271. const requestUseTime = Date.now() - requestStartTime;
  272. if (timing) {
  273. timing.contentDownload = requestUseTime;
  274. }
  275. debug('[%sms] done, %s bytes HTTP %s %s %s %s, keepAliveSocket: %s, timing: %j',
  276. requestUseTime, responseSize, statusCode, options.method, options.host, options.path,
  277. keepAliveSocket, timing);
  278. const response = {
  279. status: statusCode,
  280. statusCode: statusCode,
  281. headers: headers,
  282. size: responseSize,
  283. aborted: responseAborted,
  284. rt: requestUseTime,
  285. keepAliveSocket: keepAliveSocket,
  286. data: data,
  287. requestUrls: args.requestUrls,
  288. timing: timing,
  289. remoteAddress: remoteAddress,
  290. remotePort: remotePort,
  291. };
  292. if (err) {
  293. let agentStatus = '';
  294. if (agent && typeof agent.getCurrentStatus === 'function') {
  295. // add current agent status to error message for logging and debug
  296. agentStatus = ', agent status: ' + JSON.stringify(agent.getCurrentStatus());
  297. }
  298. err.message += ', ' + options.method + ' ' + url + ' ' + statusCode
  299. + ' (connected: ' + connected + ', keepalive socket: ' + keepAliveSocket + agentStatus + ')'
  300. + '\nheaders: ' + JSON.stringify(headers);
  301. err.data = data;
  302. err.path = options.path;
  303. err.status = statusCode;
  304. err.headers = headers;
  305. err.res = response;
  306. }
  307. cb(err, data, args.streaming ? res : response);
  308. if (args.emitter) {
  309. // keep to use the same reqMeta object on request event before
  310. reqMeta.url = url;
  311. reqMeta.socket = req && req.connection;
  312. reqMeta.options = options;
  313. reqMeta.size = requestSize;
  314. args.emitter.emit('response', {
  315. requestId: reqId,
  316. error: err,
  317. ctx: args.ctx,
  318. req: reqMeta,
  319. res: response,
  320. });
  321. }
  322. }
  323. function handleRedirect(res) {
  324. let err = null;
  325. if (args.followRedirect && statuses.redirect[res.statusCode]) { // handle redirect
  326. args._followRedirectCount = (args._followRedirectCount || 0) + 1;
  327. const location = res.headers.location;
  328. if (!location) {
  329. err = new Error('Got statusCode ' + res.statusCode + ' but cannot resolve next location from headers');
  330. err.name = 'FollowRedirectError';
  331. } else if (args._followRedirectCount > args.maxRedirects) {
  332. err = new Error('Exceeded maxRedirects. Probably stuck in a redirect loop ' + url);
  333. err.name = 'MaxRedirectError';
  334. } else {
  335. const newUrl = args.formatRedirectUrl ? args.formatRedirectUrl(url, location) : urlutil.resolve(url, location);
  336. debug('Request#%d %s: `redirected` from %s to %s', reqId, options.path, url, newUrl);
  337. // make sure timer stop
  338. cancelResponseTimer();
  339. // should clean up headers.Host on `location: http://other-domain/url`
  340. if (args.headers && args.headers.Host && PROTO_RE.test(location)) {
  341. args.headers.Host = null;
  342. }
  343. // avoid done will be execute in the future change.
  344. const cb = callback;
  345. callback = null;
  346. exports.requestWithCallback(newUrl, args, cb);
  347. return {
  348. redirect: true,
  349. error: null
  350. };
  351. }
  352. }
  353. return {
  354. redirect: false,
  355. error: err
  356. };
  357. }
  358. if (args.gzip) {
  359. if (!options.headers['Accept-Encoding'] && !options.headers['accept-encoding']) {
  360. options.headers['Accept-Encoding'] = 'gzip';
  361. }
  362. }
  363. function decodeContent(res, body, cb) {
  364. const encoding = res.headers['content-encoding'];
  365. // if (body.length === 0) {
  366. // return cb(null, body, encoding);
  367. // }
  368. // if (!encoding || encoding.toLowerCase() !== 'gzip') {
  369. return cb(null, body, encoding);
  370. // }
  371. // debug('gunzip %d length body', body.length);
  372. // zlib.gunzip(body, cb);
  373. }
  374. const writeStream = args.writeStream;
  375. debug('Request#%d %s %s with headers %j, options.path: %s',
  376. reqId, method, url, options.headers, options.path);
  377. args.requestUrls.push(url);
  378. function onResponse(res) {
  379. if (timing) {
  380. timing.waiting = Date.now() - requestStartTime;
  381. }
  382. debug('Request#%d %s `req response` event emit: status %d, headers: %j',
  383. reqId, url, res.statusCode, res.headers);
  384. if (args.streaming) {
  385. const result = handleRedirect(res);
  386. if (result.redirect) {
  387. res.resume();
  388. return;
  389. }
  390. if (result.error) {
  391. res.resume();
  392. return done(result.error, null, res);
  393. }
  394. return done(null, null, res);
  395. }
  396. res.on('close', function () {
  397. debug('Request#%d %s: `res close` event emit, total size %d',
  398. reqId, url, responseSize);
  399. });
  400. res.on('error', function () {
  401. debug('Request#%d %s: `res error` event emit, total size %d',
  402. reqId, url, responseSize);
  403. });
  404. res.on('aborted', function () {
  405. responseAborted = true;
  406. debug('Request#%d %s: `res aborted` event emit, total size %d',
  407. reqId, url, responseSize);
  408. });
  409. if (writeStream) {
  410. // If there's a writable stream to recieve the response data, just pipe the
  411. // response stream to that writable stream and call the callback when it has
  412. // finished writing.
  413. //
  414. // NOTE that when the response stream `res` emits an 'end' event it just
  415. // means that it has finished piping data to another stream. In the
  416. // meanwhile that writable stream may still writing data to the disk until
  417. // it emits a 'close' event.
  418. //
  419. // That means that we should not apply callback until the 'close' of the
  420. // writable stream is emited.
  421. //
  422. // See also:
  423. // - https://github.com/TBEDP/urllib/commit/959ac3365821e0e028c231a5e8efca6af410eabb
  424. // - http://nodejs.org/api/stream.html#stream_event_end
  425. // - http://nodejs.org/api/stream.html#stream_event_close_1
  426. const result = handleRedirect(res);
  427. if (result.redirect) {
  428. res.resume();
  429. return;
  430. }
  431. if (result.error) {
  432. res.resume();
  433. // end ths stream first
  434. writeStream.end();
  435. return done(result.error, null, res);
  436. }
  437. // you can set consumeWriteStream false that only wait response end
  438. if (args.consumeWriteStream === false) {
  439. res.on('end', done.bind(null, null, null, res));
  440. } else {
  441. // node 0.10, 0.12: only emit res aborted, writeStream close not fired
  442. // if (isNode010 || isNode012) {
  443. // first([
  444. // [ writeStream, 'close' ],
  445. // [ res, 'aborted' ],
  446. // ], function(_, stream, event) {
  447. // debug('Request#%d %s: writeStream or res %s event emitted', reqId, url, event);
  448. // done(__err || null, null, res);
  449. // });
  450. if (false) {
  451. } else {
  452. writeStream.on('close', function() {
  453. debug('Request#%d %s: writeStream close event emitted', reqId, url);
  454. done(__err || null, null, res);
  455. });
  456. }
  457. }
  458. return res.pipe(writeStream);
  459. }
  460. // Otherwise, just concat those buffers.
  461. //
  462. // NOTE that the `chunk` is not a String but a Buffer. It means that if
  463. // you simply concat two chunk with `+` you're actually converting both
  464. // Buffers into Strings before concating them. It'll cause problems when
  465. // dealing with multi-byte characters.
  466. //
  467. // The solution is to store each chunk in an array and concat them with
  468. // 'buffer-concat' when all chunks is recieved.
  469. //
  470. // See also:
  471. // http://cnodejs.org/topic/4faf65852e8fb5bc65113403
  472. const chunks = [];
  473. res.on('data', function (chunk) {
  474. debug('Request#%d %s: `res data` event emit, size %d', reqId, url, chunk.length);
  475. responseSize += chunk.length;
  476. chunks.push(chunk);
  477. });
  478. res.on('end', function () {
  479. const body = Buffer.concat(chunks, responseSize);
  480. debug('Request#%d %s: `res end` event emit, total size %d, _dumped: %s',
  481. reqId, url, responseSize, res._dumped);
  482. if (__err) {
  483. // req.abort() after `res data` event emit.
  484. return done(__err, body, res);
  485. }
  486. const result = handleRedirect(res);
  487. if (result.error) {
  488. return done(result.error, body, res);
  489. }
  490. if (result.redirect) {
  491. return;
  492. }
  493. decodeContent(res, body, function (err, data, encoding) {
  494. if (err) {
  495. return done(err, body, res);
  496. }
  497. // if body not decode, dont touch it
  498. if (!encoding && TEXT_DATA_TYPES.indexOf(args.dataType) >= 0) {
  499. // try to decode charset
  500. try {
  501. data = decodeBodyByCharset(data, res);
  502. } catch (e) {
  503. debug('decodeBodyByCharset error: %s', e);
  504. // if error, dont touch it
  505. return done(null, data, res);
  506. }
  507. if (args.dataType === 'json') {
  508. if (responseSize === 0) {
  509. data = null;
  510. } else {
  511. const r = parseJSON(data, fixJSONCtlChars);
  512. if (r.error) {
  513. err = r.error;
  514. } else {
  515. data = r.data;
  516. }
  517. }
  518. }
  519. }
  520. if (responseAborted) {
  521. // err = new Error('Remote socket was terminated before `response.end()` was called');
  522. // err.name = 'RemoteSocketClosedError';
  523. debug('Request#%d %s: Remote socket was terminated before `response.end()` was called', reqId, url);
  524. }
  525. done(err, data, res);
  526. });
  527. });
  528. }
  529. let connectTimeout, responseTimeout;
  530. if (Array.isArray(args.timeout)) {
  531. connectTimeout = ms(args.timeout[0]);
  532. responseTimeout = ms(args.timeout[1]);
  533. } else { // set both timeout equal
  534. connectTimeout = responseTimeout = ms(args.timeout);
  535. }
  536. debug('ConnectTimeout: %d, ResponseTimeout: %d', connectTimeout, responseTimeout);
  537. function startConnectTimer() {
  538. debug('Connect timer ticking, timeout: %d', connectTimeout);
  539. connectTimer = setTimeout(function () {
  540. connectTimer = null;
  541. if (statusCode === -1) {
  542. statusCode = -2;
  543. }
  544. let msg = 'Connect timeout for ' + connectTimeout + 'ms';
  545. let errorName = 'ConnectionTimeoutError';
  546. if (!req.socket) {
  547. errorName = 'SocketAssignTimeoutError';
  548. msg += ', working sockets is full';
  549. }
  550. __err = new Error(msg);
  551. __err.name = errorName;
  552. __err.requestId = reqId;
  553. debug('ConnectTimeout: Request#%d %s %s: %s, connected: %s', reqId, url, __err.name, msg, connected);
  554. abortRequest();
  555. }, connectTimeout);
  556. }
  557. function startResposneTimer() {
  558. debug('Response timer ticking, timeout: %d', responseTimeout);
  559. responseTimer = setTimeout(function () {
  560. responseTimer = null;
  561. const msg = 'Response timeout for ' + responseTimeout + 'ms';
  562. const errorName = 'ResponseTimeoutError';
  563. __err = new Error(msg);
  564. __err.name = errorName;
  565. __err.requestId = reqId;
  566. debug('ResponseTimeout: Request#%d %s %s: %s, connected: %s', reqId, url, __err.name, msg, connected);
  567. abortRequest();
  568. }, responseTimeout);
  569. }
  570. let req;
  571. // request headers checker will throw error
  572. options.mode = args.mode ? args.mode : '';
  573. try {
  574. req = httplib.request(options, onResponse);
  575. } catch (err) {
  576. return done(err);
  577. }
  578. // environment detection: browser or nodejs
  579. if (typeof(window) === 'undefined') {
  580. // start connect timer just after `request` return, and just in nodejs environment
  581. startConnectTimer();
  582. } else {
  583. req.on('requestTimeout', function () {
  584. if (statusCode === -1) {
  585. statusCode = -2;
  586. }
  587. const msg = 'Connect timeout for ' + connectTimeout + 'ms';
  588. const errorName = 'ConnectionTimeoutError';
  589. __err = new Error(msg);
  590. __err.name = errorName;
  591. __err.requestId = reqId;
  592. abortRequest();
  593. });
  594. }
  595. function abortRequest() {
  596. debug('Request#%d %s abort, connected: %s', reqId, url, connected);
  597. // it wont case error event when req haven't been assigned a socket yet.
  598. if (!req.socket) {
  599. __err.noSocket = true;
  600. done(__err);
  601. }
  602. req.abort();
  603. }
  604. if (timing) {
  605. // request sent
  606. req.on('finish', function() {
  607. timing.requestSent = Date.now() - requestStartTime;
  608. });
  609. }
  610. req.once('socket', function (socket) {
  611. if (timing) {
  612. // socket queuing time
  613. timing.queuing = Date.now() - requestStartTime;
  614. }
  615. // https://github.com/nodejs/node/blob/master/lib/net.js#L377
  616. // https://github.com/nodejs/node/blob/v0.10.40-release/lib/net.js#L352
  617. // should use socket.socket on 0.10.x
  618. // if (isNode010 && socket.socket) {
  619. // socket = socket.socket;
  620. // }
  621. const readyState = socket.readyState;
  622. if (readyState === 'opening') {
  623. socket.once('lookup', function(err, ip, addressType) {
  624. debug('Request#%d %s lookup: %s, %s, %s', reqId, url, err, ip, addressType);
  625. if (timing) {
  626. timing.dnslookup = Date.now() - requestStartTime;
  627. }
  628. if (ip) {
  629. remoteAddress = ip;
  630. }
  631. });
  632. socket.once('connect', function() {
  633. if (timing) {
  634. // socket connected
  635. timing.connected = Date.now() - requestStartTime;
  636. }
  637. // cancel socket timer at first and start tick for TTFB
  638. cancelConnectTimer();
  639. startResposneTimer();
  640. debug('Request#%d %s new socket connected', reqId, url);
  641. connected = true;
  642. if (!remoteAddress) {
  643. remoteAddress = socket.remoteAddress;
  644. }
  645. remotePort = socket.remotePort;
  646. });
  647. return;
  648. }
  649. debug('Request#%d %s reuse socket connected, readyState: %s', reqId, url, readyState);
  650. connected = true;
  651. keepAliveSocket = true;
  652. if (!remoteAddress) {
  653. remoteAddress = socket.remoteAddress;
  654. }
  655. remotePort = socket.remotePort;
  656. // reuse socket, timer should be canceled.
  657. cancelConnectTimer();
  658. startResposneTimer();
  659. });
  660. req.on('error', function (err) {
  661. //TypeError for browser fetch api, Error for browser xmlhttprequest api
  662. if (err.name === 'Error' || err.name === 'TypeError') {
  663. err.name = connected ? 'ResponseError' : 'RequestError';
  664. }
  665. err.message += ' (req "error")';
  666. debug('Request#%d %s `req error` event emit, %s: %s', reqId, url, err.name, err.message);
  667. done(__err || err);
  668. });
  669. if (writeStream) {
  670. writeStream.once('error', function (err) {
  671. err.message += ' (writeStream "error")';
  672. __err = err;
  673. debug('Request#%d %s `writeStream error` event emit, %s: %s', reqId, url, err.name, err.message);
  674. abortRequest();
  675. });
  676. }
  677. if (args.stream) {
  678. args.stream.pipe(req);
  679. args.stream.once('error', function (err) {
  680. err.message += ' (stream "error")';
  681. __err = err;
  682. debug('Request#%d %s `readStream error` event emit, %s: %s', reqId, url, err.name, err.message);
  683. abortRequest();
  684. });
  685. } else {
  686. req.end(body);
  687. }
  688. req.requestId = reqId;
  689. return req;
  690. };