Skip to content

Commit 05f12a6

Browse files
authoredMar 24, 2022
fix: Re-add support for clientError listeners (#1897)
1 parent ddc1042 commit 05f12a6

File tree

2 files changed

+161
-0
lines changed

2 files changed

+161
-0
lines changed
 

‎lib/server.js

+29
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,8 @@ function Server(options) {
205205
});
206206

207207
// Now the things we can't blindly proxy
208+
proxyEventWhenListenerAdded('clientError', this, this.server);
209+
208210
this.server.on('checkContinue', function onCheckContinue(req, res) {
209211
if (self.listeners('checkContinue').length > 0) {
210212
self.emit('checkContinue', req, res);
@@ -274,6 +276,33 @@ util.inherits(Server, EventEmitter);
274276

275277
module.exports = Server;
276278

279+
/**
280+
* Only add a listener on the wrappedEmitter when a listener for the event is
281+
* added to the wrapperEmitter. This is useful when just adding a listener to
282+
* the wrappedEmittter overrides/disables a default behavior.
283+
*
284+
* @param {string} eventName - The name of the event to proxy
285+
* @param {EventEmitter} wrapperEmitter - The emitter that proxies events from the wrappedEmitter
286+
* @param {EventEmitter} wrappedEmitter - The proxied emitter
287+
* @returns {undefined} NA
288+
*/
289+
function proxyEventWhenListenerAdded(
290+
eventName,
291+
wrapperEmitter,
292+
wrappedEmitter
293+
) {
294+
var isEventProxied = false;
295+
wrapperEmitter.on('newListener', function onNewListener(handledEventName) {
296+
if (handledEventName === eventName && !isEventProxied) {
297+
isEventProxied = true;
298+
wrappedEmitter.on(
299+
eventName,
300+
wrapperEmitter.emit.bind(wrapperEmitter, eventName)
301+
);
302+
}
303+
});
304+
}
305+
277306
///--- Server lifecycle methods
278307

279308
// eslint-disable-next-line jsdoc/check-param-names

‎test/server.test.js

+132
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ var CLIENT;
3333
var FAST_CLIENT;
3434
var SERVER;
3535

36+
var NODE_MAJOR_VERSION = process.versions.node.split('.')[0];
37+
3638
if (SKIP_IP_V6) {
3739
console.warn('IPv6 tests are skipped: No IPv6 network is available');
3840
}
@@ -2820,3 +2822,133 @@ test('async handler should discard value', function(t) {
28202822
t.end();
28212823
});
28222824
});
2825+
2826+
test('Server returns 400 on invalid method', function(t) {
2827+
SERVER.get('/snickers/bar', function echoId(req, res, next) {
2828+
res.send();
2829+
next();
2830+
});
2831+
2832+
var opts = {
2833+
hostname: '127.0.0.1',
2834+
port: PORT,
2835+
path: '/snickers/bar',
2836+
method: 'CANDYBARS',
2837+
agent: false
2838+
};
2839+
http.request(opts, function(res) {
2840+
t.equal(res.statusCode, 400);
2841+
t.equal(res.statusMessage, 'Bad Request');
2842+
res.on('data', function() {
2843+
t.fail('Data was sent on 400 error');
2844+
});
2845+
res.on('end', function() {
2846+
t.end();
2847+
});
2848+
}).end();
2849+
});
2850+
2851+
test('Server returns 4xx when header size is too large', function(t) {
2852+
SERVER.get('/jellybeans', function echoId(req, res, next) {
2853+
res.send();
2854+
next();
2855+
});
2856+
2857+
var opts = {
2858+
hostname: '127.0.0.1',
2859+
port: PORT,
2860+
path: '/jellybeans',
2861+
method: 'GET',
2862+
agent: false,
2863+
headers: {
2864+
'jellybean-colors': 'purple,green,red,black,pink,'.repeat(1000)
2865+
}
2866+
};
2867+
http.request(opts, function(res) {
2868+
if (NODE_MAJOR_VERSION > '10') {
2869+
t.equal(res.statusCode, 431);
2870+
t.equal(res.statusMessage, 'Request Header Fields Too Large');
2871+
} else {
2872+
t.equal(res.statusCode, 400);
2873+
t.equal(res.statusMessage, 'Bad Request');
2874+
}
2875+
res.on('data', function() {
2876+
t.fail('Data was sent on 431 error');
2877+
});
2878+
res.on('end', function() {
2879+
t.end();
2880+
});
2881+
}).end();
2882+
});
2883+
2884+
test('Server supports adding custom clientError listener', function(t) {
2885+
SERVER.get('/popcorn', function echoId(req, res, next) {
2886+
res.send();
2887+
next();
2888+
});
2889+
2890+
SERVER.on('clientError', function(err, socket) {
2891+
if (err.code !== 'HPE_HEADER_OVERFLOW') {
2892+
t.fail('Expected HPE_HEADER_OVERFLOW but err.code was ' + err.code);
2893+
}
2894+
socket.write("HTTP/1.1 418 I'm a teapot\r\nConnection: close\r\n\r\n");
2895+
socket.destroy(err);
2896+
});
2897+
2898+
var opts = {
2899+
hostname: '127.0.0.1',
2900+
port: PORT,
2901+
path: '/popcorn',
2902+
method: 'GET',
2903+
agent: false,
2904+
headers: {
2905+
'jellybean-colors': 'purple,green,red,black,pink,'.repeat(1000)
2906+
}
2907+
};
2908+
http.request(opts, function(res) {
2909+
t.equal(res.statusCode, 418);
2910+
t.equal(res.statusMessage, "I'm a teapot");
2911+
res.on('data', function() {});
2912+
res.on('end', function() {
2913+
t.end();
2914+
});
2915+
}).end();
2916+
});
2917+
2918+
test('Server correctly handles multiple clientError listeners', function(t) {
2919+
SERVER.get('/popcorn', function echoId(req, res, next) {
2920+
res.send();
2921+
next();
2922+
});
2923+
2924+
let numListenerCalls = 0;
2925+
SERVER.on('clientError', function(err, socket) {
2926+
socket.write("HTTP/1.1 418 I'm a teapot\r\nConnection: close\r\n\r\n");
2927+
numListenerCalls += 1;
2928+
});
2929+
SERVER.on('clientError', function(err, socket) {
2930+
if (numListenerCalls !== 1) {
2931+
t.fail('listener was called ' + numListenerCalls + ' times');
2932+
}
2933+
socket.destroy(err);
2934+
});
2935+
2936+
var opts = {
2937+
hostname: '127.0.0.1',
2938+
port: PORT,
2939+
path: '/popcorn',
2940+
method: 'GET',
2941+
agent: false,
2942+
headers: {
2943+
'jellybean-colors': 'purple,green,red,black,pink,'.repeat(1000)
2944+
}
2945+
};
2946+
http.request(opts, function(res) {
2947+
t.equal(res.statusCode, 418);
2948+
t.equal(res.statusMessage, "I'm a teapot");
2949+
res.on('data', function() {});
2950+
res.on('end', function() {
2951+
t.end();
2952+
});
2953+
}).end();
2954+
});

0 commit comments

Comments
 (0)
Please sign in to comment.