Skip to content

Commit

Permalink
http: overridable clientError
Browse files Browse the repository at this point in the history
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 7, 2016
1 parent 1ab6b21 commit 5f76b24
Show file tree
Hide file tree
Showing 6 changed files with 56 additions and 15 deletions.
5 changes: 5 additions & 0 deletions doc/api/http.markdown
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
15 changes: 8 additions & 7 deletions lib/_http_server.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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) {
Expand All @@ -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;
Expand Down Expand Up @@ -418,7 +419,7 @@ function connectionListener(socket) {

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

Expand Down
5 changes: 3 additions & 2 deletions lib/https.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
6 changes: 0 additions & 6 deletions test/parallel/test-http-client-abort.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
39 changes: 39 additions & 0 deletions test/parallel/test-http-server-client-error.js
Original file line number Diff line number Diff line change
@@ -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);
});
});
1 change: 1 addition & 0 deletions test/parallel/test-https-timeout-server.js
Original file line number Diff line number Diff line change
Expand Up @@ -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() {
Expand Down

0 comments on commit 5f76b24

Please sign in to comment.