websocket-server.test.js (36110B)
1 /* eslint no-unused-vars: ["error", { "varsIgnorePattern": "^ws$" }] */ 2 3 'use strict'; 4 5 const assert = require('assert'); 6 const crypto = require('crypto'); 7 const https = require('https'); 8 const http = require('http'); 9 const path = require('path'); 10 const net = require('net'); 11 const fs = require('fs'); 12 const os = require('os'); 13 14 const Sender = require('../lib/sender'); 15 const WebSocket = require('..'); 16 const { NOOP } = require('../lib/constants'); 17 18 describe('WebSocketServer', () => { 19 describe('#ctor', () => { 20 it('throws an error if no option object is passed', () => { 21 assert.throws( 22 () => new WebSocket.Server(), 23 new RegExp( 24 '^TypeError: One and only one of the "port", "server", or ' + 25 '"noServer" options must be specified$' 26 ) 27 ); 28 }); 29 30 describe('options', () => { 31 it('throws an error if required options are not specified', () => { 32 assert.throws( 33 () => new WebSocket.Server({}), 34 new RegExp( 35 '^TypeError: One and only one of the "port", "server", or ' + 36 '"noServer" options must be specified$' 37 ) 38 ); 39 }); 40 41 it('throws an error if mutually exclusive options are specified', () => { 42 const server = http.createServer(); 43 const variants = [ 44 { port: 0, noServer: true, server }, 45 { port: 0, noServer: true }, 46 { port: 0, server }, 47 { noServer: true, server } 48 ]; 49 50 for (const options of variants) { 51 assert.throws( 52 () => new WebSocket.Server(options), 53 new RegExp( 54 '^TypeError: One and only one of the "port", "server", or ' + 55 '"noServer" options must be specified$' 56 ) 57 ); 58 } 59 }); 60 61 it('exposes options passed to constructor', (done) => { 62 const wss = new WebSocket.Server({ port: 0 }, () => { 63 assert.strictEqual(wss.options.port, 0); 64 wss.close(done); 65 }); 66 }); 67 68 it('accepts the `maxPayload` option', (done) => { 69 const maxPayload = 20480; 70 const wss = new WebSocket.Server( 71 { 72 perMessageDeflate: true, 73 maxPayload, 74 port: 0 75 }, 76 () => { 77 const ws = new WebSocket(`ws://localhost:${wss.address().port}`); 78 79 ws.on('open', ws.close); 80 } 81 ); 82 83 wss.on('connection', (ws) => { 84 assert.strictEqual(ws._receiver._maxPayload, maxPayload); 85 assert.strictEqual( 86 ws._receiver._extensions['permessage-deflate']._maxPayload, 87 maxPayload 88 ); 89 wss.close(done); 90 }); 91 }); 92 93 it('honors the `WebSocket` option', (done) => { 94 class CustomWebSocket extends WebSocket.WebSocket { 95 get foo() { 96 return 'foo'; 97 } 98 } 99 100 const wss = new WebSocket.Server( 101 { 102 port: 0, 103 WebSocket: CustomWebSocket 104 }, 105 () => { 106 const ws = new WebSocket(`ws://localhost:${wss.address().port}`); 107 108 ws.on('open', ws.close); 109 } 110 ); 111 112 wss.on('connection', (ws) => { 113 assert.ok(ws instanceof CustomWebSocket); 114 assert.strictEqual(ws.foo, 'foo'); 115 wss.close(done); 116 }); 117 }); 118 }); 119 120 it('emits an error if http server bind fails', (done) => { 121 const wss1 = new WebSocket.Server({ port: 0 }, () => { 122 const wss2 = new WebSocket.Server({ 123 port: wss1.address().port 124 }); 125 126 wss2.on('error', () => wss1.close(done)); 127 }); 128 }); 129 130 it('starts a server on a given port', (done) => { 131 const port = 1337; 132 const wss = new WebSocket.Server({ port }, () => { 133 const ws = new WebSocket(`ws://localhost:${port}`); 134 135 ws.on('open', ws.close); 136 }); 137 138 wss.on('connection', () => wss.close(done)); 139 }); 140 141 it('binds the server on any IPv6 address when available', (done) => { 142 const wss = new WebSocket.Server({ port: 0 }, () => { 143 assert.strictEqual(wss._server.address().address, '::'); 144 wss.close(done); 145 }); 146 }); 147 148 it('uses a precreated http server', (done) => { 149 const server = http.createServer(); 150 151 server.listen(0, () => { 152 const wss = new WebSocket.Server({ server }); 153 154 wss.on('connection', () => { 155 server.close(done); 156 }); 157 158 const ws = new WebSocket(`ws://localhost:${server.address().port}`); 159 160 ws.on('open', ws.close); 161 }); 162 }); 163 164 it('426s for non-Upgrade requests', (done) => { 165 const wss = new WebSocket.Server({ port: 0 }, () => { 166 http.get(`http://localhost:${wss.address().port}`, (res) => { 167 let body = ''; 168 169 assert.strictEqual(res.statusCode, 426); 170 res.on('data', (chunk) => { 171 body += chunk; 172 }); 173 res.on('end', () => { 174 assert.strictEqual(body, http.STATUS_CODES[426]); 175 wss.close(done); 176 }); 177 }); 178 }); 179 }); 180 181 it('uses a precreated http server listening on unix socket', function (done) { 182 // 183 // Skip this test on Windows. The URL parser: 184 // 185 // - Throws an error if the named pipe uses backward slashes. 186 // - Incorrectly parses the path if the named pipe uses forward slashes. 187 // 188 if (process.platform === 'win32') return this.skip(); 189 190 const server = http.createServer(); 191 const sockPath = path.join( 192 os.tmpdir(), 193 `ws.${crypto.randomBytes(16).toString('hex')}.sock` 194 ); 195 196 server.listen(sockPath, () => { 197 const wss = new WebSocket.Server({ server }); 198 199 wss.on('connection', (ws, req) => { 200 if (wss.clients.size === 1) { 201 assert.strictEqual(req.url, '/foo?bar=bar'); 202 } else { 203 assert.strictEqual(req.url, '/'); 204 205 for (const client of wss.clients) { 206 client.close(); 207 } 208 209 server.close(done); 210 } 211 }); 212 213 const ws = new WebSocket(`ws+unix://${sockPath}:/foo?bar=bar`); 214 ws.on('open', () => new WebSocket(`ws+unix://${sockPath}`)); 215 }); 216 }); 217 }); 218 219 describe('#address', () => { 220 it('returns the address of the server', (done) => { 221 const wss = new WebSocket.Server({ port: 0 }, () => { 222 const addr = wss.address(); 223 224 assert.deepStrictEqual(addr, wss._server.address()); 225 wss.close(done); 226 }); 227 }); 228 229 it('throws an error when operating in "noServer" mode', () => { 230 const wss = new WebSocket.Server({ noServer: true }); 231 232 assert.throws(() => { 233 wss.address(); 234 }, /^Error: The server is operating in "noServer" mode$/); 235 }); 236 237 it('returns `null` if called after close', (done) => { 238 const wss = new WebSocket.Server({ port: 0 }, () => { 239 wss.close(() => { 240 assert.strictEqual(wss.address(), null); 241 done(); 242 }); 243 }); 244 }); 245 }); 246 247 describe('#close', () => { 248 it('does not throw if called multiple times', (done) => { 249 const wss = new WebSocket.Server({ port: 0 }, () => { 250 wss.on('close', done); 251 252 wss.close(); 253 wss.close(); 254 wss.close(); 255 }); 256 }); 257 258 it("doesn't close a precreated server", (done) => { 259 const server = http.createServer(); 260 const realClose = server.close; 261 262 server.close = () => { 263 done(new Error('Must not close pre-created server')); 264 }; 265 266 const wss = new WebSocket.Server({ server }); 267 268 wss.on('connection', () => { 269 wss.close(); 270 server.close = realClose; 271 server.close(done); 272 }); 273 274 server.listen(0, () => { 275 const ws = new WebSocket(`ws://localhost:${server.address().port}`); 276 277 ws.on('open', ws.close); 278 }); 279 }); 280 281 it('invokes the callback in noServer mode', (done) => { 282 const wss = new WebSocket.Server({ noServer: true }); 283 284 wss.close(done); 285 }); 286 287 it('cleans event handlers on precreated server', (done) => { 288 const server = http.createServer(); 289 const wss = new WebSocket.Server({ server }); 290 291 server.listen(0, () => { 292 wss.close(() => { 293 assert.strictEqual(server.listenerCount('listening'), 0); 294 assert.strictEqual(server.listenerCount('upgrade'), 0); 295 assert.strictEqual(server.listenerCount('error'), 0); 296 297 server.close(done); 298 }); 299 }); 300 }); 301 302 it("emits the 'close' event after the server closes", (done) => { 303 let serverCloseEventEmitted = false; 304 305 const wss = new WebSocket.Server({ port: 0 }, () => { 306 net.createConnection({ port: wss.address().port }); 307 }); 308 309 wss._server.on('connection', (socket) => { 310 wss.close(); 311 312 // 313 // The server is closing. Ensure this does not emit a `'close'` 314 // event before the server is actually closed. 315 // 316 wss.close(); 317 318 process.nextTick(() => { 319 socket.end(); 320 }); 321 }); 322 323 wss._server.on('close', () => { 324 serverCloseEventEmitted = true; 325 }); 326 327 wss.on('close', () => { 328 assert.ok(serverCloseEventEmitted); 329 done(); 330 }); 331 }); 332 333 it("emits the 'close' event if client tracking is disabled", (done) => { 334 const wss = new WebSocket.Server({ 335 noServer: true, 336 clientTracking: false 337 }); 338 339 wss.on('close', done); 340 wss.close(); 341 }); 342 343 it('calls the callback if the server is already closed', (done) => { 344 const wss = new WebSocket.Server({ port: 0 }, () => { 345 wss.close(() => { 346 assert.strictEqual(wss._state, 2); 347 348 wss.close((err) => { 349 assert.ok(err instanceof Error); 350 assert.strictEqual(err.message, 'The server is not running'); 351 done(); 352 }); 353 }); 354 }); 355 }); 356 357 it("emits the 'close' event if the server is already closed", (done) => { 358 const wss = new WebSocket.Server({ port: 0 }, () => { 359 wss.close(() => { 360 assert.strictEqual(wss._state, 2); 361 362 wss.on('close', done); 363 wss.close(); 364 }); 365 }); 366 }); 367 }); 368 369 describe('#clients', () => { 370 it('returns a list of connected clients', (done) => { 371 const wss = new WebSocket.Server({ port: 0 }, () => { 372 assert.strictEqual(wss.clients.size, 0); 373 374 const ws = new WebSocket(`ws://localhost:${wss.address().port}`); 375 376 ws.on('open', ws.close); 377 }); 378 379 wss.on('connection', () => { 380 assert.strictEqual(wss.clients.size, 1); 381 wss.close(done); 382 }); 383 }); 384 385 it('can be disabled', (done) => { 386 const wss = new WebSocket.Server( 387 { port: 0, clientTracking: false }, 388 () => { 389 assert.strictEqual(wss.clients, undefined); 390 const ws = new WebSocket(`ws://localhost:${wss.address().port}`); 391 392 ws.on('open', () => ws.close()); 393 } 394 ); 395 396 wss.on('connection', (ws) => { 397 assert.strictEqual(wss.clients, undefined); 398 ws.on('close', () => wss.close(done)); 399 }); 400 }); 401 402 it('is updated when client terminates the connection', (done) => { 403 const wss = new WebSocket.Server({ port: 0 }, () => { 404 const ws = new WebSocket(`ws://localhost:${wss.address().port}`); 405 406 ws.on('open', () => ws.terminate()); 407 }); 408 409 wss.on('connection', (ws) => { 410 ws.on('close', () => { 411 assert.strictEqual(wss.clients.size, 0); 412 wss.close(done); 413 }); 414 }); 415 }); 416 417 it('is updated when client closes the connection', (done) => { 418 const wss = new WebSocket.Server({ port: 0 }, () => { 419 const ws = new WebSocket(`ws://localhost:${wss.address().port}`); 420 421 ws.on('open', () => ws.close()); 422 }); 423 424 wss.on('connection', (ws) => { 425 ws.on('close', () => { 426 assert.strictEqual(wss.clients.size, 0); 427 wss.close(done); 428 }); 429 }); 430 }); 431 }); 432 433 describe('#shouldHandle', () => { 434 it('returns true when the path matches', () => { 435 const wss = new WebSocket.Server({ noServer: true, path: '/foo' }); 436 437 assert.strictEqual(wss.shouldHandle({ url: '/foo' }), true); 438 assert.strictEqual(wss.shouldHandle({ url: '/foo?bar=baz' }), true); 439 }); 440 441 it("returns false when the path doesn't match", () => { 442 const wss = new WebSocket.Server({ noServer: true, path: '/foo' }); 443 444 assert.strictEqual(wss.shouldHandle({ url: '/bar' }), false); 445 }); 446 }); 447 448 describe('#handleUpgrade', () => { 449 it('can be used for a pre-existing server', (done) => { 450 const server = http.createServer(); 451 452 server.listen(0, () => { 453 const wss = new WebSocket.Server({ noServer: true }); 454 455 server.on('upgrade', (req, socket, head) => { 456 wss.handleUpgrade(req, socket, head, (ws) => { 457 ws.send('hello'); 458 ws.close(); 459 }); 460 }); 461 462 const ws = new WebSocket(`ws://localhost:${server.address().port}`); 463 464 ws.on('message', (message, isBinary) => { 465 assert.deepStrictEqual(message, Buffer.from('hello')); 466 assert.ok(!isBinary); 467 server.close(done); 468 }); 469 }); 470 }); 471 472 it("closes the connection when path doesn't match", (done) => { 473 const wss = new WebSocket.Server({ port: 0, path: '/ws' }, () => { 474 const req = http.get({ 475 port: wss.address().port, 476 headers: { 477 Connection: 'Upgrade', 478 Upgrade: 'websocket', 479 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', 480 'Sec-WebSocket-Version': 13 481 } 482 }); 483 484 req.on('response', (res) => { 485 assert.strictEqual(res.statusCode, 400); 486 wss.close(done); 487 }); 488 }); 489 }); 490 491 it('closes the connection when protocol version is Hixie-76', (done) => { 492 const wss = new WebSocket.Server({ port: 0 }, () => { 493 const req = http.get({ 494 port: wss.address().port, 495 headers: { 496 Connection: 'Upgrade', 497 Upgrade: 'WebSocket', 498 'Sec-WebSocket-Key1': '4 @1 46546xW%0l 1 5', 499 'Sec-WebSocket-Key2': '12998 5 Y3 1 .P00', 500 'Sec-WebSocket-Protocol': 'sample' 501 } 502 }); 503 504 req.on('response', (res) => { 505 assert.strictEqual(res.statusCode, 400); 506 507 const chunks = []; 508 509 res.on('data', (chunk) => { 510 chunks.push(chunk); 511 }); 512 513 res.on('end', () => { 514 assert.strictEqual( 515 Buffer.concat(chunks).toString(), 516 'Missing or invalid Sec-WebSocket-Key header' 517 ); 518 wss.close(done); 519 }); 520 }); 521 }); 522 }); 523 }); 524 525 describe('#completeUpgrade', () => { 526 it('throws an error if called twice with the same socket', (done) => { 527 const server = http.createServer(); 528 529 server.listen(0, () => { 530 const wss = new WebSocket.Server({ noServer: true }); 531 532 server.on('upgrade', (req, socket, head) => { 533 wss.handleUpgrade(req, socket, head, (ws) => { 534 ws.close(); 535 }); 536 assert.throws( 537 () => wss.handleUpgrade(req, socket, head, NOOP), 538 (err) => { 539 assert.ok(err instanceof Error); 540 assert.strictEqual( 541 err.message, 542 'server.handleUpgrade() was called more than once with the ' + 543 'same socket, possibly due to a misconfiguration' 544 ); 545 return true; 546 } 547 ); 548 }); 549 550 const ws = new WebSocket(`ws://localhost:${server.address().port}`); 551 552 ws.on('open', () => { 553 ws.on('close', () => { 554 server.close(done); 555 }); 556 }); 557 }); 558 }); 559 }); 560 561 describe('Connection establishing', () => { 562 it('fails if the HTTP method is not GET', (done) => { 563 const wss = new WebSocket.Server({ port: 0 }, () => { 564 const req = http.request({ 565 method: 'POST', 566 port: wss.address().port, 567 headers: { 568 Connection: 'Upgrade', 569 Upgrade: 'websocket' 570 } 571 }); 572 573 req.on('response', (res) => { 574 assert.strictEqual(res.statusCode, 405); 575 576 const chunks = []; 577 578 res.on('data', (chunk) => { 579 chunks.push(chunk); 580 }); 581 582 res.on('end', () => { 583 assert.strictEqual( 584 Buffer.concat(chunks).toString(), 585 'Invalid HTTP method' 586 ); 587 wss.close(done); 588 }); 589 }); 590 591 req.end(); 592 }); 593 594 wss.on('connection', () => { 595 done(new Error("Unexpected 'connection' event")); 596 }); 597 }); 598 599 it('fails if the Upgrade header field value is not "websocket"', (done) => { 600 const wss = new WebSocket.Server({ port: 0 }, () => { 601 const req = http.get({ 602 port: wss.address().port, 603 headers: { 604 Connection: 'Upgrade', 605 Upgrade: 'foo' 606 } 607 }); 608 609 req.on('response', (res) => { 610 assert.strictEqual(res.statusCode, 400); 611 612 const chunks = []; 613 614 res.on('data', (chunk) => { 615 chunks.push(chunk); 616 }); 617 618 res.on('end', () => { 619 assert.strictEqual( 620 Buffer.concat(chunks).toString(), 621 'Invalid Upgrade header' 622 ); 623 wss.close(done); 624 }); 625 }); 626 }); 627 628 wss.on('connection', () => { 629 done(new Error("Unexpected 'connection' event")); 630 }); 631 }); 632 633 it('fails if the Sec-WebSocket-Key header is invalid (1/2)', (done) => { 634 const wss = new WebSocket.Server({ port: 0 }, () => { 635 const req = http.get({ 636 port: wss.address().port, 637 headers: { 638 Connection: 'Upgrade', 639 Upgrade: 'websocket' 640 } 641 }); 642 643 req.on('response', (res) => { 644 assert.strictEqual(res.statusCode, 400); 645 646 const chunks = []; 647 648 res.on('data', (chunk) => { 649 chunks.push(chunk); 650 }); 651 652 res.on('end', () => { 653 assert.strictEqual( 654 Buffer.concat(chunks).toString(), 655 'Missing or invalid Sec-WebSocket-Key header' 656 ); 657 wss.close(done); 658 }); 659 }); 660 }); 661 662 wss.on('connection', () => { 663 done(new Error("Unexpected 'connection' event")); 664 }); 665 }); 666 667 it('fails if the Sec-WebSocket-Key header is invalid (2/2)', (done) => { 668 const wss = new WebSocket.Server({ port: 0 }, () => { 669 const req = http.get({ 670 port: wss.address().port, 671 headers: { 672 Connection: 'Upgrade', 673 Upgrade: 'websocket', 674 'Sec-WebSocket-Key': 'P5l8BJcZwRc=' 675 } 676 }); 677 678 req.on('response', (res) => { 679 assert.strictEqual(res.statusCode, 400); 680 681 const chunks = []; 682 683 res.on('data', (chunk) => { 684 chunks.push(chunk); 685 }); 686 687 res.on('end', () => { 688 assert.strictEqual( 689 Buffer.concat(chunks).toString(), 690 'Missing or invalid Sec-WebSocket-Key header' 691 ); 692 wss.close(done); 693 }); 694 }); 695 }); 696 697 wss.on('connection', () => { 698 done(new Error("Unexpected 'connection' event")); 699 }); 700 }); 701 702 it('fails if the Sec-WebSocket-Version header is invalid (1/2)', (done) => { 703 const wss = new WebSocket.Server({ port: 0 }, () => { 704 const req = http.get({ 705 port: wss.address().port, 706 headers: { 707 Connection: 'Upgrade', 708 Upgrade: 'websocket', 709 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==' 710 } 711 }); 712 713 req.on('response', (res) => { 714 assert.strictEqual(res.statusCode, 400); 715 716 const chunks = []; 717 718 res.on('data', (chunk) => { 719 chunks.push(chunk); 720 }); 721 722 res.on('end', () => { 723 assert.strictEqual( 724 Buffer.concat(chunks).toString(), 725 'Missing or invalid Sec-WebSocket-Version header' 726 ); 727 wss.close(done); 728 }); 729 }); 730 }); 731 732 wss.on('connection', () => { 733 done(new Error("Unexpected 'connection' event")); 734 }); 735 }); 736 737 it('fails if the Sec-WebSocket-Version header is invalid (2/2)', (done) => { 738 const wss = new WebSocket.Server({ port: 0 }, () => { 739 const req = http.get({ 740 port: wss.address().port, 741 headers: { 742 Connection: 'Upgrade', 743 Upgrade: 'websocket', 744 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', 745 'Sec-WebSocket-Version': 12 746 } 747 }); 748 749 req.on('response', (res) => { 750 assert.strictEqual(res.statusCode, 400); 751 752 const chunks = []; 753 754 res.on('data', (chunk) => { 755 chunks.push(chunk); 756 }); 757 758 res.on('end', () => { 759 assert.strictEqual( 760 Buffer.concat(chunks).toString(), 761 'Missing or invalid Sec-WebSocket-Version header' 762 ); 763 wss.close(done); 764 }); 765 }); 766 }); 767 768 wss.on('connection', () => { 769 done(new Error("Unexpected 'connection' event")); 770 }); 771 }); 772 773 it('fails is the Sec-WebSocket-Protocol header is invalid', (done) => { 774 const wss = new WebSocket.Server({ port: 0 }, () => { 775 const req = http.get({ 776 port: wss.address().port, 777 headers: { 778 Connection: 'Upgrade', 779 Upgrade: 'websocket', 780 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', 781 'Sec-WebSocket-Version': 13, 782 'Sec-WebSocket-Protocol': 'foo;bar' 783 } 784 }); 785 786 req.on('response', (res) => { 787 assert.strictEqual(res.statusCode, 400); 788 789 const chunks = []; 790 791 res.on('data', (chunk) => { 792 chunks.push(chunk); 793 }); 794 795 res.on('end', () => { 796 assert.strictEqual( 797 Buffer.concat(chunks).toString(), 798 'Invalid Sec-WebSocket-Protocol header' 799 ); 800 wss.close(done); 801 }); 802 }); 803 }); 804 805 wss.on('connection', () => { 806 done(new Error("Unexpected 'connection' event")); 807 }); 808 }); 809 810 it('fails if the Sec-WebSocket-Extensions header is invalid', (done) => { 811 const wss = new WebSocket.Server( 812 { 813 perMessageDeflate: true, 814 port: 0 815 }, 816 () => { 817 const req = http.get({ 818 port: wss.address().port, 819 headers: { 820 Connection: 'Upgrade', 821 Upgrade: 'websocket', 822 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', 823 'Sec-WebSocket-Version': 13, 824 'Sec-WebSocket-Extensions': 825 'permessage-deflate; server_max_window_bits=foo' 826 } 827 }); 828 829 req.on('response', (res) => { 830 assert.strictEqual(res.statusCode, 400); 831 832 const chunks = []; 833 834 res.on('data', (chunk) => { 835 chunks.push(chunk); 836 }); 837 838 res.on('end', () => { 839 assert.strictEqual( 840 Buffer.concat(chunks).toString(), 841 'Invalid or unacceptable Sec-WebSocket-Extensions header' 842 ); 843 wss.close(done); 844 }); 845 }); 846 } 847 ); 848 849 wss.on('connection', () => { 850 done(new Error("Unexpected 'connection' event")); 851 }); 852 }); 853 854 it("emits the 'wsClientError' event", (done) => { 855 const wss = new WebSocket.Server({ port: 0 }, () => { 856 const req = http.request({ 857 method: 'POST', 858 port: wss.address().port, 859 headers: { 860 Connection: 'Upgrade', 861 Upgrade: 'websocket' 862 } 863 }); 864 865 req.on('response', (res) => { 866 assert.strictEqual(res.statusCode, 400); 867 wss.close(done); 868 }); 869 870 req.end(); 871 }); 872 873 wss.on('wsClientError', (err, socket, request) => { 874 assert.ok(err instanceof Error); 875 assert.strictEqual(err.message, 'Invalid HTTP method'); 876 877 assert.ok(request instanceof http.IncomingMessage); 878 assert.strictEqual(request.method, 'POST'); 879 880 socket.end('HTTP/1.1 400 Bad Request\r\n\r\n'); 881 }); 882 883 wss.on('connection', () => { 884 done(new Error("Unexpected 'connection' event")); 885 }); 886 }); 887 888 it('fails if the WebSocket server is closing or closed', (done) => { 889 const server = http.createServer(); 890 const wss = new WebSocket.Server({ noServer: true }); 891 892 server.on('upgrade', (req, socket, head) => { 893 wss.close(); 894 wss.handleUpgrade(req, socket, head, () => { 895 done(new Error('Unexpected callback invocation')); 896 }); 897 }); 898 899 server.listen(0, () => { 900 const ws = new WebSocket(`ws://localhost:${server.address().port}`); 901 902 ws.on('unexpected-response', (req, res) => { 903 assert.strictEqual(res.statusCode, 503); 904 res.resume(); 905 server.close(done); 906 }); 907 }); 908 }); 909 910 it('handles unsupported extensions', (done) => { 911 const wss = new WebSocket.Server( 912 { 913 perMessageDeflate: true, 914 port: 0 915 }, 916 () => { 917 const req = http.get({ 918 port: wss.address().port, 919 headers: { 920 Connection: 'Upgrade', 921 Upgrade: 'websocket', 922 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', 923 'Sec-WebSocket-Version': 13, 924 'Sec-WebSocket-Extensions': 'foo; bar' 925 } 926 }); 927 928 req.on('upgrade', (res, socket, head) => { 929 if (head.length) socket.unshift(head); 930 931 socket.once('data', (chunk) => { 932 assert.strictEqual(chunk[0], 0x88); 933 socket.destroy(); 934 wss.close(done); 935 }); 936 }); 937 } 938 ); 939 940 wss.on('connection', (ws) => { 941 assert.strictEqual(ws.extensions, ''); 942 ws.close(); 943 }); 944 }); 945 946 describe('`verifyClient`', () => { 947 it('can reject client synchronously', (done) => { 948 const wss = new WebSocket.Server( 949 { 950 verifyClient: () => false, 951 port: 0 952 }, 953 () => { 954 const req = http.get({ 955 port: wss.address().port, 956 headers: { 957 Connection: 'Upgrade', 958 Upgrade: 'websocket', 959 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', 960 'Sec-WebSocket-Version': 8 961 } 962 }); 963 964 req.on('response', (res) => { 965 assert.strictEqual(res.statusCode, 401); 966 wss.close(done); 967 }); 968 } 969 ); 970 971 wss.on('connection', () => { 972 done(new Error("Unexpected 'connection' event")); 973 }); 974 }); 975 976 it('can accept client synchronously', (done) => { 977 const server = https.createServer({ 978 cert: fs.readFileSync('test/fixtures/certificate.pem'), 979 key: fs.readFileSync('test/fixtures/key.pem') 980 }); 981 982 const wss = new WebSocket.Server({ 983 verifyClient: (info) => { 984 assert.strictEqual(info.origin, 'https://example.com'); 985 assert.strictEqual(info.req.headers.foo, 'bar'); 986 assert.ok(info.secure, true); 987 return true; 988 }, 989 server 990 }); 991 992 wss.on('connection', () => { 993 server.close(done); 994 }); 995 996 server.listen(0, () => { 997 const ws = new WebSocket(`wss://localhost:${server.address().port}`, { 998 headers: { Origin: 'https://example.com', foo: 'bar' }, 999 rejectUnauthorized: false 1000 }); 1001 1002 ws.on('open', ws.close); 1003 }); 1004 }); 1005 1006 it('can accept client asynchronously', (done) => { 1007 const wss = new WebSocket.Server( 1008 { 1009 verifyClient: (o, cb) => process.nextTick(cb, true), 1010 port: 0 1011 }, 1012 () => { 1013 const ws = new WebSocket(`ws://localhost:${wss.address().port}`); 1014 1015 ws.on('open', ws.close); 1016 } 1017 ); 1018 1019 wss.on('connection', () => wss.close(done)); 1020 }); 1021 1022 it('can reject client asynchronously', (done) => { 1023 const wss = new WebSocket.Server( 1024 { 1025 verifyClient: (info, cb) => process.nextTick(cb, false), 1026 port: 0 1027 }, 1028 () => { 1029 const req = http.get({ 1030 port: wss.address().port, 1031 headers: { 1032 Connection: 'Upgrade', 1033 Upgrade: 'websocket', 1034 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', 1035 'Sec-WebSocket-Version': 8 1036 } 1037 }); 1038 1039 req.on('response', (res) => { 1040 assert.strictEqual(res.statusCode, 401); 1041 wss.close(done); 1042 }); 1043 } 1044 ); 1045 1046 wss.on('connection', () => { 1047 done(new Error("Unexpected 'connection' event")); 1048 }); 1049 }); 1050 1051 it('can reject client asynchronously w/ status code', (done) => { 1052 const wss = new WebSocket.Server( 1053 { 1054 verifyClient: (info, cb) => process.nextTick(cb, false, 404), 1055 port: 0 1056 }, 1057 () => { 1058 const req = http.get({ 1059 port: wss.address().port, 1060 headers: { 1061 Connection: 'Upgrade', 1062 Upgrade: 'websocket', 1063 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', 1064 'Sec-WebSocket-Version': 8 1065 } 1066 }); 1067 1068 req.on('response', (res) => { 1069 assert.strictEqual(res.statusCode, 404); 1070 wss.close(done); 1071 }); 1072 } 1073 ); 1074 1075 wss.on('connection', () => { 1076 done(new Error("Unexpected 'connection' event")); 1077 }); 1078 }); 1079 1080 it('can reject client asynchronously w/ custom headers', (done) => { 1081 const wss = new WebSocket.Server( 1082 { 1083 verifyClient: (info, cb) => { 1084 process.nextTick(cb, false, 503, '', { 'Retry-After': 120 }); 1085 }, 1086 port: 0 1087 }, 1088 () => { 1089 const req = http.get({ 1090 port: wss.address().port, 1091 headers: { 1092 Connection: 'Upgrade', 1093 Upgrade: 'websocket', 1094 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', 1095 'Sec-WebSocket-Version': 8 1096 } 1097 }); 1098 1099 req.on('response', (res) => { 1100 assert.strictEqual(res.statusCode, 503); 1101 assert.strictEqual(res.headers['retry-after'], '120'); 1102 wss.close(done); 1103 }); 1104 } 1105 ); 1106 1107 wss.on('connection', () => { 1108 done(new Error("Unexpected 'connection' event")); 1109 }); 1110 }); 1111 }); 1112 1113 it("doesn't emit the 'connection' event if socket is closed prematurely", (done) => { 1114 const server = http.createServer(); 1115 1116 server.listen(0, () => { 1117 const wss = new WebSocket.Server({ 1118 verifyClient: ({ req: { socket } }, cb) => { 1119 assert.strictEqual(socket.readable, true); 1120 assert.strictEqual(socket.writable, true); 1121 1122 socket.on('end', () => { 1123 assert.strictEqual(socket.readable, false); 1124 assert.strictEqual(socket.writable, true); 1125 cb(true); 1126 }); 1127 }, 1128 server 1129 }); 1130 1131 wss.on('connection', () => { 1132 done(new Error("Unexpected 'connection' event")); 1133 }); 1134 1135 const socket = net.connect( 1136 { 1137 port: server.address().port, 1138 allowHalfOpen: true 1139 }, 1140 () => { 1141 socket.end( 1142 [ 1143 'GET / HTTP/1.1', 1144 'Host: localhost', 1145 'Upgrade: websocket', 1146 'Connection: Upgrade', 1147 'Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==', 1148 'Sec-WebSocket-Version: 13', 1149 '\r\n' 1150 ].join('\r\n') 1151 ); 1152 } 1153 ); 1154 1155 socket.on('end', () => { 1156 wss.close(); 1157 server.close(done); 1158 }); 1159 }); 1160 }); 1161 1162 it('handles data passed along with the upgrade request', (done) => { 1163 const wss = new WebSocket.Server({ port: 0 }, () => { 1164 const req = http.request({ 1165 port: wss.address().port, 1166 headers: { 1167 Connection: 'Upgrade', 1168 Upgrade: 'websocket', 1169 'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==', 1170 'Sec-WebSocket-Version': 13 1171 } 1172 }); 1173 1174 const list = Sender.frame(Buffer.from('Hello'), { 1175 fin: true, 1176 rsv1: false, 1177 opcode: 0x01, 1178 mask: true, 1179 readOnly: false 1180 }); 1181 1182 req.write(Buffer.concat(list)); 1183 req.end(); 1184 }); 1185 1186 wss.on('connection', (ws) => { 1187 ws.on('message', (data, isBinary) => { 1188 assert.deepStrictEqual(data, Buffer.from('Hello')); 1189 assert.ok(!isBinary); 1190 wss.close(done); 1191 }); 1192 }); 1193 }); 1194 1195 describe('`handleProtocols`', () => { 1196 it('allows to select a subprotocol', (done) => { 1197 const handleProtocols = (protocols, request) => { 1198 assert.ok(request instanceof http.IncomingMessage); 1199 assert.strictEqual(request.url, '/'); 1200 return Array.from(protocols).pop(); 1201 }; 1202 const wss = new WebSocket.Server({ handleProtocols, port: 0 }, () => { 1203 const ws = new WebSocket(`ws://localhost:${wss.address().port}`, [ 1204 'foo', 1205 'bar' 1206 ]); 1207 1208 ws.on('open', () => { 1209 assert.strictEqual(ws.protocol, 'bar'); 1210 wss.close(done); 1211 }); 1212 }); 1213 1214 wss.on('connection', (ws) => { 1215 ws.close(); 1216 }); 1217 }); 1218 }); 1219 1220 it("emits the 'headers' event", (done) => { 1221 const wss = new WebSocket.Server({ port: 0 }, () => { 1222 const ws = new WebSocket(`ws://localhost:${wss.address().port}`); 1223 1224 ws.on('open', ws.close); 1225 }); 1226 1227 wss.on('headers', (headers, request) => { 1228 assert.deepStrictEqual(headers.slice(0, 3), [ 1229 'HTTP/1.1 101 Switching Protocols', 1230 'Upgrade: websocket', 1231 'Connection: Upgrade' 1232 ]); 1233 assert.ok(request instanceof http.IncomingMessage); 1234 assert.strictEqual(request.url, '/'); 1235 1236 wss.on('connection', () => wss.close(done)); 1237 }); 1238 }); 1239 }); 1240 1241 describe('permessage-deflate', () => { 1242 it('is disabled by default', (done) => { 1243 const wss = new WebSocket.Server({ port: 0 }, () => { 1244 const ws = new WebSocket(`ws://localhost:${wss.address().port}`); 1245 1246 ws.on('open', ws.close); 1247 }); 1248 1249 wss.on('connection', (ws, req) => { 1250 assert.strictEqual( 1251 req.headers['sec-websocket-extensions'], 1252 'permessage-deflate; client_max_window_bits' 1253 ); 1254 assert.strictEqual(ws.extensions, ''); 1255 wss.close(done); 1256 }); 1257 }); 1258 1259 it('uses configuration options', (done) => { 1260 const wss = new WebSocket.Server( 1261 { 1262 perMessageDeflate: { clientMaxWindowBits: 8 }, 1263 port: 0 1264 }, 1265 () => { 1266 const ws = new WebSocket(`ws://localhost:${wss.address().port}`); 1267 1268 ws.on('upgrade', (res) => { 1269 assert.strictEqual( 1270 res.headers['sec-websocket-extensions'], 1271 'permessage-deflate; client_max_window_bits=8' 1272 ); 1273 1274 wss.close(done); 1275 }); 1276 } 1277 ); 1278 1279 wss.on('connection', (ws) => { 1280 ws.close(); 1281 }); 1282 }); 1283 }); 1284 });