Skip to content

Commit 93e3552

Browse files
committedDec 9, 2023
[feature] Introduce the allowMultipleEventsPerMicrotask option
The `allowMultipleEventsPerMicrotask` option allows the `'message'`, `'ping'`, and `'pong'` events to be emitted more than once per microtask. Refs: #2160
1 parent 603a039 commit 93e3552

File tree

5 files changed

+76
-11
lines changed

5 files changed

+76
-11
lines changed
 

‎doc/ws.md

+8
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,10 @@ This class represents a WebSocket server. It extends the `EventEmitter`.
7272
### new WebSocketServer(options[, callback])
7373

7474
- `options` {Object}
75+
- `allowMultipleEventsPerMicrotask` {Boolean} Specifies whether or not to
76+
process more than one of the `'message'`, `'ping'`, and `'pong'` events per
77+
microtask. To improve compatibility with the WHATWG standard, the default
78+
value is `false`. Setting it to `true` improves performance slightly.
7579
- `backlog` {Number} The maximum length of the queue of pending connections.
7680
- `clientTracking` {Boolean} Specifies whether or not to track clients.
7781
- `handleProtocols` {Function} A function which can be used to handle the
@@ -292,6 +296,10 @@ This class represents a WebSocket. It extends the `EventEmitter`.
292296
- `address` {String|url.URL} The URL to which to connect.
293297
- `protocols` {String|Array} The list of subprotocols.
294298
- `options` {Object}
299+
- `allowMultipleEventsPerMicrotask` {Boolean} Specifies whether or not to
300+
process more than one of the `'message'`, `'ping'`, and `'pong'` events per
301+
microtask. To improve compatibility with the WHATWG standard, the default
302+
value is `false`. Setting it to `true` improves performance slightly.
295303
- `finishRequest` {Function} A function which can be used to customize the
296304
headers of each http request before it is sent. See description below.
297305
- `followRedirects` {Boolean} Whether or not to follow redirects. Defaults to

‎lib/receiver.js

