xhr.js 23 KB

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