GeezerVison is showing github.com — http: attach error handler to socket synchronously in onSocket · nodejs/node@435f3dd
File treeExpand file treeCollapse file tree Open diff view settings Expand file treeCollapse file tree Open diff view settings Collapse file Copy file name to clipboardExpand all lines: lib/_http_client.js +14-6Lines changed: 14 additions & 6 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -571,7 +571,7 @@ function socketErrorListener(err) { | 571 | 571 | if (req) { | 572 | 572 | // For Safety. Some additional errors might fire later on | 573 | 573 | // and we need to make sure we don't double-fire the error event. | 574 | | - req.socket._hadError = true; | | 574 | + socket._hadError = true; | 575 | 575 | emitErrorEvent(req, err); | 576 | 576 | } | 577 | 577 |
| @@ -910,7 +910,6 @@ function tickOnSocket(req, socket) { | 910 | 910 | parser.joinDuplicateHeaders = req.joinDuplicateHeaders; | 911 | 911 |
| 912 | 912 | parser.onIncoming = parserOnIncomingClient; | 913 | | - socket.on('error', socketErrorListener); | 914 | 913 | socket.on('data', socketOnData); | 915 | 914 | socket.on('end', socketOnEnd); | 916 | 915 | socket.on('close', socketCloseListener); | @@ -949,8 +948,15 @@ function listenSocketTimeout(req) { | 949 | 948 | } | 950 | 949 |
| 951 | 950 | ClientRequest.prototype.onSocket = function onSocket(socket, err) { | 952 | | - // TODO(ronag): Between here and onSocketNT the socket | 953 | | - // has no 'error' handler. | | 951 | + // Attach the error listener synchronously so that any errors emitted on | | 952 | + // the socket before onSocketNT runs (e.g. from a blocklist check or other | | 953 | + // next-tick error) are forwarded to the request and can be caught by the | | 954 | + // user's error handler. socketErrorListener requires socket._httpMessage | | 955 | + // to be set so we set it here too. | | 956 | + if (socket && !err) { | | 957 | + socket._httpMessage = this; | | 958 | + socket.on('error', socketErrorListener); | | 959 | + } | 954 | 960 | process.nextTick(onSocketNT, this, socket, err); | 955 | 961 | }; | 956 | 962 |
| @@ -962,8 +968,10 @@ function onSocketNT(req, socket, err) { | 962 | 968 | if (!req.aborted && !err) { | 963 | 969 | err = new ConnResetException('socket hang up'); | 964 | 970 | } | 965 | | - // ERR_PROXY_TUNNEL is handled by the proxying logic | 966 | | - if (err && err.code !== 'ERR_PROXY_TUNNEL') { | | 971 | + // ERR_PROXY_TUNNEL is handled by the proxying logic. | | 972 | + // Skip if the error was already emitted by the early socketErrorListener. | | 973 | + if (err && err.code !== 'ERR_PROXY_TUNNEL' && | | 974 | + !socket?._hadError) { | 967 | 975 | emitErrorEvent(req, err); | 968 | 976 | } | 969 | 977 | req._closed = true; | |
Collapse file Copy file name to clipboardExpand all lines: lib/net.js +5-13Lines changed: 5 additions & 13 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -1125,7 +1125,7 @@ function internalConnect( | 1125 | 1125 | err = checkBindError(err, localPort, self._handle); | 1126 | 1126 | if (err) { | 1127 | 1127 | const ex = new ExceptionWithHostPort(err, 'bind', localAddress, localPort); | 1128 | | - process.nextTick(emitErrorAndDestroy, self, ex); | | 1128 | + self.destroy(ex); | 1129 | 1129 | return; | 1130 | 1130 | } | 1131 | 1131 | } | @@ -1135,7 +1135,7 @@ function internalConnect( | 1135 | 1135 |
| 1136 | 1136 | if (addressType === 6 || addressType === 4) { | 1137 | 1137 | if (self.blockList?.check(address, `ipv${addressType}`)) { | 1138 | | - process.nextTick(emitErrorAndDestroy, self, new ERR_IP_BLOCKED(address)); | | 1138 | + self.destroy(new ERR_IP_BLOCKED(address)); | 1139 | 1139 | return; | 1140 | 1140 | } | 1141 | 1141 | const req = new TCPConnectWrap(); | @@ -1167,20 +1167,12 @@ function internalConnect( | 1167 | 1167 | } | 1168 | 1168 |
| 1169 | 1169 | const ex = new ExceptionWithHostPort(err, 'connect', address, port, details); | 1170 | | - process.nextTick(emitErrorAndDestroy, self, ex); | | 1170 | + self.destroy(ex); | 1171 | 1171 | } else if ((addressType === 6 || addressType === 4) && hasObserver('net')) { | 1172 | 1172 | startPerf(self, kPerfHooksNetConnectContext, { type: 'net', name: 'connect', detail: { host: address, port } }); | 1173 | 1173 | } | 1174 | 1174 | } | 1175 | 1175 |
| 1176 | | -// Helper function to defer socket destruction to the next tick. | 1177 | | -// This ensures that error handlers have a chance to be set up | 1178 | | -// before the error is emitted, particularly important when using | 1179 | | -// http.request with a custom lookup function. | 1180 | | -function emitErrorAndDestroy(self, err) { | 1181 | | - self.destroy(err); | 1182 | | -} | 1183 | | - | 1184 | 1176 |
| 1185 | 1177 | function internalConnectMultiple(context, canceled) { | 1186 | 1178 | clearTimeout(context[kTimeout]); | @@ -1194,11 +1186,11 @@ function internalConnectMultiple(context, canceled) { | 1194 | 1186 | // All connections have been tried without success, destroy with error | 1195 | 1187 | if (canceled || context.current === context.addresses.length) { | 1196 | 1188 | if (context.errors.length === 0) { | 1197 | | - process.nextTick(emitErrorAndDestroy, self, new ERR_SOCKET_CONNECTION_TIMEOUT()); | | 1189 | + self.destroy(new ERR_SOCKET_CONNECTION_TIMEOUT()); | 1198 | 1190 | return; | 1199 | 1191 | } | 1200 | 1192 |
| 1201 | | - process.nextTick(emitErrorAndDestroy, self, new NodeAggregateError(context.errors)); | | 1193 | + self.destroy(new NodeAggregateError(context.errors)); | 1202 | 1194 | return; | 1203 | 1195 | } | 1204 | 1196 |
| |
Collapse file Copy file name to clipboardExpand all lines: test/parallel/test-http-request-lookup-error-catchable.js +2-2Lines changed: 2 additions & 2 deletions | Original file line number | Diff line number | Diff line change |
|---|
@@ -13,8 +13,8 @@ const net = require('net'); | 13 | 13 | // 2. The lookup returns an IP that triggers a synchronous error (e.g., blockList) | 14 | 14 | // 3. The error is emitted before http's error handler is set up (via nextTick) | 15 | 15 | // | 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. | 18 | 18 |
| 19 | 19 | const blockList = new net.BlockList(); | 20 | 20 | blockList.addAddress(common.localhostIPv4); | |
|