Skip to content

fix(ext/node): http client compat improvements#33337

Merged
bartlomieju merged 10 commits into
mainfrom
fix/http-compat-improvements
Apr 21, 2026
Merged

fix(ext/node): http client compat improvements#33337
bartlomieju merged 10 commits into
mainfrom
fix/http-compat-improvements

Conversation

@bartlomieju

@bartlomieju bartlomieju commented Apr 20, 2026

Copy link
Copy Markdown
Member

Summary

Several fixes to node:http client and server compatibility:

  • Fix response abort/error not emitting on req.abort() - removed overly broad req.destroyed guard in socketCloseListener that prevented cleanup of the response stream
  • Fix keep-alive socket pooling with server-initiated close - use socket.destroyed for early exit in agent's 'free' handler, but still check !socket.writable before assigning to queued requests (prevents handing half-closed sockets to new requests)
  • Make http.globalAgent assignable - globalAgent was a static import binding; now uses getter/setter so http.globalAgent = newAgent takes effect on subsequent requests
  • Spread URL object in urlToHttpOptions - match Node's ...url spread so user-added properties (like headers) on URL objects are preserved
  • Detach socket on HTTP CONNECT/upgrade - remove all server-added listeners from the socket before emitting the 'connect'/'upgrade' event, matching Node's behavior
  • Implement ERR_HTTP_CONTENT_LENGTH_MISMATCH - add strict content-length validation in write_() and end() when strictContentLength is enabled

New compat tests enabled

  • test-http-aborted.js
  • test-http-agent-keepalive.js
  • test-http-client-override-global-agent.js
  • test-http-client-request-options.js
  • test-http-client-set-timeout.js
  • test-http-client-timeout-event.js
  • test-http-connect-req-res.js
  • test-http-connect.js
  • test-http-content-length-mismatch.js

Test plan

  • All newly enabled compat tests pass
  • ./x test-node http passes (5 tests)
  • Existing keep-alive compat tests still pass
  • ./x lint and ./x fmt pass

🤖 Generated with Claude Code

bartlomieju and others added 10 commits April 20, 2026 23:31
Remove the overly broad `req.destroyed` early-return guard in
socketCloseListener. When req.abort() is called, req.destroyed is set
to true before the socket 'close' event fires — causing
socketCloseListener to skip res.destroy(), so 'aborted' and 'error'
events never reached the response.

Node.js does not have this guard; it sets req.destroyed inside
socketCloseListener itself. The original Deno guard was a workaround
for a timing issue with responseKeepAlive, but the !req check alone
is sufficient.

Enables test-http-aborted.js in the compat suite.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
In Node.js, process.nextTick always runs before I/O callbacks. The HTTP
client uses nextTick to defer emitFreeNT (which returns sockets to the
agent's free pool). In Deno, I/O can interleave with nextTick, so a
server FIN may arrive before emitFreeNT runs, making the socket
non-writable.

Fix by checking socket.destroyed instead of !socket.writable in the
agent's 'free' handler. A socket that received a server FIN but hasn't
been destroyed yet can still be pooled — it will be cleaned up by the
'close' listener installed in installListeners.

Enables test-http-agent-keepalive.js in the compat suite.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
The globalAgent was exported as a static value, so assigning
http.globalAgent = newAgent had no effect on subsequent requests.

Fix by:
- Making globalAgent a mutable let export in _http_agent.js
- Adding setGlobalAgent() helper
- Using getter/setter on the http default export
- Accessing globalAgent via httpAgent.globalAgent in _http_client.js
  (dynamic lookup instead of static import binding)

Enables test-http-client-override-global-agent.js in compat suite.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Node.js spreads the URL object (...url) into the options so that
user-added properties (like headers) are preserved. Deno only extracted
known URL properties, losing any user extensions.

Enables test-http-client-request-options.js in compat suite.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
When the server emits 'connect' or 'upgrade', the socket should be
fully detached from the HTTP server — all server-added listeners
removed. Previously Deno left them attached, causing listener count
assertions to fail and the socket to remain under server control.

Match Node.js behavior by removing data, end, close, drain, error,
and timeout listeners before emitting the event. Also remove the
Deno-specific ConnectionsList tracking listener and pop the socket
from the connections list.

Enables test-http-connect.js and test-http-connect-req-res.js in the
compat suite.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
Add strict content-length validation to OutgoingMessage:
- In write_(): throw if bytes written exceed content-length (or don't
  match on end)
- In end(): throw if total bytes written doesn't match content-length
- Add ERR_HTTP_CONTENT_LENGTH_MISMATCH error class

Enables test-http-content-length-mismatch.js in compat suite.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>
…ket guard

- Implement ERR_HTTP_CONTENT_LENGTH_MISMATCH validation in write_() and
  end() for strictContentLength mode
- Tighten agent free socket guard: use socket.destroyed for early exit,
  but still check !socket.writable before assigning to queued requests
  (prevents handing half-closed sockets to new requests)

Enables test-http-content-length-mismatch.js in compat suite.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>

@nathanwhit nathanwhit left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@bartlomieju
bartlomieju merged commit 528ed97 into main Apr 21, 2026
220 of 222 checks passed
@bartlomieju
bartlomieju deleted the fix/http-compat-improvements branch April 21, 2026 19:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants