Skip to content
Permalink
Browse files

http: overridable `clientError`

Make default `clientError` behavior (close socket immediately)
overridable. With this APIs it is possible to write a custom error
handler, and to send, for example, a 400 HTTP response.

    http.createServer(...).on('clientError', function(err, socket) {
      socket.end('HTTP/1.1 400 Bad Request\r\n\r\n');
      socket.destroy();
    });

Fix: #4543
PR-URL: #4557
Reviewed-By: Brian White <mscdex@mscdex.net>
  • Loading branch information...
indutny committed Jan 6, 2016
1 parent 1ab6b21 commit 5f76b24e5ee440b7c2d2bdc74a9bb94374df9f2a
@@ -421,6 +421,11 @@ not be emitted.
`function (exception, socket) { }`

If a client connection emits an `'error'` event, it will be forwarded here.
Listener of this event is responsible for closing/destroying the underlying
socket. For example, one may wish to more gracefully close the socket with an
HTTP '400 Bad Request' response instead of abruptly severing the connection.

Default behavior is to destroy the socket immediately on malformed request.

`socket` is the [`net.Socket`][] object that the error originated from.

@@ -237,10 +237,6 @@ function Server(requestListener) {

this.addListener('connection', connectionListener);

this.addListener('clientError', function(err, conn) {
conn.destroy(err);
});

this.timeout = 2 * 60 * 1000;

this._pendingResponseData = 0;
@@ -353,7 +349,12 @@ function connectionListener(socket) {

// TODO(isaacs): Move all these functions out of here
function socketOnError(e) {
self.emit('clientError', e, this);
// Ignore further errors
this.removeListener('error', socketOnError);
this.on('error', () => {});

if (!self.emit('clientError', e, this))
this.destroy(e);
}

function socketOnData(d) {
@@ -372,7 +373,7 @@ function connectionListener(socket) {
function onParserExecuteCommon(ret, d) {
if (ret instanceof Error) {
debug('parse error');
socket.destroy(ret);
socketOnError.call(socket, ret);
} else if (parser.incoming && parser.incoming.upgrade) {
// Upgrade or CONNECT
var bytesParsed = ret;
@@ -418,7 +419,7 @@ function connectionListener(socket) {

if (ret instanceof Error) {
debug('parse error');
socket.destroy(ret);
socketOnError.call(socket, ret);
return;
}

@@ -29,8 +29,9 @@ function Server(opts, requestListener) {
this.addListener('request', requestListener);
}

this.addListener('clientError', function(err, conn) {
conn.destroy();
this.addListener('tlsClientError', function(err, conn) {
if (!this.emit('clientError', err, conn))
conn.destroy(err);
});

this.timeout = 2 * 60 * 1000;
@@ -21,12 +21,6 @@ var server = http.Server(function(req, res) {
server.close();
}
});

// since there is already clientError, maybe that would be appropriate,
// since "error" is magical
req.on('clientError', function() {
console.log('Got clientError');
});
});

var responses = 0;
@@ -0,0 +1,39 @@
'use strict';
const common = require('../common');
const assert = require('assert');

const http = require('http');
const net = require('net');

const server = http.createServer(common.mustCall(function(req, res) {
res.end();
}));

server.on('clientError', common.mustCall(function(err, socket) {
socket.end('HTTP/1.1 400 Bad Request\r\n\r\n');

server.close();
}));

server.listen(common.PORT, function() {
function next() {
// Invalid request
const client = net.connect(common.PORT);
client.end('Oopsie-doopsie\r\n');

var chunks = '';
client.on('data', function(chunk) {
chunks += chunk;
});
client.once('end', function() {
assert.equal(chunks, 'HTTP/1.1 400 Bad Request\r\n\r\n');
});
}

// Normal request
http.get({ port: common.PORT, path: '/' }, function(res) {
assert.equal(res.statusCode, 200);
res.resume();
res.once('end', next);
});
});
@@ -32,6 +32,7 @@ server.on('clientError', function(err, conn) {
assert.equal(conn._secureEstablished, false);
server.close();
clientErrors++;
conn.destroy();
});

server.listen(common.PORT, function() {

0 comments on commit 5f76b24

Please sign in to comment.
You can’t perform that action at this time.