Skip to content

Commit c788f55

Browse files
committed
http: attach error handler to socket synchronously in onSocket
Between onSocket and onSocketNT, the socket had no error handler, meaning any errors emitted during that window (e.g. from a blocklist check or custom lookup) would be unhandled even if the user had set up a request error handler. Fix this by attaching socketErrorListener synchronously in onSocket, setting socket._httpMessage so the listener can forward errors to the request. tickOnSocket removes and re-adds the listener after the parser is set up to avoid duplicates. The _destroy path in onSocketNT is also guarded to prevent double-firing if socketErrorListener already emitted the error. Fixes: nodejs#48771 Refs: nodejs#61658
1 parent 04946a7 commit c788f55

File tree

2 files changed

+22
-7
lines changed

2 files changed

+22
-7
lines changed

lib/_http_client.js

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -571,7 +571,9 @@ function socketErrorListener(err) {
571571
if (req) {
572572
// For Safety. Some additional errors might fire later on
573573
// and we need to make sure we don't double-fire the error event.
574-
req.socket._hadError = true;
574+
// Use socket directly as req.socket may not be assigned yet (e.g. when
575+
// the error is emitted before onSocketNT runs).
576+
(req.socket || socket)._hadError = true;
575577
emitErrorEvent(req, err);
576578
}
577579

@@ -906,6 +908,9 @@ function tickOnSocket(req, socket) {
906908
parser.joinDuplicateHeaders = req.joinDuplicateHeaders;
907909

908910
parser.onIncoming = parserOnIncomingClient;
911+
// Remove the early error listener attached in onSocket (if any) before
912+
// re-adding it here to avoid duplicate listeners.
913+
socket.removeListener('error', socketErrorListener);
909914
socket.on('error', socketErrorListener);
910915
socket.on('data', socketOnData);
911916
socket.on('end', socketOnEnd);
@@ -945,8 +950,16 @@ function listenSocketTimeout(req) {
945950
}
946951

947952
ClientRequest.prototype.onSocket = function onSocket(socket, err) {
948-
// TODO(ronag): Between here and onSocketNT the socket
949-
// has no 'error' handler.
953+
// Attach the error listener synchronously so that any errors emitted on
954+
// the socket before onSocketNT runs (e.g. from a blocklist check or other
955+
// next-tick error) are forwarded to the request and can be caught by the
956+
// user's error handler. socketErrorListener requires socket._httpMessage
957+
// to be set so we set it here too. tickOnSocket will re-add these after
958+
// the parser is fully set up, so we remove them first to avoid duplicates.
959+
if (socket && !err) {
960+
socket._httpMessage = this;
961+
socket.on('error', socketErrorListener);
962+
}
950963
process.nextTick(onSocketNT, this, socket, err);
951964
};
952965

@@ -958,8 +971,10 @@ function onSocketNT(req, socket, err) {
958971
if (!req.aborted && !err) {
959972
err = new ConnResetException('socket hang up');
960973
}
961-
// ERR_PROXY_TUNNEL is handled by the proxying logic
962-
if (err && err.code !== 'ERR_PROXY_TUNNEL') {
974+
// ERR_PROXY_TUNNEL is handled by the proxying logic.
975+
// Skip if the error was already emitted by the early socketErrorListener.
976+
if (err && err.code !== 'ERR_PROXY_TUNNEL' &&
977+
!socket?._hadError) {
963978
emitErrorEvent(req, err);
964979
}
965980
req._closed = true;

test/parallel/test-http-request-lookup-error-catchable.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@ const net = require('net');
1313
// 2. The lookup returns an IP that triggers a synchronous error (e.g., blockList)
1414
// 3. The error is emitted before http's error handler is set up (via nextTick)
1515
//
16-
// The fix defers socket.destroy() calls in internalConnect to the next tick,
17-
// giving http.request() time to set up its error handlers.
16+
// The fix attaches socketErrorListener synchronously in onSocket so that
17+
// socket errors are forwarded to the request before onSocketNT runs.
1818

1919
const blockList = new net.BlockList();
2020
blockList.addAddress(common.localhostIPv4);

0 commit comments

Comments
 (0)