+16-11
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ class Receiver extends Writable {
3939
* Creates a Receiver instance.
4040
*
4141
* @param {Object} [options] Options object
42+
* @param {Boolean} [options.allowMultipleEventsPerMicrotask=false] Specifies
43+
* whether or not to process more than one of the `'message'`, `'ping'`,
44+
* and `'pong'` events per microtask
4245
* @param {String} [options.binaryType=nodebuffer] The type for binary data
4346
* @param {Object} [options.extensions] An object containing the negotiated
4447
* extensions
@@ -51,6 +54,8 @@ class Receiver extends Writable {
5154
constructor(options = {}) {
5255
super();
5356

57+
this._allowMultipleEventsPerMicrotask =
58+
!!options.allowMultipleEventsPerMicrotask;
5459
this._binaryType = options.binaryType || BINARY_TYPES[0];
5560
this._extensions = options.extensions || {};
5661
this._isServer = !!options.isServer;
@@ -561,7 +566,9 @@ class Receiver extends Writable {
561566
}
562567
}
563568

564-
this._state = WAIT_MICROTASK;
569+
this._state = this._allowMultipleEventsPerMicrotask
570+
? GET_INFO
571+
: WAIT_MICROTASK;
565572
}
566573

567574
/**
@@ -578,8 +585,6 @@ class Receiver extends Writable {
578585
if (data.length === 0) {
579586
this.emit('conclude', 1005, EMPTY_BUFFER);
580587
this.end();
581-
582-
this._state = GET_INFO;
583588
} else {
584589
const code = data.readUInt16BE(0);
585590

@@ -611,16 +616,16 @@ class Receiver extends Writable {
611616

612617
this.emit('conclude', code, buf);
613618
this.end();
614-
615-
this._state = GET_INFO;
616619
}
617-
} else if (this._opcode === 0x09) {
618-
this.emit('ping', data);
619-
this._state = WAIT_MICROTASK;
620-
} else {
621-
this.emit('pong', data);
622-
this._state = WAIT_MICROTASK;
620+
621+
this._state = GET_INFO;
622+
return;
623623
}
624+
625+
this.emit(this._opcode === 0x09 ? 'ping' : 'pong', data);
626+
this._state = this._allowMultipleEventsPerMicrotask
627+
? GET_INFO
628+
: WAIT_MICROTASK;
624629
}
625630
}
626631

‎lib/websocket-server.js

+6
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,9 @@ class WebSocketServer extends EventEmitter {
2929
* Create a `WebSocketServer` instance.
3030
*
3131
* @param {Object} options Configuration options
32+
* @param {Boolean} [options.allowMultipleEventsPerMicrotask=false] Specifies
33+
* whether or not to process more than one of the `'message'`, `'ping'`,
34+
* and `'pong'` events per microtask
3235
* @param {Number} [options.backlog=511] The maximum length of the queue of
3336
* pending connections
3437
* @param {Boolean} [options.clientTracking=true] Specifies whether or not to
@@ -55,6 +58,7 @@ class WebSocketServer extends EventEmitter {
5558
super();
5659

5760
options = {
61+
allowMultipleEventsPerMicrotask: false,
5862
maxPayload: 100 * 1024 * 1024,
5963
skipUTF8Validation: false,
6064
perMessageDeflate: false,
@@ -409,6 +413,8 @@ class WebSocketServer extends EventEmitter {
409413
socket.removeListener('error', socketOnError);
410414

411415
ws.setSocket(socket, head, {
416+
allowMultipleEventsPerMicrotask:
417+
this.options.allowMultipleEventsPerMicrotask,
412418
maxPayload: this.options.maxPayload,
413419
skipUTF8Validation: this.options.skipUTF8Validation
414420
});

‎lib/websocket.js

+9
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,9 @@ class WebSocket extends EventEmitter {
192192
* @param {Duplex} socket The network socket between the server and client
193193
* @param {Buffer} head The first packet of the upgraded stream
194194
* @param {Object} options Options object
195+
* @param {Boolean} [options.allowMultipleEventsPerMicrotask=false] Specifies
196+
* whether or not to process more than one of the `'message'`, `'ping'`,
197+
* and `'pong'` events per microtask
195198
* @param {Function} [options.generateMask] The function used to generate the
196199
* masking key
197200
* @param {Number} [options.maxPayload=0] The maximum allowed message size
@@ -201,6 +204,7 @@ class WebSocket extends EventEmitter {
201204
*/
202205
setSocket(socket, head, options) {
203206
const receiver = new Receiver({
207+
allowMultipleEventsPerMicrotask: options.allowMultipleEventsPerMicrotask,
204208
binaryType: this.binaryType,
205209
extensions: this._extensions,
206210
isServer: this._isServer,
@@ -618,6 +622,9 @@ module.exports = WebSocket;
618622
* @param {(String|URL)} address The URL to which to connect
619623
* @param {Array} protocols The subprotocols
620624
* @param {Object} [options] Connection options
625+
* @param {Boolean} [options.allowMultipleEventsPerMicrotask=false] Specifies
626+
* whether or not to process more than one of the `'message'`, `'ping'`,
627+
* and `'pong'` events per microtask
621628
* @param {Function} [options.finishRequest] A function which can be used to
622629
* customize the headers of each http request before it is sent
623630
* @param {Boolean} [options.followRedirects=false] Whether or not to follow
@@ -642,6 +649,7 @@ module.exports = WebSocket;
642649
*/
643650
function initAsClient(websocket, address, protocols, options) {
644651
const opts = {
652+
allowMultipleEventsPerMicrotask: false,
645653
protocolVersion: protocolVersions[1],
646654
maxPayload: 100 * 1024 * 1024,
647655
skipUTF8Validation: false,
@@ -993,6 +1001,7 @@ function initAsClient(websocket, address, protocols, options) {
9931001
}
9941002

9951003
websocket.setSocket(socket, head, {
1004+
allowMultipleEventsPerMicrotask: opts.allowMultipleEventsPerMicrotask,
9961005
generateMask: opts.generateMask,
9971006
maxPayload: opts.maxPayload,
9981007
skipUTF8Validation: opts.skipUTF8Validation

‎test/receiver.test.js

+37
Original file line numberDiff line numberDiff line change
@@ -1150,4 +1150,41 @@ describe('Receiver', () => {
11501150

11511151
receiver.write(Buffer.from('82008200', 'hex'));
11521152
});
1153+
1154+
it('honors the `allowMultipleEventsPerMicrotask` option', (done) => {
1155+
const actual = [];
1156+
const expected = [
1157+
'1',
1158+
'2',
1159+
'3',
1160+
'4',
1161+
'microtask 1',
1162+
'microtask 2',
1163+
'microtask 3',
1164+
'microtask 4'
1165+
];
1166+
1167+
function listener(data) {
1168+
const message = data.toString();
1169+
actual.push(message);
1170+
1171+
// `queueMicrotask()` is not available in Node.js < 11.
1172+
Promise.resolve().then(() => {
1173+
actual.push(`microtask ${message}`);
1174+
1175+
if (actual.length === 8) {
1176+
assert.deepStrictEqual(actual, expected);
1177+
done();
1178+
}
1179+
});
1180+
}
1181+
1182+
const receiver = new Receiver({ allowMultipleEventsPerMicrotask: true });
1183+
1184+
receiver.on('message', listener);
1185+
receiver.on('ping', listener);
1186+
receiver.on('pong', listener);
1187+
1188+
receiver.write(Buffer.from('8101318901328a0133810134', 'hex'));
1189+
});
11531190
});

0 commit comments

Comments
 (0)
Please sign in to comment.