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

test.js 19KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728
  1. /**
  2. * Module dependencies.
  3. */
  4. var fs = require('fs');
  5. var url = require('url');
  6. var net = require('net');
  7. var tls = require('tls');
  8. var http = require('http');
  9. var https = require('https');
  10. var WebSocket = require('ws');
  11. var assert = require('assert');
  12. var events = require('events');
  13. var inherits = require('util').inherits;
  14. var Agent = require('../');
  15. var PassthroughAgent = Agent(function(req, opts) {
  16. return opts.secureEndpoint ? https.globalAgent : http.globalAgent;
  17. });
  18. describe('Agent', function() {
  19. describe('subclass', function() {
  20. it('should be subclassable', function(done) {
  21. function MyAgent() {
  22. Agent.call(this);
  23. }
  24. inherits(MyAgent, Agent);
  25. MyAgent.prototype.callback = function(req, opts, fn) {
  26. assert.equal(req.path, '/foo');
  27. assert.equal(req.getHeader('host'), '127.0.0.1:1234');
  28. assert.equal(opts.secureEndpoint, true);
  29. done();
  30. };
  31. var info = url.parse('https://127.0.0.1:1234/foo');
  32. info.agent = new MyAgent();
  33. https.get(info);
  34. });
  35. });
  36. describe('options', function() {
  37. it('should support an options Object as first argument', function() {
  38. var agent = new Agent({ timeout: 1000 });
  39. assert.equal(1000, agent.timeout);
  40. });
  41. it('should support an options Object as second argument', function() {
  42. var agent = new Agent(function() {}, { timeout: 1000 });
  43. assert.equal(1000, agent.timeout);
  44. });
  45. it('should be mixed in with HTTP request options', function(done) {
  46. var agent = new Agent({
  47. host: 'my-proxy.com',
  48. port: 3128,
  49. foo: 'bar'
  50. });
  51. agent.callback = function(req, opts, fn) {
  52. assert.equal('bar', opts.foo);
  53. assert.equal('a', opts.b);
  54. // `host` and `port` are special-cases, and should always be
  55. // overwritten in the request `opts` inside the agent-base callback
  56. assert.equal('localhost', opts.host);
  57. assert.equal(80, opts.port);
  58. done();
  59. };
  60. var opts = {
  61. b: 'a',
  62. agent: agent
  63. };
  64. http.get(opts);
  65. });
  66. });
  67. describe('`this` context', function() {
  68. it('should be the Agent instance', function(done) {
  69. var called = false;
  70. var agent = new Agent();
  71. agent.callback = function() {
  72. called = true;
  73. assert.equal(this, agent);
  74. };
  75. var info = url.parse('http://127.0.0.1/foo');
  76. info.agent = agent;
  77. var req = http.get(info);
  78. req.on('error', function(err) {
  79. assert(/no Duplex stream was returned/.test(err.message));
  80. done();
  81. });
  82. });
  83. it('should be the Agent instance with callback signature', function(done) {
  84. var called = false;
  85. var agent = new Agent();
  86. agent.callback = function(req, opts, fn) {
  87. called = true;
  88. assert.equal(this, agent);
  89. fn();
  90. };
  91. var info = url.parse('http://127.0.0.1/foo');
  92. info.agent = agent;
  93. var req = http.get(info);
  94. req.on('error', function(err) {
  95. assert(/no Duplex stream was returned/.test(err.message));
  96. done();
  97. });
  98. });
  99. });
  100. describe('"error" event', function() {
  101. it('should be invoked on `http.ClientRequest` instance if `callback()` has not been defined', function(
  102. done
  103. ) {
  104. var agent = new Agent();
  105. var info = url.parse('http://127.0.0.1/foo');
  106. info.agent = agent;
  107. var req = http.get(info);
  108. req.on('error', function(err) {
  109. assert.equal(
  110. '"agent-base" has no default implementation, you must subclass and override `callback()`',
  111. err.message
  112. );
  113. done();
  114. });
  115. });
  116. it('should be invoked on `http.ClientRequest` instance if Error passed to callback function on the first tick', function(
  117. done
  118. ) {
  119. var agent = new Agent(function(req, opts, fn) {
  120. fn(new Error('is this caught?'));
  121. });
  122. var info = url.parse('http://127.0.0.1/foo');
  123. info.agent = agent;
  124. var req = http.get(info);
  125. req.on('error', function(err) {
  126. assert.equal('is this caught?', err.message);
  127. done();
  128. });
  129. });
  130. it('should be invoked on `http.ClientRequest` instance if Error passed to callback function after the first tick', function(
  131. done
  132. ) {
  133. var agent = new Agent(function(req, opts, fn) {
  134. setTimeout(function() {
  135. fn(new Error('is this caught?'));
  136. }, 10);
  137. });
  138. var info = url.parse('http://127.0.0.1/foo');
  139. info.agent = agent;
  140. var req = http.get(info);
  141. req.on('error', function(err) {
  142. assert.equal('is this caught?', err.message);
  143. done();
  144. });
  145. });
  146. });
  147. describe('artificial "streams"', function() {
  148. it('should send a GET request', function(done) {
  149. var stream = new events.EventEmitter();
  150. // needed for the `http` module to call .write() on the stream
  151. stream.writable = true;
  152. stream.write = function(str) {
  153. assert(0 == str.indexOf('GET / HTTP/1.1'));
  154. done();
  155. };
  156. // needed for `http` module in Node.js 4
  157. stream.cork = function() {};
  158. var opts = {
  159. method: 'GET',
  160. host: '127.0.0.1',
  161. path: '/',
  162. port: 80,
  163. agent: new Agent(function(req, opts, fn) {
  164. fn(null, stream);
  165. })
  166. };
  167. var req = http.request(opts);
  168. req.end();
  169. });
  170. it('should receive a GET response', function(done) {
  171. var stream = new events.EventEmitter();
  172. var opts = {
  173. method: 'GET',
  174. host: '127.0.0.1',
  175. path: '/',
  176. port: 80,
  177. agent: new Agent(function(req, opts, fn) {
  178. fn(null, stream);
  179. })
  180. };
  181. var req = http.request(opts, function(res) {
  182. assert.equal('1.0', res.httpVersion);
  183. assert.equal(200, res.statusCode);
  184. assert.equal('bar', res.headers.foo);
  185. assert.deepEqual(['1', '2'], res.headers['set-cookie']);
  186. done();
  187. });
  188. // have to wait for the "socket" event since `http.ClientRequest`
  189. // doesn't *actually* attach the listeners to the "stream" until
  190. // this happens
  191. req.once('socket', function() {
  192. var buf = Buffer.from(
  193. 'HTTP/1.0 200\r\n' +
  194. 'Foo: bar\r\n' +
  195. 'Set-Cookie: 1\r\n' +
  196. 'Set-Cookie: 2\r\n\r\n'
  197. );
  198. stream.emit('data', buf);
  199. });
  200. req.end();
  201. });
  202. });
  203. });
  204. describe('"http" module', function() {
  205. var server;
  206. var port;
  207. // setup test HTTP server
  208. before(function(done) {
  209. server = http.createServer();
  210. server.listen(0, function() {
  211. port = server.address().port;
  212. done();
  213. });
  214. });
  215. // shut down test HTTP server
  216. after(function(done) {
  217. server.once('close', function() {
  218. done();
  219. });
  220. server.close();
  221. });
  222. it('should work for basic HTTP requests', function(done) {
  223. var called = false;
  224. var agent = new Agent(function(req, opts, fn) {
  225. called = true;
  226. var socket = net.connect(opts);
  227. fn(null, socket);
  228. });
  229. // add HTTP server "request" listener
  230. var gotReq = false;
  231. server.once('request', function(req, res) {
  232. gotReq = true;
  233. res.setHeader('X-Foo', 'bar');
  234. res.setHeader('X-Url', req.url);
  235. res.end();
  236. });
  237. var info = url.parse('http://127.0.0.1:' + port + '/foo');
  238. info.agent = agent;
  239. http.get(info, function(res) {
  240. assert.equal('bar', res.headers['x-foo']);
  241. assert.equal('/foo', res.headers['x-url']);
  242. assert(gotReq);
  243. assert(called);
  244. done();
  245. });
  246. });
  247. it('should support direct return in `connect()`', function(done) {
  248. var called = false;
  249. var agent = new Agent(function(req, opts) {
  250. called = true;
  251. return net.connect(opts);
  252. });
  253. // add HTTP server "request" listener
  254. var gotReq = false;
  255. server.once('request', function(req, res) {
  256. gotReq = true;
  257. res.setHeader('X-Foo', 'bar');
  258. res.setHeader('X-Url', req.url);
  259. res.end();
  260. });
  261. var info = url.parse('http://127.0.0.1:' + port + '/foo');
  262. info.agent = agent;
  263. http.get(info, function(res) {
  264. assert.equal('bar', res.headers['x-foo']);
  265. assert.equal('/foo', res.headers['x-url']);
  266. assert(gotReq);
  267. assert(called);
  268. done();
  269. });
  270. });
  271. it('should support returning a Promise in `connect()`', function(done) {
  272. var called = false;
  273. var agent = new Agent(function(req, opts) {
  274. return new Promise(function(resolve, reject) {
  275. called = true;
  276. resolve(net.connect(opts));
  277. });
  278. });
  279. // add HTTP server "request" listener
  280. var gotReq = false;
  281. server.once('request', function(req, res) {
  282. gotReq = true;
  283. res.setHeader('X-Foo', 'bar');
  284. res.setHeader('X-Url', req.url);
  285. res.end();
  286. });
  287. var info = url.parse('http://127.0.0.1:' + port + '/foo');
  288. info.agent = agent;
  289. http.get(info, function(res) {
  290. assert.equal('bar', res.headers['x-foo']);
  291. assert.equal('/foo', res.headers['x-url']);
  292. assert(gotReq);
  293. assert(called);
  294. done();
  295. });
  296. });
  297. it('should set the `Connection: close` response header', function(done) {
  298. var called = false;
  299. var agent = new Agent(function(req, opts, fn) {
  300. called = true;
  301. var socket = net.connect(opts);
  302. fn(null, socket);
  303. });
  304. // add HTTP server "request" listener
  305. var gotReq = false;
  306. server.once('request', function(req, res) {
  307. gotReq = true;
  308. res.setHeader('X-Url', req.url);
  309. assert.equal('close', req.headers.connection);
  310. res.end();
  311. });
  312. var info = url.parse('http://127.0.0.1:' + port + '/bar');
  313. info.agent = agent;
  314. http.get(info, function(res) {
  315. assert.equal('/bar', res.headers['x-url']);
  316. assert.equal('close', res.headers.connection);
  317. assert(gotReq);
  318. assert(called);
  319. done();
  320. });
  321. });
  322. it('should pass through options from `http.request()`', function(done) {
  323. var agent = new Agent(function(req, opts, fn) {
  324. assert.equal('google.com', opts.host);
  325. assert.equal('bar', opts.foo);
  326. done();
  327. });
  328. http.get({
  329. host: 'google.com',
  330. foo: 'bar',
  331. agent: agent
  332. });
  333. });
  334. it('should default to port 80', function(done) {
  335. var agent = new Agent(function(req, opts, fn) {
  336. assert.equal(80, opts.port);
  337. done();
  338. });
  339. // (probably) not hitting a real HTTP server here,
  340. // so no need to add a httpServer request listener
  341. http.get({
  342. host: '127.0.0.1',
  343. path: '/foo',
  344. agent: agent
  345. });
  346. });
  347. it('should support the "timeout" option', function(done) {
  348. // ensure we timeout after the "error" event had a chance to trigger
  349. this.timeout(1000);
  350. this.slow(800);
  351. var agent = new Agent(
  352. function(req, opts, fn) {
  353. // this function will time out
  354. },
  355. { timeout: 100 }
  356. );
  357. var opts = url.parse('http://nodejs.org');
  358. opts.agent = agent;
  359. var req = http.get(opts);
  360. req.once('error', function(err) {
  361. assert.equal('ETIMEOUT', err.code);
  362. req.abort();
  363. done();
  364. });
  365. });
  366. it('should free sockets after use', function(done) {
  367. var agent = new Agent(function(req, opts, fn) {
  368. var socket = net.connect(opts);
  369. fn(null, socket);
  370. });
  371. // add HTTP server "request" listener
  372. var gotReq = false;
  373. server.once('request', function(req, res) {
  374. gotReq = true;
  375. res.end();
  376. });
  377. var info = url.parse('http://127.0.0.1:' + port + '/foo');
  378. info.agent = agent;
  379. http.get(info, function(res) {
  380. res.socket.emit('free');
  381. assert.equal(true, res.socket.destroyed);
  382. assert(gotReq);
  383. done();
  384. });
  385. });
  386. describe('PassthroughAgent', function() {
  387. it('should pass through to `http.globalAgent`', function(done) {
  388. // add HTTP server "request" listener
  389. var gotReq = false;
  390. server.once('request', function(req, res) {
  391. gotReq = true;
  392. res.setHeader('X-Foo', 'bar');
  393. res.setHeader('X-Url', req.url);
  394. res.end();
  395. });
  396. var info = url.parse('http://127.0.0.1:' + port + '/foo');
  397. info.agent = PassthroughAgent;
  398. http.get(info, function(res) {
  399. assert.equal('bar', res.headers['x-foo']);
  400. assert.equal('/foo', res.headers['x-url']);
  401. assert(gotReq);
  402. done();
  403. });
  404. });
  405. });
  406. });
  407. describe('"https" module', function() {
  408. var server;
  409. var port;
  410. // setup test HTTPS server
  411. before(function(done) {
  412. var options = {
  413. key: fs.readFileSync(__dirname + '/ssl-cert-snakeoil.key'),
  414. cert: fs.readFileSync(__dirname + '/ssl-cert-snakeoil.pem')
  415. };
  416. server = https.createServer(options);
  417. server.listen(0, function() {
  418. port = server.address().port;
  419. done();
  420. });
  421. });
  422. // shut down test HTTP server
  423. after(function(done) {
  424. server.once('close', function() {
  425. done();
  426. });
  427. server.close();
  428. });
  429. it('should not modify the passed in Options object', function(done) {
  430. var called = false;
  431. var agent = new Agent(function(req, opts, fn) {
  432. called = true;
  433. assert.equal(true, opts.secureEndpoint);
  434. assert.equal(443, opts.port);
  435. assert.equal('localhost', opts.host);
  436. });
  437. var opts = { agent: agent };
  438. var req = https.request(opts);
  439. assert.equal(true, called);
  440. assert.equal(false, 'secureEndpoint' in opts);
  441. assert.equal(false, 'port' in opts);
  442. done();
  443. });
  444. it('should work with a String URL', function(done) {
  445. var endpoint = 'https://127.0.0.1:' + port;
  446. var req = https.get(endpoint);
  447. // it's gonna error out since `rejectUnauthorized` is not being passed in
  448. req.on('error', function(err) {
  449. assert.equal(err.code, 'DEPTH_ZERO_SELF_SIGNED_CERT');
  450. done();
  451. });
  452. });
  453. it('should work for basic HTTPS requests', function(done) {
  454. var called = false;
  455. var agent = new Agent(function(req, opts, fn) {
  456. called = true;
  457. assert(opts.secureEndpoint);
  458. var socket = tls.connect(opts);
  459. fn(null, socket);
  460. });
  461. // add HTTPS server "request" listener
  462. var gotReq = false;
  463. server.once('request', function(req, res) {
  464. gotReq = true;
  465. res.setHeader('X-Foo', 'bar');
  466. res.setHeader('X-Url', req.url);
  467. res.end();
  468. });
  469. var info = url.parse('https://127.0.0.1:' + port + '/foo');
  470. info.agent = agent;
  471. info.rejectUnauthorized = false;
  472. https.get(info, function(res) {
  473. assert.equal('bar', res.headers['x-foo']);
  474. assert.equal('/foo', res.headers['x-url']);
  475. assert(gotReq);
  476. assert(called);
  477. done();
  478. });
  479. });
  480. it('should pass through options from `https.request()`', function(done) {
  481. var agent = new Agent(function(req, opts, fn) {
  482. assert.equal('google.com', opts.host);
  483. assert.equal('bar', opts.foo);
  484. done();
  485. });
  486. https.get({
  487. host: 'google.com',
  488. foo: 'bar',
  489. agent: agent
  490. });
  491. });
  492. it('should support the 3-argument `https.get()`', function(done) {
  493. var agent = new Agent(function(req, opts, fn) {
  494. assert.equal('google.com', opts.host);
  495. assert.equal('/q', opts.pathname || opts.path);
  496. assert.equal('881', opts.port);
  497. assert.equal('bar', opts.foo);
  498. done();
  499. });
  500. https.get(
  501. 'https://google.com:881/q',
  502. {
  503. host: 'google.com',
  504. foo: 'bar',
  505. agent: agent
  506. }
  507. );
  508. });
  509. it('should default to port 443', function(done) {
  510. var agent = new Agent(function(req, opts, fn) {
  511. assert.equal(true, opts.secureEndpoint);
  512. assert.equal(false, opts.rejectUnauthorized);
  513. assert.equal(443, opts.port);
  514. done();
  515. });
  516. // (probably) not hitting a real HTTPS server here,
  517. // so no need to add a httpsServer request listener
  518. https.get({
  519. host: '127.0.0.1',
  520. path: '/foo',
  521. agent: agent,
  522. rejectUnauthorized: false
  523. });
  524. });
  525. it('should not re-patch https.request', () => {
  526. var patchModulePath = "../patch-core";
  527. var patchedRequest = https.request;
  528. delete require.cache[require.resolve(patchModulePath)];
  529. require(patchModulePath);
  530. assert.equal(patchedRequest, https.request);
  531. assert.equal(true, https.request.__agent_base_https_request_patched__);
  532. });
  533. describe('PassthroughAgent', function() {
  534. it('should pass through to `https.globalAgent`', function(done) {
  535. // add HTTP server "request" listener
  536. var gotReq = false;
  537. server.once('request', function(req, res) {
  538. gotReq = true;
  539. res.setHeader('X-Foo', 'bar');
  540. res.setHeader('X-Url', req.url);
  541. res.end();
  542. });
  543. var info = url.parse('https://127.0.0.1:' + port + '/foo');
  544. info.agent = PassthroughAgent;
  545. info.rejectUnauthorized = false;
  546. https.get(info, function(res) {
  547. assert.equal('bar', res.headers['x-foo']);
  548. assert.equal('/foo', res.headers['x-url']);
  549. assert(gotReq);
  550. done();
  551. });
  552. });
  553. });
  554. });
  555. describe('"ws" server', function() {
  556. var wss;
  557. var server;
  558. var port;
  559. // setup test HTTP server
  560. before(function(done) {
  561. server = http.createServer();
  562. wss = new WebSocket.Server({ server: server });
  563. server.listen(0, function() {
  564. port = server.address().port;
  565. done();
  566. });
  567. });
  568. // shut down test HTTP server
  569. after(function(done) {
  570. server.once('close', function() {
  571. done();
  572. });
  573. server.close();
  574. });
  575. it('should work for basic WebSocket connections', function(done) {
  576. function onconnection(ws) {
  577. ws.on('message', function(data) {
  578. assert.equal('ping', data);
  579. ws.send('pong');
  580. });
  581. }
  582. wss.on('connection', onconnection);
  583. var agent = new Agent(function(req, opts, fn) {
  584. var socket = net.connect(opts);
  585. fn(null, socket);
  586. });
  587. var client = new WebSocket('ws://127.0.0.1:' + port + '/', {
  588. agent: agent
  589. });
  590. client.on('open', function() {
  591. client.send('ping');
  592. });
  593. client.on('message', function(data) {
  594. assert.equal('pong', data);
  595. client.close();
  596. wss.removeListener('connection', onconnection);
  597. done();
  598. });
  599. });
  600. });
  601. describe('"wss" server', function() {
  602. var wss;
  603. var server;
  604. var port;
  605. // setup test HTTP server
  606. before(function(done) {
  607. var options = {
  608. key: fs.readFileSync(__dirname + '/ssl-cert-snakeoil.key'),
  609. cert: fs.readFileSync(__dirname + '/ssl-cert-snakeoil.pem')
  610. };
  611. server = https.createServer(options);
  612. wss = new WebSocket.Server({ server: server });
  613. server.listen(0, function() {
  614. port = server.address().port;
  615. done();
  616. });
  617. });
  618. // shut down test HTTP server
  619. after(function(done) {
  620. server.once('close', function() {
  621. done();
  622. });
  623. server.close();
  624. });
  625. it('should work for secure WebSocket connections', function(done) {
  626. function onconnection(ws) {
  627. ws.on('message', function(data) {
  628. assert.equal('ping', data);
  629. ws.send('pong');
  630. });
  631. }
  632. wss.on('connection', onconnection);
  633. var agent = new Agent(function(req, opts, fn) {
  634. var socket = tls.connect(opts);
  635. fn(null, socket);
  636. });
  637. var client = new WebSocket('wss://127.0.0.1:' + port + '/', {
  638. agent: agent,
  639. rejectUnauthorized: false
  640. });
  641. client.on('open', function() {
  642. client.send('ping');
  643. });
  644. client.on('message', function(data) {
  645. assert.equal('pong', data);
  646. client.close();
  647. wss.removeListener('connection', onconnection);
  648. done();
  649. });
  650. });
  651. });