Skip to content

Commit 6e5a5ce

Browse files
committedMay 26, 2022
[feature] Introduce the 'wsClientError' event (#2046)
Add the ability to inspect the invalid handshake requests and respond to them with a custom HTTP response. Closes #2045
1 parent 903ec62 commit 6e5a5ce

File tree

3 files changed

+80
-6
lines changed

3 files changed

+80
-6
lines changed
 

‎doc/ws.md

+16
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
- [Event: 'error'](#event-error)
1010
- [Event: 'headers'](#event-headers)
1111
- [Event: 'listening'](#event-listening)
12+
- [Event: 'wsClientError'](event-wsclienterror)
1213
- [server.address()](#serveraddress)
1314
- [server.clients](#serverclients)
1415
- [server.close([callback])](#serverclosecallback)
@@ -202,6 +203,21 @@ handshake. This allows you to inspect/modify the headers before they are sent.
202203

203204
Emitted when the underlying server has been bound.
204205

206+
### Event: 'wsClientError'
207+
208+
- `error` {Error}
209+
- `socket` {net.Socket|tls.Socket}
210+
- `request` {http.IncomingMessage}
211+
212+
Emitted when an error occurs before the WebSocket connection is established.
213+
`socket` and `request` are respectively the socket and the HTTP request from
214+
which the error originated. The listener of this event is responsible for
215+
closing the socket. When the `'wsClientError'` event is emitted there is no
216+
`http.ServerResponse` object, so any HTTP response, including the response
217+
headers and body, must be written directly to the `socket`. If there is no
218+
listener for this event, the socket is closed with a default 4xx response
219+
containing a descriptive error message.
220+
205221
### server.address()
206222

207223
Returns an object with `port`, `family`, and `address` properties specifying the

‎lib/websocket-server.js

+30-6
Original file line numberDiff line numberDiff line change
@@ -234,24 +234,26 @@ class WebSocketServer extends EventEmitter {
234234
const version = +req.headers['sec-websocket-version'];
235235

236236
if (req.method !== 'GET') {
237-
abortHandshake(socket, 405, 'Invalid HTTP method');
237+
const message = 'Invalid HTTP method';
238+
abortHandshakeOrEmitwsClientError(this, req, socket, 405, message);
238239
return;
239240
}
240241

241242
if (req.headers.upgrade.toLowerCase() !== 'websocket') {
242-
abortHandshake(socket, 400, 'Invalid Upgrade header');
243+
const message = 'Invalid Upgrade header';
244+
abortHandshakeOrEmitwsClientError(this, req, socket, 400, message);
243245
return;
244246
}
245247

246248
if (!key || !keyRegex.test(key)) {
247249
const message = 'Missing or invalid Sec-WebSocket-Key header';
248-
abortHandshake(socket, 400, message);
250+
abortHandshakeOrEmitwsClientError(this, req, socket, 400, message);
249251
return;
250252
}
251253

252254
if (version !== 8 && version !== 13) {
253255
const message = 'Missing or invalid Sec-WebSocket-Version header';
254-
abortHandshake(socket, 400, message);
256+
abortHandshakeOrEmitwsClientError(this, req, socket, 400, message);
255257
return;
256258
}
257259

@@ -268,7 +270,7 @@ class WebSocketServer extends EventEmitter {
268270
protocols = subprotocol.parse(secWebSocketProtocol);
269271
} catch (err) {
270272
const message = 'Invalid Sec-WebSocket-Protocol header';
271-
abortHandshake(socket, 400, message);
273+
abortHandshakeOrEmitwsClientError(this, req, socket, 400, message);
272274
return;
273275
}
274276
}
@@ -296,7 +298,7 @@ class WebSocketServer extends EventEmitter {
296298
} catch (err) {
297299
const message =
298300
'Invalid or unacceptable Sec-WebSocket-Extensions header';
299-
abortHandshake(socket, 400, message);
301+
abortHandshakeOrEmitwsClientError(this, req, socket, 400, message);
300302
return;
301303
}
302304
}
@@ -509,3 +511,25 @@ function abortHandshake(socket, code, message, headers) {
509511
message
510512
);
511513
}
514+
515+
/**
516+
* Emit a `'wsClientError'` event on a `WebSocketServer` if there is at least
517+
* one listener for it, otherwise call `abortHandshake()`.
518+
*
519+
* @param {WebSocketServer} server The WebSocket server
520+
* @param {http.IncomingMessage} req The request object
521+
* @param {(net.Socket|tls.Socket)} socket The socket of the upgrade request
522+
* @param {Number} code The HTTP response status code
523+
* @param {String} message The HTTP response body
524+
* @private
525+
*/
526+
function abortHandshakeOrEmitwsClientError(server, req, socket, code, message) {
527+
if (server.listenerCount('wsClientError')) {
528+
const err = new Error(message);
529+
Error.captureStackTrace(err, abortHandshakeOrEmitwsClientError);
530+
531+
server.emit('wsClientError', err, socket, req);
532+
} else {
533+
abortHandshake(socket, code, message);
534+
}
535+
}

‎test/websocket-server.test.js

+34
Original file line numberDiff line numberDiff line change
@@ -851,6 +851,40 @@ describe('WebSocketServer', () => {
851851
});
852852
});
853853

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+
854888
it('fails if the WebSocket server is closing or closed', (done) => {
855889
const server = http.createServer();
856890
const wss = new WebSocket.Server({ noServer: true });

0 commit comments

Comments
 (0)
Please sign in to comment.