http.js (27164B)
1 var expect = require('chai').expect; 2 var util = require('./util'); 3 var fs = require('fs'); 4 var path = require('path'); 5 var url = require('url'); 6 var net = require('net'); 7 8 var http2 = require('../lib/http'); 9 var https = require('https'); 10 11 var serverOptions = { 12 key: fs.readFileSync(path.join(__dirname, '../example/localhost.key')), 13 cert: fs.readFileSync(path.join(__dirname, '../example/localhost.crt')), 14 rejectUnauthorized: true, 15 log: util.serverLog 16 }; 17 18 var agentOptions = { 19 key: serverOptions.key, 20 ca: serverOptions.cert, 21 rejectUnauthorized: true, 22 log: util.clientLog 23 }; 24 25 var globalAgent = new http2.Agent(agentOptions); 26 27 describe('http.js', function() { 28 beforeEach(function() { 29 http2.globalAgent = globalAgent; 30 }); 31 describe('Server', function() { 32 describe('new Server(options)', function() { 33 it('should throw if called without \'plain\' or TLS options', function() { 34 expect(function() { 35 new http2.Server(); 36 }).to.throw(Error); 37 expect(function() { 38 http2.createServer(util.noop); 39 }).to.throw(Error); 40 }); 41 }); 42 describe('method `listen()`', function () { 43 it('should emit `listening` event', function (done) { 44 var server = http2.createServer(serverOptions); 45 46 server.on('listening', function () { 47 server.close(); 48 49 done(); 50 }) 51 52 server.listen(0); 53 }); 54 it('should emit `error` on failure', function (done) { 55 var server = http2.createServer(serverOptions); 56 57 // This TCP server is used to explicitly take a port to make 58 // server.listen() fails. 59 var net = require('net').createServer(); 60 61 server.on('error', function () { 62 net.close() 63 64 done(); 65 }); 66 67 net.listen(0, function () { 68 server.listen(this.address().port); 69 }); 70 }); 71 }); 72 describe('property `timeout`', function() { 73 it('should be a proxy for the backing HTTPS server\'s `timeout` property', function() { 74 var server = new http2.Server(serverOptions); 75 var backingServer = server._server; 76 var newTimeout = 10; 77 server.timeout = newTimeout; 78 expect(server.timeout).to.be.equal(newTimeout); 79 expect(backingServer.timeout).to.be.equal(newTimeout); 80 }); 81 }); 82 describe('method `setTimeout(timeout, [callback])`', function() { 83 it('should be a proxy for the backing HTTPS server\'s `setTimeout` method', function() { 84 var server = new http2.Server(serverOptions); 85 var backingServer = server._server; 86 var newTimeout = 10; 87 var newCallback = util.noop; 88 backingServer.setTimeout = function(timeout, callback) { 89 expect(timeout).to.be.equal(newTimeout); 90 expect(callback).to.be.equal(newCallback); 91 }; 92 server.setTimeout(newTimeout, newCallback); 93 }); 94 }); 95 }); 96 describe('Agent', function() { 97 describe('property `maxSockets`', function() { 98 it('should be a proxy for the backing HTTPS agent\'s `maxSockets` property', function() { 99 var agent = new http2.Agent({ log: util.clientLog }); 100 var backingAgent = agent._httpsAgent; 101 var newMaxSockets = backingAgent.maxSockets + 1; 102 agent.maxSockets = newMaxSockets; 103 expect(agent.maxSockets).to.be.equal(newMaxSockets); 104 expect(backingAgent.maxSockets).to.be.equal(newMaxSockets); 105 }); 106 }); 107 describe('method `request(options, [callback])`', function() { 108 it('should use a new agent for request-specific TLS settings', function(done) { 109 var path = '/x'; 110 var message = 'Hello world'; 111 112 var server = http2.createServer(serverOptions, function(request, response) { 113 expect(request.url).to.equal(path); 114 response.end(message); 115 }); 116 117 server.listen(1234, function() { 118 var options = url.parse('https://localhost:1234' + path); 119 options.key = agentOptions.key; 120 options.ca = agentOptions.ca; 121 options.rejectUnauthorized = true; 122 123 http2.globalAgent = new http2.Agent({ log: util.clientLog }); 124 http2.get(options, function(response) { 125 response.on('data', function(data) { 126 expect(data.toString()).to.equal(message); 127 server.close(); 128 done(); 129 }); 130 }); 131 }); 132 }); 133 it('should throw when trying to use with \'http\' scheme', function() { 134 expect(function() { 135 var agent = new http2.Agent({ log: util.clientLog }); 136 agent.request({ protocol: 'http:' }); 137 }).to.throw(Error); 138 }); 139 }); 140 }); 141 describe('OutgoingRequest', function() { 142 function testFallbackProxyMethod(name, originalArguments, done) { 143 var request = new http2.OutgoingRequest(); 144 145 // When in HTTP/2 mode, this call should be ignored 146 request.stream = { reset: util.noop }; 147 request[name].apply(request, originalArguments); 148 delete request.stream; 149 150 // When in fallback mode, this call should be forwarded 151 request[name].apply(request, originalArguments); 152 var mockFallbackRequest = { on: util.noop }; 153 mockFallbackRequest[name] = function() { 154 expect(Array.prototype.slice.call(arguments)).to.deep.equal(originalArguments); 155 done(); 156 }; 157 request._fallback(mockFallbackRequest); 158 } 159 describe('method `setNoDelay(noDelay)`', function() { 160 it('should act as a proxy for the backing HTTPS agent\'s `setNoDelay` method', function(done) { 161 testFallbackProxyMethod('setNoDelay', [true], done); 162 }); 163 }); 164 describe('method `setSocketKeepAlive(enable, initialDelay)`', function() { 165 it('should act as a proxy for the backing HTTPS agent\'s `setSocketKeepAlive` method', function(done) { 166 testFallbackProxyMethod('setSocketKeepAlive', [true, util.random(10, 100)], done); 167 }); 168 }); 169 describe('method `setTimeout(timeout, [callback])`', function() { 170 it('should act as a proxy for the backing HTTPS agent\'s `setTimeout` method', function(done) { 171 testFallbackProxyMethod('setTimeout', [util.random(10, 100), util.noop], done); 172 }); 173 }); 174 describe('method `abort()`', function() { 175 it('should act as a proxy for the backing HTTPS agent\'s `abort` method', function(done) { 176 testFallbackProxyMethod('abort', [], done); 177 }); 178 }); 179 }); 180 describe('OutgoingResponse', function() { 181 it('should throw error when writeHead is called multiple times on it', function() { 182 var called = false; 183 var stream = { _log: util.log, headers: function () { 184 if (called) { 185 throw new Error('Should not send headers twice'); 186 } else { 187 called = true; 188 } 189 }, once: util.noop }; 190 var response = new http2.OutgoingResponse(stream); 191 192 response.writeHead(200); 193 response.writeHead(404); 194 }); 195 it('field finished should be Boolean', function(){ 196 var stream = { _log: util.log, headers: function () {}, once: util.noop }; 197 var response = new http2.OutgoingResponse(stream); 198 expect(response.finished).to.be.a('Boolean'); 199 }); 200 it('field finished should initially be false and then go to true when response completes',function(done){ 201 var res; 202 var server = http2.createServer(serverOptions, function(request, response) { 203 res = response; 204 expect(res.finished).to.be.false; 205 response.end('HiThere'); 206 }); 207 server.listen(1236, function() { 208 http2.get('https://localhost:1236/finished-test', function(response) { 209 response.on('data', function(data){ 210 var sink = data; // 211 }); 212 response.on('end',function(){ 213 expect(res.finished).to.be.true; 214 server.close(); 215 done(); 216 }); 217 }); 218 }); 219 }); 220 }); 221 describe('test scenario', function() { 222 describe('simple request', function() { 223 it('should work as expected', function(done) { 224 var path = '/x'; 225 var message = 'Hello world'; 226 227 var server = http2.createServer(serverOptions, function(request, response) { 228 expect(request.url).to.equal(path); 229 response.end(message); 230 }); 231 232 server.listen(1234, function() { 233 http2.get('https://localhost:1234' + path, function(response) { 234 response.on('data', function(data) { 235 expect(data.toString()).to.equal(message); 236 server.close(); 237 done(); 238 }); 239 }); 240 }); 241 }); 242 }); 243 describe('2 simple request in parallel', function() { 244 it('should work as expected', function(originalDone) { 245 var path = '/x'; 246 var message = 'Hello world'; 247 var done = util.callNTimes(2, function() { 248 server.close(); 249 originalDone(); 250 }); 251 252 var server = http2.createServer(serverOptions, function(request, response) { 253 expect(request.url).to.equal(path); 254 response.end(message); 255 }); 256 257 server.listen(1234, function() { 258 http2.get('https://localhost:1234' + path, function(response) { 259 response.on('data', function(data) { 260 expect(data.toString()).to.equal(message); 261 done(); 262 }); 263 }); 264 http2.get('https://localhost:1234' + path, function(response) { 265 response.on('data', function(data) { 266 expect(data.toString()).to.equal(message); 267 done(); 268 }); 269 }); 270 }); 271 }); 272 }); 273 describe('100 simple request in a series', function() { 274 it('should work as expected', function(done) { 275 var path = '/x'; 276 var message = 'Hello world'; 277 278 var server = http2.createServer(serverOptions, function(request, response) { 279 expect(request.url).to.equal(path); 280 response.end(message); 281 }); 282 283 var n = 100; 284 server.listen(1242, function() { 285 doRequest(); 286 function doRequest() { 287 http2.get('https://localhost:1242' + path, function(response) { 288 response.on('data', function(data) { 289 expect(data.toString()).to.equal(message); 290 if (n) { 291 n -= 1; 292 doRequest(); 293 } else { 294 server.close(); 295 done(); 296 } 297 }); 298 }); 299 } 300 }); 301 }); 302 }); 303 describe('request with payload', function() { 304 it('should work as expected', function(done) { 305 var path = '/x'; 306 var message = 'Hello world'; 307 308 var server = http2.createServer(serverOptions, function(request, response) { 309 expect(request.url).to.equal(path); 310 request.once('data', function(data) { 311 expect(data.toString()).to.equal(message); 312 response.end(); 313 }); 314 }); 315 316 server.listen(1240, function() { 317 var request = http2.request({ 318 host: 'localhost', 319 port: 1240, 320 path: path 321 }); 322 request.write(message); 323 request.end(); 324 request.on('response', function() { 325 server.close(); 326 done(); 327 }); 328 }); 329 }); 330 }); 331 describe('request with custom status code and headers', function() { 332 it('should work as expected', function(done) { 333 var path = '/x'; 334 var message = 'Hello world'; 335 var headerName = 'name'; 336 var headerValue = 'value'; 337 338 var server = http2.createServer(serverOptions, function(request, response) { 339 // Request URL and headers 340 expect(request.url).to.equal(path); 341 expect(request.headers[headerName]).to.equal(headerValue); 342 343 // A header to be overwritten later 344 response.setHeader(headerName, 'to be overwritten'); 345 expect(response.getHeader(headerName)).to.equal('to be overwritten'); 346 347 // A header to be deleted 348 response.setHeader('nonexistent', 'x'); 349 response.removeHeader('nonexistent'); 350 expect(response.getHeader('nonexistent')).to.equal(undefined); 351 352 // A set-cookie header which should always be an array 353 response.setHeader('set-cookie', 'foo'); 354 355 // Don't send date 356 response.sendDate = false; 357 358 // Specifying more headers, the status code and a reason phrase with `writeHead` 359 var moreHeaders = {}; 360 moreHeaders[headerName] = headerValue; 361 response.writeHead(600, 'to be discarded', moreHeaders); 362 expect(response.getHeader(headerName)).to.equal(headerValue); 363 364 // Empty response body 365 response.end(message); 366 }); 367 368 server.listen(1239, function() { 369 var headers = {}; 370 headers[headerName] = headerValue; 371 var request = http2.request({ 372 host: 'localhost', 373 port: 1239, 374 path: path, 375 headers: headers 376 }); 377 request.end(); 378 request.on('response', function(response) { 379 expect(response.headers[headerName]).to.equal(headerValue); 380 expect(response.headers['nonexistent']).to.equal(undefined); 381 expect(response.headers['set-cookie']).to.an.instanceof(Array) 382 expect(response.headers['set-cookie']).to.deep.equal(['foo']) 383 expect(response.headers['date']).to.equal(undefined); 384 response.on('data', function(data) { 385 expect(data.toString()).to.equal(message); 386 server.close(); 387 done(); 388 }); 389 }); 390 }); 391 }); 392 }); 393 describe('request over plain TCP', function() { 394 it('should work as expected', function(done) { 395 var path = '/x'; 396 var message = 'Hello world'; 397 398 var server = http2.raw.createServer({ 399 log: util.serverLog 400 }, function(request, response) { 401 expect(request.url).to.equal(path); 402 response.end(message); 403 }); 404 405 server.listen(1237, function() { 406 var request = http2.raw.request({ 407 plain: true, 408 host: 'localhost', 409 port: 1237, 410 path: path 411 }, function(response) { 412 response.on('data', function(data) { 413 expect(data.toString()).to.equal(message); 414 server.close(); 415 done(); 416 }); 417 }); 418 request.end(); 419 }); 420 }); 421 }); 422 describe('get over plain TCP', function() { 423 it('should work as expected', function(done) { 424 var path = '/x'; 425 var message = 'Hello world'; 426 427 var server = http2.raw.createServer({ 428 log: util.serverLog 429 }, function(request, response) { 430 expect(request.url).to.equal(path); 431 response.end(message); 432 }); 433 434 server.listen(1237, function() { 435 var request = http2.raw.get('http://localhost:1237/x', function(response) { 436 response.on('data', function(data) { 437 expect(data.toString()).to.equal(message); 438 server.close(); 439 done(); 440 }); 441 }); 442 request.end(); 443 }); 444 }); 445 }); 446 describe('request to an HTTPS/1 server', function() { 447 it('should fall back to HTTPS/1 successfully', function(done) { 448 var path = '/x'; 449 var message = 'Hello world'; 450 451 var server = https.createServer(serverOptions, function(request, response) { 452 expect(request.url).to.equal(path); 453 response.end(message); 454 }); 455 456 server.listen(5678, function() { 457 http2.get('https://localhost:5678' + path, function(response) { 458 response.on('data', function(data) { 459 expect(data.toString()).to.equal(message); 460 done(); 461 }); 462 }); 463 }); 464 }); 465 }); 466 describe('2 parallel request to an HTTPS/1 server', function() { 467 it('should fall back to HTTPS/1 successfully', function(originalDone) { 468 var path = '/x'; 469 var message = 'Hello world'; 470 var done = util.callNTimes(2, function() { 471 server.close(); 472 originalDone(); 473 }); 474 475 var server = https.createServer(serverOptions, function(request, response) { 476 expect(request.url).to.equal(path); 477 response.end(message); 478 }); 479 480 server.listen(6789, function() { 481 http2.get('https://localhost:6789' + path, function(response) { 482 response.on('data', function(data) { 483 expect(data.toString()).to.equal(message); 484 done(); 485 }); 486 }); 487 http2.get('https://localhost:6789' + path, function(response) { 488 response.on('data', function(data) { 489 expect(data.toString()).to.equal(message); 490 done(); 491 }); 492 }); 493 }); 494 }); 495 }); 496 describe('HTTPS/1 request to a HTTP/2 server', function() { 497 it('should fall back to HTTPS/1 successfully', function(done) { 498 var path = '/x'; 499 var message = 'Hello world'; 500 501 var server = http2.createServer(serverOptions, function(request, response) { 502 expect(request.url).to.equal(path); 503 response.end(message); 504 }); 505 506 server.listen(1236, function() { 507 var options = url.parse('https://localhost:1236' + path); 508 options.agent = new https.Agent(agentOptions); 509 https.get(options, function(response) { 510 response.on('data', function(data) { 511 expect(data.toString()).to.equal(message); 512 done(); 513 }); 514 }); 515 }); 516 }); 517 }); 518 describe('two parallel request', function() { 519 it('should work as expected', function(done) { 520 var path = '/x'; 521 var message = 'Hello world'; 522 523 var server = http2.createServer(serverOptions, function(request, response) { 524 expect(request.url).to.equal(path); 525 response.end(message); 526 }); 527 528 server.listen(1237, function() { 529 done = util.callNTimes(2, done); 530 // 1. request 531 http2.get('https://localhost:1237' + path, function(response) { 532 response.on('data', function(data) { 533 expect(data.toString()).to.equal(message); 534 done(); 535 }); 536 }); 537 // 2. request 538 http2.get('https://localhost:1237' + path, function(response) { 539 response.on('data', function(data) { 540 expect(data.toString()).to.equal(message); 541 done(); 542 }); 543 }); 544 }); 545 }); 546 }); 547 describe('two subsequent request', function() { 548 it('should use the same HTTP/2 connection', function(done) { 549 var path = '/x'; 550 var message = 'Hello world'; 551 552 var server = http2.createServer(serverOptions, function(request, response) { 553 expect(request.url).to.equal(path); 554 response.end(message); 555 }); 556 557 server.listen(1238, function() { 558 // 1. request 559 http2.get('https://localhost:1238' + path, function(response) { 560 response.on('data', function(data) { 561 expect(data.toString()).to.equal(message); 562 563 // 2. request 564 http2.get('https://localhost:1238' + path, function(response) { 565 response.on('data', function(data) { 566 expect(data.toString()).to.equal(message); 567 done(); 568 }); 569 }); 570 }); 571 }); 572 }); 573 }); 574 }); 575 describe('https server node module specification conformance', function() { 576 it('should provide API for remote HTTP 1.1 client address', function(done) { 577 var remoteAddress = null; 578 var remotePort = null; 579 580 var server = http2.createServer(serverOptions, function(request, response) { 581 // HTTPS 1.1 client with Node 0.10 server 582 if (!request.remoteAddress) { 583 if (request.socket.socket) { 584 remoteAddress = request.socket.socket.remoteAddress; 585 remotePort = request.socket.socket.remotePort; 586 } else { 587 remoteAddress = request.socket.remoteAddress; 588 remotePort = request.socket.remotePort; 589 } 590 } else { 591 // HTTPS 1.1/2.0 client with Node 0.12 server 592 remoteAddress = request.remoteAddress; 593 remotePort = request.remotePort; 594 } 595 response.write('Pong'); 596 response.end(); 597 }); 598 599 server.listen(1259, 'localhost', function() { 600 var request = https.request({ 601 host: 'localhost', 602 port: 1259, 603 path: '/', 604 ca: serverOptions.cert 605 }); 606 request.write('Ping'); 607 request.end(); 608 request.on('response', function(response) { 609 response.on('data', function(data) { 610 var localAddress = response.socket.address(); 611 expect(remoteAddress).to.equal(localAddress.address); 612 expect(remotePort).to.equal(localAddress.port); 613 server.close(); 614 done(); 615 }); 616 }); 617 }); 618 }); 619 it('should provide API for remote HTTP 2.0 client address', function(done) { 620 var remoteAddress = null; 621 var remotePort = null; 622 var localAddress = null; 623 624 var server = http2.createServer(serverOptions, function(request, response) { 625 remoteAddress = request.remoteAddress; 626 remotePort = request.remotePort; 627 response.write('Pong'); 628 response.end(); 629 }); 630 631 server.listen(1258, 'localhost', function() { 632 var request = http2.request({ 633 host: 'localhost', 634 port: 1258, 635 path: '/' 636 }); 637 request.write('Ping'); 638 globalAgent.on('false:localhost:1258', function(endpoint) { 639 localAddress = endpoint.socket.address(); 640 }); 641 request.end(); 642 request.on('response', function(response) { 643 response.on('data', function(data) { 644 expect(remoteAddress).to.equal(localAddress.address); 645 expect(remotePort).to.equal(localAddress.port); 646 server.close(); 647 done(); 648 }); 649 }); 650 }); 651 }); 652 it('should expose net.Socket as .socket and .connection', function(done) { 653 var server = http2.createServer(serverOptions, function(request, response) { 654 expect(request.socket).to.equal(request.connection); 655 expect(request.socket).to.be.instanceof(net.Socket); 656 response.write('Pong'); 657 response.end(); 658 done(); 659 }); 660 661 server.listen(1248, 'localhost', function() { 662 var request = https.request({ 663 host: 'localhost', 664 port: 1248, 665 path: '/', 666 ca: serverOptions.cert 667 }); 668 request.write('Ping'); 669 request.end(); 670 }); 671 }); 672 }); 673 describe('request and response with trailers', function() { 674 it('should work as expected', function(done) { 675 var path = '/x'; 676 var message = 'Hello world'; 677 var requestTrailers = { 'content-md5': 'x' }; 678 var responseTrailers = { 'content-md5': 'y' }; 679 680 var server = http2.createServer(serverOptions, function(request, response) { 681 expect(request.url).to.equal(path); 682 request.on('data', util.noop); 683 request.once('end', function() { 684 expect(request.trailers).to.deep.equal(requestTrailers); 685 response.write(message); 686 response.addTrailers(responseTrailers); 687 response.end(); 688 }); 689 }); 690 691 server.listen(1241, function() { 692 var request = http2.request('https://localhost:1241' + path); 693 request.addTrailers(requestTrailers); 694 request.end(); 695 request.on('response', function(response) { 696 response.on('data', util.noop); 697 response.once('end', function() { 698 expect(response.trailers).to.deep.equal(responseTrailers); 699 done(); 700 }); 701 }); 702 }); 703 }); 704 }); 705 describe('Handle socket error', function () { 706 it('HTTPS on Connection Refused error', function (done) { 707 var path = '/x'; 708 var request = http2.request('https://127.0.0.1:6666' + path); 709 710 request.on('error', function (err) { 711 expect(err.errno).to.equal('ECONNREFUSED'); 712 done(); 713 }); 714 715 request.on('response', function (response) { 716 server._server._handle.destroy(); 717 718 response.on('data', util.noop); 719 720 response.once('end', function () { 721 done(new Error('Request should have failed')); 722 }); 723 }); 724 725 request.end(); 726 727 }); 728 it('HTTP on Connection Refused error', function (done) { 729 var path = '/x'; 730 731 var request = http2.raw.request('http://127.0.0.1:6666' + path); 732 733 request.on('error', function (err) { 734 expect(err.errno).to.equal('ECONNREFUSED'); 735 done(); 736 }); 737 738 request.on('response', function (response) { 739 server._server._handle.destroy(); 740 741 response.on('data', util.noop); 742 743 response.once('end', function () { 744 done(new Error('Request should have failed')); 745 }); 746 }); 747 748 request.end(); 749 }); 750 }); 751 describe('server push', function() { 752 it('should work as expected', function(done) { 753 var path = '/x'; 754 var message = 'Hello world'; 755 var pushedPath = '/y'; 756 var pushedMessage = 'Hello world 2'; 757 758 var server = http2.createServer(serverOptions, function(request, response) { 759 expect(request.url).to.equal(path); 760 var push1 = response.push('/y'); 761 push1.end(pushedMessage); 762 var push2 = response.push({ path: '/y', protocol: 'https:' }); 763 push2.end(pushedMessage); 764 response.end(message); 765 }); 766 767 server.listen(1235, function() { 768 var request = http2.get('https://localhost:1235' + path); 769 done = util.callNTimes(5, done); 770 771 request.on('response', function(response) { 772 response.on('data', function(data) { 773 expect(data.toString()).to.equal(message); 774 done(); 775 }); 776 response.on('end', done); 777 }); 778 779 request.on('push', function(promise) { 780 expect(promise.url).to.be.equal(pushedPath); 781 promise.on('response', function(pushStream) { 782 pushStream.on('data', function(data) { 783 expect(data.toString()).to.equal(pushedMessage); 784 done(); 785 }); 786 pushStream.on('end', done); 787 }); 788 }); 789 }); 790 }); 791 }); 792 }); 793 });