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