Skip to content

Replace http.request() with raw TCP/TLS sockets for CONNECT tunneling#663

Draft
jancurn wants to merge 2 commits into
masterfrom
claude/fix-bun-e2e-tests-4p3oy
Draft

Replace http.request() with raw TCP/TLS sockets for CONNECT tunneling#663
jancurn wants to merge 2 commits into
masterfrom
claude/fix-bun-e2e-tests-4p3oy

Conversation

@jancurn
Copy link
Copy Markdown
Member

@jancurn jancurn commented May 21, 2026

Summary

Refactors the upstream proxy CONNECT tunnel implementation to use raw TCP/TLS sockets instead of http.request() with the 'connect' event. This change improves compatibility with runtimes like Bun 1.3 that have limitations with their HTTP client implementation.

Key Changes

  • Chain handler refactoring: Replaced http.request() / https.request() with direct net.createConnection() and tls.connect() calls

    • Manually constructs and sends the CONNECT request as a raw HTTP string
    • Manually parses the CONNECT response headers instead of relying on the HTTP client's event handling
    • Eliminates dependency on the 'connect' event which behaves differently across runtimes
  • Byte counting improvements: Enhanced countTargetBytes() utility to handle runtimes where socket-level byte counters are unreliable

    • Added manual byte tracking via 'data' and 'pipe'/'unpipe' event listeners
    • Falls back to manual counters when socket-level bytesRead/bytesWritten report 0
    • Maintains backward compatibility with Node.js where socket counters work correctly
  • Server request routing: Added explicit routing of CONNECT requests through onConnect() in the 'request' event handler

    • Handles runtimes (like Bun 1.3) that deliver CONNECT requests via the generic 'request' event instead of the dedicated 'connect' event
  • SSL certificate regeneration: Updated test SSL certificate and key to use modern PKCS#8 format

Implementation Details

  • The raw socket approach sidesteps two Bun 1.3 quirks:

    1. Rejection of non-URL CONNECT paths like :443 with "fetch() URL is invalid"
    2. Silent swallowing of 407/590-class upstream responses that tests rely on
  • Manual byte counting uses passive listeners with zero overhead on Node.js (socket counters always win) while providing the only reliable source of truth on Bun 1.3

  • Maintains full backward compatibility with existing behavior on Node.js while improving cross-runtime support

https://claude.ai/code/session_01Vn16UCyisJSwEaAoHLSNX6

claude added 2 commits May 12, 2026 20:43
Two narrowly-scoped fixes that knock out concrete failure categories in the
Bun e2e suite without touching Node behavior:

1. `count_target_bytes` now also accumulates bytes via passive 'data'
   listeners on the target socket and via a 'pipe'/'unpipe'-tracked
   listener on whatever's piped into it. Node continues to win on its own
   `socket.bytesRead`/`bytesWritten` counters (they're always at least as
   high as the manual tally, so `Math.max` keeps them). Bun 1.3 reports 0
   on http client sockets — the manual counters now carry it.

2. Regenerated `test/e2e/ssl.crt` / `ssl.key` with a 100-year validity.
   The previous cert expired 2018-11-10; Node accepted it because the
   tests pass `rejectUnauthorized: false`, but Bun rejects expired certs
   strictly on the WS path and four `https-stress` / WS tests failed
   with "certificate has expired".
…as-request in server

Inspired by #649. Two changes that let proxy-chain itself run under Bun
when used as a library, independent of what the test harness can prove:

1. `src/chain.ts` no longer goes through `http.request().on('connect')`
   to reach the upstream proxy. Bun 1.3 implements http.request on top of
   fetch and (a) rejects RFC 7230 authority-form paths like `:443` with
   "fetch() URL is invalid", (b) swallows the 407/590-class upstream
   responses. We now open a raw `net` or `tls` socket, send the CONNECT
   line ourselves, parse the response header buffer, and hand the
   remaining bytes back via `socket.unshift`. `rawHeaders` is preserved
   on the synthetic IncomingMessage so `tunnelConnectResponded`
   subscribers (the anonymizeProxy listener test) keep working.

2. `Server.onRequest` falls back to `onConnect` when `request.method ===
   'CONNECT'`. Bun delivers CONNECT through the generic 'request' event
   for some client shapes; Node ignores this branch because it always
   uses the dedicated 'connect' event.
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.

3 participants