/* eslint no-unused-vars: ["error", { "varsIgnorePattern": "^ws$" }] */
'use strict';
const assert = require( 'assert');
const crypto = require( 'crypto');
const https = require( 'https');
const http = require( 'http');
const path = require( 'path');
const net = require( 'net');
const fs = require( 'fs');
const os = require( 'os');
const Sender = require( '../lib/sender');
const WebSocket = require( '..');
const { NOOP } = require( '../lib/constants');
describe( 'WebSocketServer', () => {
describe( '#ctor', () => {
it( 'throws an error if no option object is passed', () => {
assert. throws(
() => new WebSocket.Server(),
new RegExp(
'^TypeError: One and only one of the "port", "server", or ' +
'"noServer" options must be specified$'
)
);
});
describe( 'options', () => {
it( 'throws an error if required options are not specified', () => {
assert. throws(
() => new WebSocket.Server({}),
new RegExp(
'^TypeError: One and only one of the "port", "server", or ' +
'"noServer" options must be specified$'
)
);
});
it( 'throws an error if mutually exclusive options are specified', () => {
const server = http.createServer();
const variants = [
{ port: 0, noServer: true, server },
{ port: 0, noServer: true },
{ port: 0, server },
{ noServer: true, server }
];
for ( const options of variants) {
assert. throws(
() => new WebSocket.Server(options),
new RegExp(
'^TypeError: One and only one of the "port", "server", or ' +
'"noServer" options must be specified$'
)
);
}
});
it( 'exposes options passed to constructor', (done) => {
const wss = new WebSocket.Server({ port: 0 }, () => {
assert.strictEqual(wss.options.port, 0);
wss.close(done);
});
});
it( 'accepts the `maxPayload` option', (done) => {
const maxPayload = 20480;
const wss = new WebSocket.Server(
{
perMessageDeflate: true,
maxPayload,
port: 0
},
() => {
const ws = new WebSocket(`ws: //localhost:${wss.address().port}`);
ws.on( 'open', ws.close);
}
);
wss.on( 'connection', (ws) => {
assert.strictEqual(ws._receiver._maxPayload, maxPayload);
assert.strictEqual(
ws._receiver._extensions[ 'permessage-deflate']._maxPayload,
maxPayload
);
wss.close(done);
});
});
it( 'honors the `WebSocket` option', (done) => {
class CustomWebSocket extends WebSocket.WebSocket {
get foo() {
return 'foo';
}
}
const wss = new WebSocket.Server(
{
port: 0,
WebSocket: CustomWebSocket
},
() => {
const ws = new WebSocket(`ws: //localhost:${wss.address().port}`);
ws.on( 'open', ws.close);
}
);
wss.on( 'connection', (ws) => {
assert.ok(ws instanceof CustomWebSocket);
assert.strictEqual(ws.foo, 'foo');
wss.close(done);
});
});
});
it( 'emits an error if http server bind fails', (done) => {
const wss1 = new WebSocket.Server({ port: 0 }, () => {
const wss2 = new WebSocket.Server({
port: wss1.address().port
});
wss2.on( 'error', () => wss1.close(done));
});
});
it( 'starts a server on a given port', (done) => {
const port = 1337;
const wss = new WebSocket.Server({ port }, () => {
const ws = new WebSocket(`ws: //localhost:${port}`);
ws.on( 'open', ws.close);
});
wss.on( 'connection', () => wss.close(done));
});
it( 'binds the server on any IPv6 address when available', (done) => {
const wss = new WebSocket.Server({ port: 0 }, () => {
assert.strictEqual(wss._server.address().address, '::');
wss.close(done);
});
});
it( 'uses a precreated http server', (done) => {
const server = http.createServer();
server.listen( 0, () => {
const wss = new WebSocket.Server({ server });
wss.on( 'connection', () => {
server.close(done);
});
const ws = new WebSocket(`ws: //localhost:${server.address().port}`);
ws.on( 'open', ws.close);
});
});
it( '426s for non-Upgrade requests', (done) => {
const wss = new WebSocket.Server({ port: 0 }, () => {
http.get(`http: //localhost:${wss.address().port}`, (res) => {
let body = '';
assert.strictEqual(res.statusCode, 426);
res.on( 'data', (chunk) => {
body += chunk;
});
res.on( 'end', () => {
assert.strictEqual(body, http.STATUS_CODES[ 426]);
wss.close(done);
});
});
});
});
it( 'uses a precreated http server listening on unix socket', function (done) {
//
// Skip this test on Windows. The URL parser:
//
// - Throws an error if the named pipe uses backward slashes.
// - Incorrectly parses the path if the named pipe uses forward slashes.
//
if (process.platform === 'win32') return this.skip();
const server = http.createServer();
const sockPath = path.join(
os.tmpdir(),
`ws.${crypto.randomBytes( 16).toString( 'hex')}.sock`
);
server.listen(sockPath, () => {
const wss = new WebSocket.Server({ server });
wss.on( 'connection', (ws, req) => {
if (wss.clients.size === 1) {
assert.strictEqual(req.url, '/foo?bar=bar');
} else {
assert.strictEqual(req.url, '/');
for ( const client of wss.clients) {
client.close();
}
server.close(done);
}
});
const ws = new WebSocket(`ws+unix: //${sockPath}:/foo?bar=bar`);
ws.on( 'open', () => new WebSocket(`ws+unix: //${sockPath}`));
});
});
});
describe( '#address', () => {
it( 'returns the address of the server', (done) => {
const wss = new WebSocket.Server({ port: 0 }, () => {
const addr = wss.address();
assert.deepStrictEqual(addr, wss._server.address());
wss.close(done);
});
});
it( 'throws an error when operating in "noServer" mode', () => {
const wss = new WebSocket.Server({ noServer: true });
assert. throws(() => {
wss.address();
}, /^Error: The server is operating in "noServer" mode$/);
});
it( 'returns `null` if called after close', (done) => {
const wss = new WebSocket.Server({ port: 0 }, () => {
wss.close(() => {
assert.strictEqual(wss.address(), null);
done();
});
});
});
});
describe( '#close', () => {
it( 'does not throw if called multiple times', (done) => {
const wss = new WebSocket.Server({ port: 0 }, () => {
wss.on( 'close', done);
wss.close();
wss.close();
wss.close();
});
});
it( "doesn't close a precreated server", (done) => {
const server = http.createServer();
const realClose = server.close;
server.close = () => {
done( new Error( 'Must not close pre-created server'));
};
const wss = new WebSocket.Server({ server });
wss.on( 'connection', () => {
wss.close();
server.close = realClose;
server.close(done);
});
server.listen( 0, () => {
const ws = new WebSocket(`ws: //localhost:${server.address().port}`);
ws.on( 'open', ws.close);
});
});
it( 'invokes the callback in noServer mode', (done) => {
const wss = new WebSocket.Server({ noServer: true });
wss.close(done);
});
it( 'cleans event handlers on precreated server', (done) => {
const server = http.createServer();
const wss = new WebSocket.Server({ server });
server.listen( 0, () => {
wss.close(() => {
assert.strictEqual(server.listenerCount( 'listening'), 0);
assert.strictEqual(server.listenerCount( 'upgrade'), 0);
assert.strictEqual(server.listenerCount( 'error'), 0);
server.close(done);
});
});
});
it( "emits the 'close' event after the server closes", (done) => {
let serverCloseEventEmitted = false;
const wss = new WebSocket.Server({ port: 0 }, () => {
net.createConnection({ port: wss.address().port });
});
wss._server.on( 'connection', (socket) => {
wss.close();
//
// The server is closing. Ensure this does not emit a `'close'`
// event before the server is actually closed.
//
wss.close();
process.nextTick(() => {
socket.end();
});
});
wss._server.on( 'close', () => {
serverCloseEventEmitted = true;
});
wss.on( 'close', () => {
assert.ok(serverCloseEventEmitted);
done();
});
});
it( "emits the 'close' event if client tracking is disabled", (done) => {
const wss = new WebSocket.Server({
noServer: true,
clientTracking: false
});
wss.on( 'close', done);
wss.close();
});
it( 'calls the callback if the server is already closed', (done) => {
const wss = new WebSocket.Server({ port: 0 }, () => {
wss.close(() => {
assert.strictEqual(wss._state, 2);
wss.close((err) => {
assert.ok(err instanceof Error);
assert.strictEqual(err.message, 'The server is not running');
done();
});
});
});
});
it( "emits the 'close' event if the server is already closed", (done) => {
const wss = new WebSocket.Server({ port: 0 }, () => {
wss.close(() => {
assert.strictEqual(wss._state, 2);
wss.on( 'close', done);
wss.close();
});
});
});
});
describe( '#clients', () => {
it( 'returns a list of connected clients', (done) => {
const wss = new WebSocket.Server({ port: 0 }, () => {
assert.strictEqual(wss.clients.size, 0);
const ws = new WebSocket(`ws: //localhost:${wss.address().port}`);
ws.on( 'open', ws.close);
});
wss.on( 'connection', () => {
assert.strictEqual(wss.clients.size, 1);
wss.close(done);
});
});
it( 'can be disabled', (done) => {
const wss = new WebSocket.Server(
{ port: 0, clientTracking: false },
() => {
assert.strictEqual(wss.clients, undefined);
const ws = new WebSocket(`ws: //localhost:${wss.address().port}`);
ws.on( 'open', () => ws.close());
}
);
wss.on( 'connection', (ws) => {
assert.strictEqual(wss.clients, undefined);
ws.on( 'close', () => wss.close(done));
});
});
it( 'is updated when client terminates the connection', (done) => {
const wss = new WebSocket.Server({ port: 0 }, () => {
const ws = new WebSocket(`ws: //localhost:${wss.address().port}`);
ws.on( 'open', () => ws.terminate());
});
wss.on( 'connection', (ws) => {
ws.on( 'close', () => {
assert.strictEqual(wss.clients.size, 0);
wss.close(done);
});
});
});
it( 'is updated when client closes the connection', (done) => {
const wss = new WebSocket.Server({ port: 0 }, () => {
const ws = new WebSocket(`ws: //localhost:${wss.address().port}`);
ws.on( 'open', () => ws.close());
});
wss.on( 'connection', (ws) => {
ws.on( 'close', () => {
assert.strictEqual(wss.clients.size, 0);
wss.close(done);
});
});
});
});
describe( '#shouldHandle', () => {
it( 'returns true when the path matches', () => {
const wss = new WebSocket.Server({ noServer: true, path: '/foo' });
assert.strictEqual(wss.shouldHandle({ url: '/foo' }), true);
assert.strictEqual(wss.shouldHandle({ url: '/foo?bar=baz' }), true);
});
it( "returns false when the path doesn't match", () => {
const wss = new WebSocket.Server({ noServer: true, path: '/foo' });
assert.strictEqual(wss.shouldHandle({ url: '/bar' }), false);
});
});
describe( '#handleUpgrade', () => {
it( 'can be used for a pre-existing server', (done) => {
const server = http.createServer();
server.listen( 0, () => {
const wss = new WebSocket.Server({ noServer: true });
server.on( 'upgrade', (req, socket, head) => {
wss.handleUpgrade(req, socket, head, (ws) => {
ws.send( 'hello');
ws.close();
});
});
const ws = new WebSocket(`ws: //localhost:${server.address().port}`);
ws.on( 'message', (message, isBinary) => {
assert.deepStrictEqual(message, Buffer.from( 'hello'));
assert.ok(!isBinary);
server.close(done);
});
});
});
it( "closes the connection when path doesn't match", (done) => {
const wss = new WebSocket.Server({ port: 0, path: '/ws' }, () => {
const req = http.get({
port: wss.address().port,
headers: {
Connection: 'Upgrade',
Upgrade: 'websocket',
'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==',
'Sec-WebSocket-Version': 13
}
});
req.on( 'response', (res) => {
assert.strictEqual(res.statusCode, 400);
wss.close(done);
});
});
});
it( 'closes the connection when protocol version is Hixie-76', (done) => {
const wss = new WebSocket.Server({ port: 0 }, () => {
const req = http.get({
port: wss.address().port,
headers: {
Connection: 'Upgrade',
Upgrade: 'WebSocket',
'Sec-WebSocket-Key1': '4 @1 46546xW%0l 1 5',
'Sec-WebSocket-Key2': '12998 5 Y3 1 .P00',
'Sec-WebSocket-Protocol': 'sample'
}
});
req.on( 'response', (res) => {
assert.strictEqual(res.statusCode, 400);
const chunks = [];
res.on( 'data', (chunk) => {
chunks.push(chunk);
});
res.on( 'end', () => {
assert.strictEqual(
Buffer.concat(chunks).toString(),
'Missing or invalid Sec-WebSocket-Key header'
);
wss.close(done);
});
});
});
});
});
describe( '#completeUpgrade', () => {
it( 'throws an error if called twice with the same socket', (done) => {
const server = http.createServer();
server.listen( 0, () => {
const wss = new WebSocket.Server({ noServer: true });
server.on( 'upgrade', (req, socket, head) => {
wss.handleUpgrade(req, socket, head, (ws) => {
ws.close();
});
assert. throws(
() => wss.handleUpgrade(req, socket, head, NOOP),
(err) => {
assert.ok(err instanceof Error);
assert.strictEqual(
err.message,
'server.handleUpgrade() was called more than once with the ' +
'same socket, possibly due to a misconfiguration'
);
return true;
}
);
});
const ws = new WebSocket(`ws: //localhost:${server.address().port}`);
ws.on( 'open', () => {
ws.on( 'close', () => {
server.close(done);
});
});
});
});
});
describe( 'Connection establishing', () => {
it( 'fails if the HTTP method is not GET', (done) => {
const wss = new WebSocket.Server({ port: 0 }, () => {
const req = http.request({
method: 'POST',
port: wss.address().port,
headers: {
Connection: 'Upgrade',
Upgrade: 'websocket'
}
});
req.on( 'response', (res) => {
assert.strictEqual(res.statusCode, 405);
const chunks = [];
res.on( 'data', (chunk) => {
chunks.push(chunk);
});
res.on( 'end', () => {
assert.strictEqual(
Buffer.concat(chunks).toString(),
'Invalid HTTP method'
);
wss.close(done);
});
});
req.end();
});
wss.on( 'connection', () => {
done( new Error( "Unexpected 'connection' event"));
});
});
it( 'fails if the Upgrade header field value is not "websocket"', (done) => {
const wss = new WebSocket.Server({ port: 0 }, () => {
const req = http.get({
port: wss.address().port,
headers: {
Connection: 'Upgrade',
Upgrade: 'foo'
}
});
req.on( 'response', (res) => {
assert.strictEqual(res.statusCode, 400);
const chunks = [];
res.on( 'data', (chunk) => {
chunks.push(chunk);
});
res.on( 'end', () => {
assert.strictEqual(
Buffer.concat(chunks).toString(),
'Invalid Upgrade header'
);
wss.close(done);
});
});
});
wss.on( 'connection', () => {
done( new Error( "Unexpected 'connection' event"));
});
});
it( 'fails if the Sec-WebSocket-Key header is invalid (1/2)', (done) => {
const wss = new WebSocket.Server({ port: 0 }, () => {
const req = http.get({
port: wss.address().port,
headers: {
Connection: 'Upgrade',
Upgrade: 'websocket'
}
});
req.on( 'response', (res) => {
assert.strictEqual(res.statusCode, 400);
const chunks = [];
res.on( 'data', (chunk) => {
chunks.push(chunk);
});
res.on( 'end', () => {
assert.strictEqual(
Buffer.concat(chunks).toString(),
'Missing or invalid Sec-WebSocket-Key header'
);
wss.close(done);
});
});
});
wss.on( 'connection', () => {
done( new Error( "Unexpected 'connection' event"));
});
});
it( 'fails if the Sec-WebSocket-Key header is invalid (2/2)', (done) => {
const wss = new WebSocket.Server({ port: 0 }, () => {
const req = http.get({
port: wss.address().port,
headers: {
Connection: 'Upgrade',
Upgrade: 'websocket',
'Sec-WebSocket-Key': 'P5l8BJcZwRc='
}
});
req.on( 'response', (res) => {
assert.strictEqual(res.statusCode, 400);
const chunks = [];
res.on( 'data', (chunk) => {
chunks.push(chunk);
});
res.on( 'end', () => {
assert.strictEqual(
Buffer.concat(chunks).toString(),
'Missing or invalid Sec-WebSocket-Key header'
);
wss.close(done);
});
});
});
wss.on( 'connection', () => {
done( new Error( "Unexpected 'connection' event"));
});
});
it( 'fails if the Sec-WebSocket-Version header is invalid (1/2)', (done) => {
const wss = new WebSocket.Server({ port: 0 }, () => {
const req = http.get({
port: wss.address().port,
headers: {
Connection: 'Upgrade',
Upgrade: 'websocket',
'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ=='
}
});
req.on( 'response', (res) => {
assert.strictEqual(res.statusCode, 400);
const chunks = [];
res.on( 'data', (chunk) => {
chunks.push(chunk);
});
res.on( 'end', () => {
assert.strictEqual(
Buffer.concat(chunks).toString(),
'Missing or invalid Sec-WebSocket-Version header'
);
wss.close(done);
});
});
});
wss.on( 'connection', () => {
done( new Error( "Unexpected 'connection' event"));
});
});
it( 'fails if the Sec-WebSocket-Version header is invalid (2/2)', (done) => {
const wss = new WebSocket.Server({ port: 0 }, () => {
const req = http.get({
port: wss.address().port,
headers: {
Connection: 'Upgrade',
Upgrade: 'websocket',
'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==',
'Sec-WebSocket-Version': 12
}
});
req.on( 'response', (res) => {
assert.strictEqual(res.statusCode, 400);
const chunks = [];
res.on( 'data', (chunk) => {
chunks.push(chunk);
});
res.on( 'end', () => {
assert.strictEqual(
Buffer.concat(chunks).toString(),
'Missing or invalid Sec-WebSocket-Version header'
);
wss.close(done);
});
});
});
wss.on( 'connection', () => {
done( new Error( "Unexpected 'connection' event"));
});
});
it( 'fails is the Sec-WebSocket-Protocol header is invalid', (done) => {
const wss = new WebSocket.Server({ port: 0 }, () => {
const req = http.get({
port: wss.address().port,
headers: {
Connection: 'Upgrade',
Upgrade: 'websocket',
'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==',
'Sec-WebSocket-Version': 13,
'Sec-WebSocket-Protocol': 'foo;bar'
}
});
req.on( 'response', (res) => {
assert.strictEqual(res.statusCode, 400);
const chunks = [];
res.on( 'data', (chunk) => {
chunks.push(chunk);
});
res.on( 'end', () => {
assert.strictEqual(
Buffer.concat(chunks).toString(),
'Invalid Sec-WebSocket-Protocol header'
);
wss.close(done);
});
});
});
wss.on( 'connection', () => {
done( new Error( "Unexpected 'connection' event"));
});
});
it( 'fails if the Sec-WebSocket-Extensions header is invalid', (done) => {
const wss = new WebSocket.Server(
{
perMessageDeflate: true,
port: 0
},
() => {
const req = http.get({
port: wss.address().port,
headers: {
Connection: 'Upgrade',
Upgrade: 'websocket',
'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==',
'Sec-WebSocket-Version': 13,
'Sec-WebSocket-Extensions':
'permessage-deflate; server_max_window_bits=foo'
}
});
req.on( 'response', (res) => {
assert.strictEqual(res.statusCode, 400);
const chunks = [];
res.on( 'data', (chunk) => {
chunks.push(chunk);
});
res.on( 'end', () => {
assert.strictEqual(
Buffer.concat(chunks).toString(),
'Invalid or unacceptable Sec-WebSocket-Extensions header'
);
wss.close(done);
});
});
}
);
wss.on( 'connection', () => {
done( new Error( "Unexpected 'connection' event"));
});
});
it( "emits the 'wsClientError' event", (done) => {
const wss = new WebSocket.Server({ port: 0 }, () => {
const req = http.request({
method: 'POST',
port: wss.address().port,
headers: {
Connection: 'Upgrade',
Upgrade: 'websocket'
}
});
req.on( 'response', (res) => {
assert.strictEqual(res.statusCode, 400);
wss.close(done);
});
req.end();
});
wss.on( 'wsClientError', (err, socket, request) => {
assert.ok(err instanceof Error);
assert.strictEqual(err.message, 'Invalid HTTP method');
assert.ok(request instanceof http.IncomingMessage);
assert.strictEqual(request.method, 'POST');
socket.end( 'HTTP/1.1 400 Bad Request\r\n\r\n');
});
wss.on( 'connection', () => {
done( new Error( "Unexpected 'connection' event"));
});
});
it( 'fails if the WebSocket server is closing or closed', (done) => {
const server = http.createServer();
const wss = new WebSocket.Server({ noServer: true });
server.on( 'upgrade', (req, socket, head) => {
wss.close();
wss.handleUpgrade(req, socket, head, () => {
done( new Error( 'Unexpected callback invocation'));
});
});
server.listen( 0, () => {
const ws = new WebSocket(`ws: //localhost:${server.address().port}`);
ws.on( 'unexpected-response', (req, res) => {
assert.strictEqual(res.statusCode, 503);
res.resume();
server.close(done);
});
});
});
it( 'handles unsupported extensions', (done) => {
const wss = new WebSocket.Server(
{
perMessageDeflate: true,
port: 0
},
() => {
const req = http.get({
port: wss.address().port,
headers: {
Connection: 'Upgrade',
Upgrade: 'websocket',
'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==',
'Sec-WebSocket-Version': 13,
'Sec-WebSocket-Extensions': 'foo; bar'
}
});
req.on( 'upgrade', (res, socket, head) => {
if (head.length) socket.unshift(head);
socket.once( 'data', (chunk) => {
assert.strictEqual(chunk[ 0], 0x88);
socket.destroy();
wss.close(done);
});
});
}
);
wss.on( 'connection', (ws) => {
assert.strictEqual(ws.extensions, '');
ws.close();
});
});
describe( '`verifyClient`', () => {
it( 'can reject client synchronously', (done) => {
const wss = new WebSocket.Server(
{
verifyClient: () => false,
port: 0
},
() => {
const req = http.get({
port: wss.address().port,
headers: {
Connection: 'Upgrade',
Upgrade: 'websocket',
'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==',
'Sec-WebSocket-Version': 8
}
});
req.on( 'response', (res) => {
assert.strictEqual(res.statusCode, 401);
wss.close(done);
});
}
);
wss.on( 'connection', () => {
done( new Error( "Unexpected 'connection' event"));
});
});
it( 'can accept client synchronously', (done) => {
const server = https.createServer({
cert: fs.readFileSync( 'test/fixtures/certificate.pem'),
key: fs.readFileSync( 'test/fixtures/key.pem')
});
const wss = new WebSocket.Server({
verifyClient: (info) => {
assert.strictEqual(info.origin, 'https://example.com');
assert.strictEqual(info.req.headers.foo, 'bar');
assert.ok(info.secure, true);
return true;
},
server
});
wss.on( 'connection', () => {
server.close(done);
});
server.listen( 0, () => {
const ws = new WebSocket(`wss: //localhost:${server.address().port}`, {
headers: { Origin: 'https://example.com', foo: 'bar' },
rejectUnauthorized: false
});
ws.on( 'open', ws.close);
});
});
it( 'can accept client asynchronously', (done) => {
const wss = new WebSocket.Server(
{
verifyClient: (o, cb) => process.nextTick(cb, true),
port: 0
},
() => {
const ws = new WebSocket(`ws: //localhost:${wss.address().port}`);
ws.on( 'open', ws.close);
}
);
wss.on( 'connection', () => wss.close(done));
});
it( 'can reject client asynchronously', (done) => {
const wss = new WebSocket.Server(
{
verifyClient: (info, cb) => process.nextTick(cb, false),
port: 0
},
() => {
const req = http.get({
port: wss.address().port,
headers: {
Connection: 'Upgrade',
Upgrade: 'websocket',
'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==',
'Sec-WebSocket-Version': 8
}
});
req.on( 'response', (res) => {
assert.strictEqual(res.statusCode, 401);
wss.close(done);
});
}
);
wss.on( 'connection', () => {
done( new Error( "Unexpected 'connection' event"));
});
});
it( 'can reject client asynchronously w/ status code', (done) => {
const wss = new WebSocket.Server(
{
verifyClient: (info, cb) => process.nextTick(cb, false, 404),
port: 0
},
() => {
const req = http.get({
port: wss.address().port,
headers: {
Connection: 'Upgrade',
Upgrade: 'websocket',
'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==',
'Sec-WebSocket-Version': 8
}
});
req.on( 'response', (res) => {
assert.strictEqual(res.statusCode, 404);
wss.close(done);
});
}
);
wss.on( 'connection', () => {
done( new Error( "Unexpected 'connection' event"));
});
});
it( 'can reject client asynchronously w/ custom headers', (done) => {
const wss = new WebSocket.Server(
{
verifyClient: (info, cb) => {
process.nextTick(cb, false, 503, '', { 'Retry-After': 120 });
},
port: 0
},
() => {
const req = http.get({
port: wss.address().port,
headers: {
Connection: 'Upgrade',
Upgrade: 'websocket',
'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==',
'Sec-WebSocket-Version': 8
}
});
req.on( 'response', (res) => {
assert.strictEqual(res.statusCode, 503);
assert.strictEqual(res.headers[ 'retry-after'], '120');
wss.close(done);
});
}
);
wss.on( 'connection', () => {
done( new Error( "Unexpected 'connection' event"));
});
});
});
it( "doesn't emit the 'connection' event if socket is closed prematurely", (done) => {
const server = http.createServer();
server.listen( 0, () => {
const wss = new WebSocket.Server({
verifyClient: ({ req: { socket } }, cb) => {
assert.strictEqual(socket.readable, true);
assert.strictEqual(socket.writable, true);
socket.on( 'end', () => {
assert.strictEqual(socket.readable, false);
assert.strictEqual(socket.writable, true);
cb( true);
});
},
server
});
wss.on( 'connection', () => {
done( new Error( "Unexpected 'connection' event"));
});
const socket = net.connect(
{
port: server.address().port,
allowHalfOpen: true
},
() => {
socket.end(
[
'GET / HTTP/1.1',
'Host: localhost',
'Upgrade: websocket',
'Connection: Upgrade',
'Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==',
'Sec-WebSocket-Version: 13',
'\r\n'
].join( '\r\n')
);
}
);
socket.on( 'end', () => {
wss.close();
server.close(done);
});
});
});
it( 'handles data passed along with the upgrade request', (done) => {
const wss = new WebSocket.Server({ port: 0 }, () => {
const req = http.request({
port: wss.address().port,
headers: {
Connection: 'Upgrade',
Upgrade: 'websocket',
'Sec-WebSocket-Key': 'dGhlIHNhbXBsZSBub25jZQ==',
'Sec-WebSocket-Version': 13
}
});
const list = Sender.frame(Buffer.from( 'Hello'), {
fin: true,
rsv1: false,
opcode: 0x01,
mask: true,
readOnly: false
});
req.write(Buffer.concat(list));
req.end();
});
wss.on( 'connection', (ws) => {
ws.on( 'message', (data, isBinary) => {
assert.deepStrictEqual(data, Buffer.from( 'Hello'));
assert.ok(!isBinary);
wss.close(done);
});
});
});
describe( '`handleProtocols`', () => {
it( 'allows to select a subprotocol', (done) => {
const handleProtocols = (protocols, request) => {
assert.ok(request instanceof http.IncomingMessage);
assert.strictEqual(request.url, '/');
return Array.from(protocols).pop();
};
const wss = new WebSocket.Server({ handleProtocols, port: 0 }, () => {
const ws = new WebSocket(`ws: //localhost:${wss.address().port}`, [
'foo',
'bar'
]);
ws.on( 'open', () => {
assert.strictEqual(ws.protocol, 'bar');
wss.close(done);
});
});
wss.on( 'connection', (ws) => {
ws.close();
});
});
});
it( "emits the 'headers' event", (done) => {
const wss = new WebSocket.Server({ port: 0 }, () => {
const ws = new WebSocket(`ws: //localhost:${wss.address().port}`);
ws.on( 'open', ws.close);
});
wss.on( 'headers', (headers, request) => {
assert.deepStrictEqual(headers.slice( 0, 3), [
'HTTP/1.1 101 Switching Protocols',
'Upgrade: websocket',
'Connection: Upgrade'
]);
assert.ok(request instanceof http.IncomingMessage);
assert.strictEqual(request.url, '/');
wss.on( 'connection', () => wss.close(done));
});
});
});
describe( 'permessage-deflate', () => {
it( 'is disabled by default', (done) => {
const wss = new WebSocket.Server({ port: 0 }, () => {
const ws = new WebSocket(`ws: //localhost:${wss.address().port}`);
ws.on( 'open', ws.close);
});
wss.on( 'connection', (ws, req) => {
assert.strictEqual(
req.headers[ 'sec-websocket-extensions'],
'permessage-deflate; client_max_window_bits'
);
assert.strictEqual(ws.extensions, '');
wss.close(done);
});
});
it( 'uses configuration options', (done) => {
const wss = new WebSocket.Server(
{
perMessageDeflate: { clientMaxWindowBits: 8 },
port: 0
},
() => {
const ws = new WebSocket(`ws: //localhost:${wss.address().port}`);
ws.on( 'upgrade', (res) => {
assert.strictEqual(
res.headers[ 'sec-websocket-extensions'],
'permessage-deflate; client_max_window_bits=8'
);
wss.close(done);
});
}
);
wss.on( 'connection', (ws) => {
ws.close();
});
});
});
});
¤ Dauer der Verarbeitung: 0.7 Sekunden
(vorverarbeitet am 2026-11-08)
¤
*© Formatika GbR, Deutschland
|
|