Files
webnet/packages
codingetandClaude aaec794c88 fix(http): prevent pool race when a fetch() response is promoted to WebSocket
Two bugs in fetch.ts caused the upgraded connection to be closed or
reassigned before connectWebSocket() could hijack it:

1. prepareRequest() checked Connection !== "upgrade" case-sensitively.
   A request with Connection: Upgrade (capital U) was silently rewritten
   to Connection: close, making ClientConnection auto-close the transport
   after receiving the 101 (shouldClose returned true, no body → immediate
   close).  Fixed by lowercasing before the comparison.

2. rawFetch() called onPrevBodyFinished().then(done(false)) even for 101
   responses.  A 101 has no body so the promise resolves as a microtask,
   firing done(false) — and thus pool.releaseConnection() — before the
   caller can call connectWebSocket().  With keepAlive:false the pool
   closes the connection immediately; with keepAlive:N it puts it back in
   the idle pool where a concurrent waiter can grab it.  Fixed by calling
   done(true) synchronously for 101, which calls rejectConnection()
   instead, removing the connection from the pool without closing it.

   rawFetchStream() had the same issue in its finally block when the
   caller breaks before calling connectWebSocket(); fixed likewise.

Tests added in websocket.test.ts covering all four affected paths:
  - fetch() + connectWebSocket(res, key)
  - makeFetch(keepAlive:false) + connectWebSocket(res, key)
  - pool race: second request after WebSocket hijack gets a fresh conn
  - fetchStream() + connectWebSocket(r, key) inside the loop
  - makeFetch().stream + connectWebSocket(r, key)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-01 07:45:30 +00:00
..