Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: websockets/ws
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: 8.15.1
Choose a base ref
...
head repository: websockets/ws
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: 8.16.0
Choose a head ref
  • 6 commits
  • 8 files changed
  • 2 contributors

Commits on Dec 20, 2023

  1. Copy the full SHA
    d37756a View commit details
  2. [doc] Fix nits

    lpinca committed Dec 20, 2023
    Copy the full SHA
    3e230c1 View commit details
  3. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    527ec97 View commit details

Commits on Dec 26, 2023

  1. [feature] Introduce the autoPong option

    Add the ability to disable the automatic sending of pong responses to
    pings.
    
    Fixes #2186
    lpinca committed Dec 26, 2023

    Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    01ba54e View commit details
  2. Verified

    This commit was created on GitHub.com and signed with GitHub’s verified signature. The key has expired.
    Copy the full SHA
    391ddf3 View commit details
  3. [dist] 8.16.0

    lpinca committed Dec 26, 2023
    Copy the full SHA
    d343a0c View commit details
Showing with 147 additions and 31 deletions.
  1. +31 −18 README.md
  2. +4 −0 doc/ws.md
  3. +4 −1 lib/websocket-server.js
  4. +7 −1 lib/websocket.js
  5. +1 −1 package.json
  6. +14 −4 test/create-websocket-stream.test.js
  7. +24 −0 test/websocket-server.test.js
  8. +62 −6 test/websocket.test.js
49 changes: 31 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
@@ -23,6 +23,7 @@ can use one of the many wrappers available on npm, like
- [Protocol support](#protocol-support)
- [Installing](#installing)
- [Opt-in for performance](#opt-in-for-performance)
- [Legacy opt-in for performance](#legacy-opt-in-for-performance)
- [API docs](#api-docs)
- [WebSocket compression](#websocket-compression)
- [Usage examples](#usage-examples)
@@ -57,27 +58,37 @@ npm install ws

### Opt-in for performance

There are 2 optional modules that can be installed along side with the ws
module. These modules are binary addons that improve the performance of certain
operations. Prebuilt binaries are available for the most popular platforms so
you don't necessarily need to have a C++ compiler installed on your machine.
[bufferutil][] is an optional module that can be installed alongside the ws
module:

- `npm install --save-optional bufferutil`: Allows to efficiently perform
operations such as masking and unmasking the data payload of the WebSocket
frames.
- `npm install --save-optional utf-8-validate`: Allows to efficiently check if a
message contains valid UTF-8.
```
npm install --save-optional bufferutil
```

This is a binary addon that improves the performance of certain operations such
as masking and unmasking the data payload of the WebSocket frames. Prebuilt
binaries are available for the most popular platforms, so you don't necessarily
need to have a C++ compiler installed on your machine.

To force ws to not use bufferutil, use the
[`WS_NO_BUFFER_UTIL`](./doc/ws.md#ws_no_buffer_util) environment variable. This
can be useful to enhance security in systems where a user can put a package in
the package search path of an application of another user, due to how the
Node.js resolver algorithm works.

#### Legacy opt-in for performance

If you are running on an old version of Node.js (prior to v18.14.0), ws also
supports the [utf-8-validate][] module:

```
npm install --save-optional utf-8-validate
```

To not even try to require and use these modules, use the
[`WS_NO_BUFFER_UTIL`](./doc/ws.md#ws_no_buffer_util) and
[`WS_NO_UTF_8_VALIDATE`](./doc/ws.md#ws_no_utf_8_validate) environment
variables. These might be useful to enhance security in systems where a user can
put a package in the package search path of an application of another user, due
to how the Node.js resolver algorithm works.
This contains a binary polyfill for [`buffer.isUtf8()`][].

The `utf-8-validate` module is not needed and is not required, even if it is
already installed, regardless of the value of the `WS_NO_UTF_8_VALIDATE`
environment variable, if [`buffer.isUtf8()`][] is available.
To force ws to not use utf-8-validate, use the
[`WS_NO_UTF_8_VALIDATE`](./doc/ws.md#ws_no_utf_8_validate) environment variable.

## API docs

@@ -523,6 +534,7 @@ We're using the GitHub [releases][changelog] for changelog entries.
[MIT](LICENSE)

[`buffer.isutf8()`]: https://nodejs.org/api/buffer.html#bufferisutf8input
[bufferutil]: https://github.com/websockets/bufferutil
[changelog]: https://github.com/websockets/ws/releases
[client-report]: http://websockets.github.io/ws/autobahn/clients/
[https-proxy-agent]: https://github.com/TooTallNate/node-https-proxy-agent
@@ -533,4 +545,5 @@ We're using the GitHub [releases][changelog] for changelog entries.
[server-report]: http://websockets.github.io/ws/autobahn/servers/
[session-parse-example]: ./examples/express-session-parse
[socks-proxy-agent]: https://github.com/TooTallNate/node-socks-proxy-agent
[utf-8-validate]: https://github.com/websockets/utf-8-validate
[ws-server-options]: ./doc/ws.md#new-websocketserveroptions-callback
4 changes: 4 additions & 0 deletions doc/ws.md
Original file line number Diff line number Diff line change
@@ -72,6 +72,8 @@ This class represents a WebSocket server. It extends the `EventEmitter`.
### new WebSocketServer(options[, callback])

- `options` {Object}
- `autoPong` {Boolean} Specifies whether or not to automatically send a pong
in response to a ping. Defaults to `true`.
- `allowSynchronousEvents` {Boolean} Specifies whether any of the `'message'`,
`'ping'`, and `'pong'` events can be emitted multiple times in the same
tick. To improve compatibility with the WHATWG standard, the default value
@@ -296,6 +298,8 @@ This class represents a WebSocket. It extends the `EventEmitter`.
- `address` {String|url.URL} The URL to which to connect.
- `protocols` {String|Array} The list of subprotocols.
- `options` {Object}
- `autoPong` {Boolean} Specifies whether or not to automatically send a pong
in response to a ping. Defaults to `true`.
- `allowSynchronousEvents` {Boolean} Specifies whether any of the `'message'`,
`'ping'`, and `'pong'` events can be emitted multiple times in the same
tick. To improve compatibility with the WHATWG standard, the default value
5 changes: 4 additions & 1 deletion lib/websocket-server.js
Original file line number Diff line number Diff line change
@@ -32,6 +32,8 @@ class WebSocketServer extends EventEmitter {
* @param {Boolean} [options.allowSynchronousEvents=false] Specifies whether
* any of the `'message'`, `'ping'`, and `'pong'` events can be emitted
* multiple times in the same tick
* @param {Boolean} [options.autoPong=true] Specifies whether or not to
* automatically send a pong in response to a ping
* @param {Number} [options.backlog=511] The maximum length of the queue of
* pending connections
* @param {Boolean} [options.clientTracking=true] Specifies whether or not to
@@ -59,6 +61,7 @@ class WebSocketServer extends EventEmitter {

options = {
allowSynchronousEvents: false,
autoPong: true,
maxPayload: 100 * 1024 * 1024,
skipUTF8Validation: false,
perMessageDeflate: false,
@@ -379,7 +382,7 @@ class WebSocketServer extends EventEmitter {
`Sec-WebSocket-Accept: ${digest}`
];

const ws = new this.options.WebSocket(null);
const ws = new this.options.WebSocket(null, undefined, this.options);

if (protocols.size) {
//
8 changes: 7 additions & 1 deletion lib/websocket.js
Original file line number Diff line number Diff line change
@@ -84,6 +84,7 @@ class WebSocket extends EventEmitter {

initAsClient(this, address, protocols, options);
} else {
this._autoPong = options.autoPong;
this._isServer = true;
}
}
@@ -625,6 +626,8 @@ module.exports = WebSocket;
* @param {Boolean} [options.allowSynchronousEvents=false] Specifies whether any
* of the `'message'`, `'ping'`, and `'pong'` events can be emitted multiple
* times in the same tick
* @param {Boolean} [options.autoPong=true] Specifies whether or not to
* automatically send a pong in response to a ping
* @param {Function} [options.finishRequest] A function which can be used to
* customize the headers of each http request before it is sent
* @param {Boolean} [options.followRedirects=false] Whether or not to follow
@@ -650,6 +653,7 @@ module.exports = WebSocket;
function initAsClient(websocket, address, protocols, options) {
const opts = {
allowSynchronousEvents: false,
autoPong: true,
protocolVersion: protocolVersions[1],
maxPayload: 100 * 1024 * 1024,
skipUTF8Validation: false,
@@ -668,6 +672,8 @@ function initAsClient(websocket, address, protocols, options) {
port: undefined
};

websocket._autoPong = opts.autoPong;

if (!protocolVersions.includes(opts.protocolVersion)) {
throw new RangeError(
`Unsupported protocol version: ${opts.protocolVersion} ` +
@@ -1212,7 +1218,7 @@ function receiverOnMessage(data, isBinary) {
function receiverOnPing(data) {
const websocket = this[kWebSocket];

websocket.pong(data, !websocket._isServer, NOOP);
if (websocket._autoPong) websocket.pong(data, !this._isServer, NOOP);
websocket.emit('ping', data);
}

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "ws",
"version": "8.15.1",
"version": "8.16.0",
"description": "Simple to use, blazing fast and thoroughly tested websocket client and server for Node.js",
"keywords": [
"HyBi",
18 changes: 14 additions & 4 deletions test/create-websocket-stream.test.js
Original file line number Diff line number Diff line change
@@ -3,14 +3,18 @@
const assert = require('assert');
const EventEmitter = require('events');
const { createServer } = require('http');
const { Duplex } = require('stream');
const { Duplex, getDefaultHighWaterMark } = require('stream');
const { randomBytes } = require('crypto');

const createWebSocketStream = require('../lib/stream');
const Sender = require('../lib/sender');
const WebSocket = require('..');
const { EMPTY_BUFFER } = require('../lib/constants');

const highWaterMark = getDefaultHighWaterMark
? getDefaultHighWaterMark(false)
: 16 * 1024;

describe('createWebSocketStream', () => {
it('is exposed as a property of the `WebSocket` class', () => {
assert.strictEqual(WebSocket.createWebSocketStream, createWebSocketStream);
@@ -445,12 +449,15 @@ describe('createWebSocketStream', () => {
};

const list = [
...Sender.frame(randomBytes(16 * 1024), { rsv1: false, ...opts }),
...Sender.frame(randomBytes(highWaterMark), {
rsv1: false,
...opts
}),
...Sender.frame(Buffer.alloc(1), { rsv1: true, ...opts })
];

// This hack is used because there is no guarantee that more than
// 16 KiB will be sent as a single TCP packet.
// `highWaterMark` bytes will be sent as a single TCP packet.
ws._socket.push(Buffer.concat(list));
});

@@ -494,7 +501,10 @@ describe('createWebSocketStream', () => {
};

const list = [
...Sender.frame(randomBytes(16 * 1024), { rsv1: false, ...opts }),
...Sender.frame(randomBytes(highWaterMark), {
rsv1: false,
...opts
}),
...Sender.frame(Buffer.alloc(1), { rsv1: true, ...opts })
];

24 changes: 24 additions & 0 deletions test/websocket-server.test.js
Original file line number Diff line number Diff line change
@@ -116,6 +116,30 @@ describe('WebSocketServer', () => {
wss.close(done);
});
});

it('honors the `autoPong` option', (done) => {
const wss = new WebSocket.Server({ autoPong: false, port: 0 }, () => {
const ws = new WebSocket(`ws://localhost:${wss.address().port}`);

ws.on('open', () => {
ws.ping();
});

ws.on('pong', () => {
done(new Error("Unexpected 'pong' event"));
});
});

wss.on('connection', (ws) => {
ws.on('ping', () => {
ws.close();
});

ws.on('close', () => {
wss.close(done);
});
});
});
});

it('emits an error if http server bind fails', (done) => {
68 changes: 62 additions & 6 deletions test/websocket.test.js
Original file line number Diff line number Diff line change
@@ -11,6 +11,7 @@ const net = require('net');
const tls = require('tls');
const os = require('os');
const fs = require('fs');
const { getDefaultHighWaterMark } = require('stream');
const { URL } = require('url');

const Sender = require('../lib/sender');
@@ -23,6 +24,10 @@ const {
} = require('../lib/event-target');
const { EMPTY_BUFFER, GUID, kListener, NOOP } = require('../lib/constants');

const highWaterMark = getDefaultHighWaterMark
? getDefaultHighWaterMark(false)
: 16 * 1024;

class CustomAgent extends http.Agent {
addRequest() {}
}
@@ -197,6 +202,30 @@ describe('WebSocket', () => {
});
});
});

it('honors the `autoPong` option', (done) => {
const wss = new WebSocket.Server({ port: 0 }, () => {
const ws = new WebSocket(`ws://localhost:${wss.address().port}`, {
autoPong: false
});

ws.on('ping', () => {
ws.close();
});

ws.on('close', () => {
wss.close(done);
});
});

wss.on('connection', (ws) => {
ws.on('pong', () => {
done(new Error("Unexpected 'pong' event"));
});

ws.ping();
});
});
});
});

@@ -2325,6 +2354,29 @@ describe('WebSocket', () => {
ws.close();
});
});

it('is called automatically when a ping is received', (done) => {
const buf = Buffer.from('hi');
const wss = new WebSocket.Server({ port: 0 }, () => {
const ws = new WebSocket(`ws://localhost:${wss.address().port}`);

ws.on('open', () => {
ws.ping(buf);
});

ws.on('pong', (data) => {
assert.deepStrictEqual(data, buf);
wss.close(done);
});
});

wss.on('connection', (ws) => {
ws.on('ping', (data) => {
assert.deepStrictEqual(data, buf);
ws.close();
});
});
});
});

describe('#resume', () => {
@@ -4045,7 +4097,7 @@ describe('WebSocket', () => {
ws.terminate();
};

const payload1 = Buffer.alloc(15 * 1024);
const payload1 = Buffer.alloc(highWaterMark - 1024);
const payload2 = Buffer.alloc(1);

const opts = {
@@ -4060,13 +4112,17 @@ describe('WebSocket', () => {
...Sender.frame(payload2, { rsv1: true, ...opts })
];

for (let i = 0; i < 399; i++) {
for (let i = 0; i < 340; i++) {
list.push(list[list.length - 2], list[list.length - 1]);
}

const data = Buffer.concat(list);

assert.ok(data.length > highWaterMark);

// This hack is used because there is no guarantee that more than
// 16 KiB will be sent as a single TCP packet.
push.call(ws._socket, Buffer.concat(list));
// `highWaterMark` bytes will be sent as a single TCP packet.
push.call(ws._socket, data);

wss.clients
.values()
@@ -4081,8 +4137,8 @@ describe('WebSocket', () => {

ws.on('close', (code) => {
assert.strictEqual(code, 1006);
assert.strictEqual(messageLengths.length, 402);
assert.strictEqual(messageLengths[0], 15360);
assert.strictEqual(messageLengths.length, 343);
assert.strictEqual(messageLengths[0], highWaterMark - 1024);
assert.strictEqual(messageLengths[messageLengths.length - 1], 1);
wss.close(done);
});