Skip to content

feat: http/2 listener support#102

Open
SukkaW wants to merge 16 commits intounjs:mainfrom
SukkaW:http2-support
Open

feat: http/2 listener support#102
SukkaW wants to merge 16 commits intounjs:mainfrom
SukkaW:http2-support

Conversation

@SukkaW
Copy link
Collaborator

@SukkaW SukkaW commented Feb 20, 2026

First step toward #38 with HTTP/2 server/listener support.

Summary by CodeRabbit

  • New Features

    • Added HTTP/2 support with a new http2 configuration option to enable HTTP/2 listeners alongside HTTP/1.x compatibility.
    • Enhanced header handling to support HTTP/2 pseudo-headers for proper request routing.
  • Tests

    • Added comprehensive HTTP/2 proxy integration test suite covering HTTP/2-to-HTTP and HTTP/2-to-HTTPS scenarios.
  • Chores

    • Added undici as a development dependency for HTTP/2 testing capabilities.

@codecov
Copy link

codecov bot commented Feb 20, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 96.57%. Comparing base (4b03756) to head (91b63fb).

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #102      +/-   ##
==========================================
+ Coverage   96.47%   96.57%   +0.09%     
==========================================
  Files           8        8              
  Lines         596      613      +17     
  Branches      222      230       +8     
==========================================
+ Hits          575      592      +17     
  Misses         21       21              

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@SukkaW SukkaW requested review from pi0 and sapphi-red and removed request for pi0 February 20, 2026 15:32
@SukkaW SukkaW marked this pull request as ready for review February 20, 2026 15:32
@SukkaW
Copy link
Collaborator Author

SukkaW commented Feb 20, 2026

@pi0 I believe this PR is ready for review now. All tests passed on my machine locally

image

@SukkaW SukkaW changed the title feat: initial http/2 listener support feat: http/2 listener support Feb 20, 2026
@SukkaW SukkaW requested review from Copilot and pi0 February 23, 2026 09:57
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Adds initial HTTP/2 (h2) listener support to the proxy server as a step toward issue #38 (HTTP2/3 support), including middleware updates to handle HTTP/2-specific header semantics and new integration tests.

Changes:

  • Introduce http2?: boolean option and create an HTTP/2 secure listener (with allowHTTP1) when enabled.
  • Update incoming/outgoing middleware + core utils to handle HTTP/2 pseudo-headers (:authority) and remove HTTP/2-incompatible hop-by-hop headers.
  • Add HTTP/2 integration tests (via undici with h2-enabled agent) and extend stubs/tests for httpVersionMajor.

Reviewed changes

Copilot reviewed 11 out of 12 changed files in this pull request and generated 4 comments.

Show a summary per file
File Description
test/middleware/web-outgoing.test.ts Adds coverage for :authority redirect rewriting and HTTP/2 connection-header behavior.
test/https-proxy.test.ts Exports helper functions for listening (currently used by new HTTP/2 tests).
test/http2-proxy.test.ts New HTTP/2 listener integration tests (HTTP/2+HTTP/1.1 clients over TLS).
test/_stubs.ts Enhances IncomingMessage stubs with httpVersion/httpVersionMajor; tweaks ProxyServer stub typing.
src/types.ts Adds http2 option and documents SSL usage for HTTP/2 listener.
src/server.ts Implements HTTP/2 secure server creation path; broadens request/response typings to include HTTP/2 compat types.
src/middleware/web-outgoing.ts Removes HTTP/2-incompatible headers (e.g., connection), supports :authority for redirects/status handling.
src/middleware/web-incoming.ts Uses :authority as a fallback for x-forwarded-host.
src/middleware/_utils.ts Extends middleware typing to accept HTTP/2 request/response types (with one remaining generic constraint issue).
src/_utils.ts Filters HTTP/2 pseudo-headers from outgoing request options and uses :authority for host/port derivation.
package.json Adds undici dev dependency for HTTP/2-capable fetch in tests.
pnpm-lock.yaml Locks undici addition.
Files not reviewed (1)
  • pnpm-lock.yaml: Language not supported
Comments suppressed due to low confidence (4)

src/server.ts:85

  • When http2 is enabled, the code always creates a TLS server via http2.createSecureServer(...), but ssl is still optional in ProxyServerOptions. If a user sets http2: true without providing TLS credentials, the server will fail at runtime; consider validating options.ssl (or requiring it in types) and throwing a clear error early.
    if (this.options.http2) {
      this._server = http2.createSecureServer({ ...this.options.ssl, allowHTTP1: true }, closure);
    } else if (this.options.ssl) {
      this._server = https.createServer(this.options.ssl, closure);

src/server.ts:43

  • _webPasses is still typed as ProxyMiddleware<http.ServerResponse>[], but when http2 is enabled the res passed through the passes can be http2.Http2ServerResponse. This mismatch is currently papered over with casts in _getPasses/listen, reducing type safety and making it easy for a pass to accidentally use HTTP/1-only APIs. Consider widening the _webPasses type (and the middleware list types) to include Http2ServerResponse so incompatibilities are caught by typecheck.
  _webPasses: ProxyMiddleware<http.ServerResponse>[] = [...webIncomingMiddleware];
  _wsPasses: ProxyMiddleware<net.Socket>[] = [...websocketIncomingMiddleware];

test/http2-proxy.test.ts:25

  • undici Agent instances keep connection pools open; without closing them, the test runner can hang or report open handles. Add a top-level afterAll to close()/destroy() both http1Agent and http2Agent once the suite completes.
const http1Agent = new Agent({
  allowH2: false,
  connect: {
    // Allow to use SSL self signed
    rejectUnauthorized: false,
  },
});
const http2Agent = new Agent({
  allowH2: true,
  connect: {
    // Allow to use SSL self signed
    rejectUnauthorized: false,
  },
});

src/middleware/_utils.ts:29

  • defineProxyMiddleware's generic constraint is still T extends ServerResponse | Socket, but ProxyMiddleware now supports Http2ServerResponse as well. This makes it impossible to declare a middleware explicitly typed as ProxyMiddleware<Http2ServerResponse> (it won't satisfy the constraint) and is inconsistent with the updated ProxyMiddleware type. Widen defineProxyMiddleware's constraint to include Http2ServerResponse.
export function defineProxyMiddleware<T extends ServerResponse | Socket = ServerResponse>(
  m: ProxyMiddleware<T>,
) {

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@coderabbitai
Copy link

coderabbitai bot commented Mar 1, 2026

📝 Walkthrough

Walkthrough

This pull request introduces HTTP/2 support to the proxy library by adding type-aware request/response handling, HTTP/2 pseudo-header management, conditional server creation with HTTP/2 flags, and comprehensive test coverage including new HTTP/2 integration tests.

Changes

Cohort / File(s) Summary
Dependencies
package.json
Added undici devDependency (^7.21.0) for HTTP/2-capable client testing.
Core HTTP/2 Type Support
src/types.ts
Added optional http2 flag to ProxyServerOptions to enable HTTP/2 listener creation.
Request/Response Utilities
src/_utils.ts
Extended port and TLS connection detection to handle Http2ServerRequest; added HTTP/2 pseudo-header stripping and authority-to-host copying for outgoing requests.
Middleware Type Definitions
src/middleware/_utils.ts
Broadened ResOfType, ProxyMiddleware, and ProxyOutgoingMiddleware to include Http2ServerRequest and Http2ServerResponse union types.
HTTP/2 Middleware Logic
src/middleware/web-incoming.ts, src/middleware/web-outgoing.ts
Updated x-forwarded-host assignment to prefer :authority pseudo-header; refined setConnection to delete connection header for HTTP/2+; generalized status code handling and redirect rewriting to support HTTP/2.
Server Implementation
src/server.ts
Expanded generic parameters to include HTTP/2 types; implemented conditional HTTP/2 secure server creation; updated web/ws method signatures and internal proxy function types to support mixed HTTP/1 and HTTP/2 contexts.
Test Utilities
test/_stubs.ts, test/_utils.ts
Enhanced stubIncomingMessage with HTTP version fields; generalized stubProxyServer; added listenOn and proxyListen helper functions for ephemeral port resolution.
Test Consolidation
test/http-proxy.test.ts, test/https-proxy.test.ts, test/middleware/web-incoming.test.ts
Replaced in-file listenOn/proxyListen implementations with shared utils imports.
HTTP/2 Integration Tests
test/http2-proxy.test.ts
New comprehensive test suite validating HTTP/2 proxy with both plaintext and HTTPS targets using self-signed certificates and undici agents.
HTTP/2 Middleware Tests
test/middleware/web-outgoing.test.ts
Added test coverage for authority-based redirect rewriting, HTTP/2 connection header deletion, and httpVersionMajor-specific behaviors.

Sequence Diagram

sequenceDiagram
    participant Client
    participant Proxy as HTTP/2 Proxy
    participant Target as Target Server

    Client->>Proxy: HTTP/2 Request (with :authority pseudo-header)
    Note over Proxy: Extract authority header<br/>Identify HTTP/2 (httpVersionMajor > 1)<br/>Strip pseudo-headers from outgoing
    Proxy->>Proxy: setupOutgoing:<br/>Copy :authority → host<br/>Strip HTTP/2 pseudo-headers
    Proxy->>Target: HTTP/1.1 Request (host header)
    Target-->>Proxy: Response (statusCode, headers)
    Note over Proxy: setConnection: Delete for HTTP/2+<br/>writeStatusCode: Skip statusMessage for HTTP/2+<br/>setRedirectHostRewrite: Prefer :authority
    Proxy-->>Client: HTTP/2 Response
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~65 minutes

Poem

🐰 Bouncing through protocols with glee,
HTTP/2 support now flows so free,
Authority headers dance, pseudo ones fade,
Connection-less HTTP/2 paths we've made!
From :authority to host, the proxy now sees,
Version-aware logic flowing with ease.

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat: http/2 listener support' clearly summarizes the main change—adding HTTP/2 listener support to the proxy—and accurately reflects the primary objective across all modified files.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🧹 Nitpick comments (2)
test/_stubs.ts (1)

60-61: Minor type inconsistency in return cast.

The return type is ProxyServer<any, any> but the cast on line 61 uses the unparameterized ProxyServer. This works due to the as unknown intermediate cast but could be slightly cleaner.

Optional fix for type consistency
 export function stubProxyServer(overrides: Record<string, unknown> = {}): ProxyServer<any, any> {
-  return overrides as unknown as ProxyServer;
+  return overrides as unknown as ProxyServer<any, any>;
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/_stubs.ts` around lines 60 - 61, The stubProxyServer function returns
ProxyServer<any, any> but currently casts to the raw ProxyServer type; update
the return cast to match the declared generic parameters—change the final cast
to "as unknown as ProxyServer<any, any>" in the stubProxyServer function so the
returned type is consistently ProxyServer<any, any>.
test/http2-proxy.test.ts (1)

87-91: Consider awaiting source.close() for proper cleanup.

source.close() (for http.Server / https.Server) is asynchronous and accepts an optional callback. While it often works without awaiting, for deterministic test cleanup, consider wrapping it in a Promise or using a callback to ensure the server is fully closed before the test suite ends.

♻️ Optional: Await source close for consistent cleanup
     afterAll(async () => {
       // cleans up
       await new Promise<void>((resolve) => proxy.close(resolve));
-      source.close();
+      await new Promise<void>((resolve, reject) => 
+        source.close((err) => err ? reject(err) : resolve())
+      );
     });

Also applies to: 161-165

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/http2-proxy.test.ts` around lines 87 - 91, The afterAll cleanup
currently calls source.close() without awaiting its completion; change it to
await the server close by wrapping source.close in a Promise (or using its
callback) so the test waits for full shutdown, similar to how
proxy.close(resolve) is awaited; update all occurrences (e.g., the afterAll
blocks around source.close at the shown locations) to await the
Promise-returning close to ensure deterministic teardown.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@package.json`:
- Line 56: The package entry for undici is fine but HTTP/2 is opt-in; when you
create or configure undici's Agent or Client (Agent, Client) for HTTP/2 tests
set the allowH2 option to true (e.g., pass allowH2: true to Agent/Client
constructor or options) so HTTP/2 is actually enabled, and optionally bump the
package version to 7.22.0 if you want the latest patch release.

In `@src/server.ts`:
- Around line 82-88: The server currently calls http2.createSecureServer when
this.options.http2 is true but does not validate that this.options.ssl is
provided; add a runtime check in the server initialization (before calling
http2.createSecureServer) that if this.options.http2 is true and
this.options.ssl is falsy, throw or reject with a clear error explaining that
HTTP/2 requires SSL (referencing ProxyServerOptions in the message), otherwise
proceed to call http2.createSecureServer; ensure the check is placed alongside
the existing branch that inspects this.options.http2 / this.options.ssl so the
error occurs early with a helpful message rather than a cryptic
createSecureServer failure.

In `@test/_utils.ts`:
- Around line 16-27: The proxyListen helper can miss synchronous errors because
proxy.listen(0, "127.0.0.1") is called before attaching the server error
listener; to fix, in proxyListen wrap the call to proxy.listen(...) in a
try/catch and immediately reject on synchronous throw, then grab (proxy as
any)._server and attach server.once("error", reject) and
server.once("listening", ...) as before; keep the existing resolve logic in the
"listening" handler for port resolution.

---

Nitpick comments:
In `@test/_stubs.ts`:
- Around line 60-61: The stubProxyServer function returns ProxyServer<any, any>
but currently casts to the raw ProxyServer type; update the return cast to match
the declared generic parameters—change the final cast to "as unknown as
ProxyServer<any, any>" in the stubProxyServer function so the returned type is
consistently ProxyServer<any, any>.

In `@test/http2-proxy.test.ts`:
- Around line 87-91: The afterAll cleanup currently calls source.close() without
awaiting its completion; change it to await the server close by wrapping
source.close in a Promise (or using its callback) so the test waits for full
shutdown, similar to how proxy.close(resolve) is awaited; update all occurrences
(e.g., the afterAll blocks around source.close at the shown locations) to await
the Promise-returning close to ensure deterministic teardown.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 4b03756 and 91b63fb.

⛔ Files ignored due to path filters (1)
  • pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (14)
  • package.json
  • src/_utils.ts
  • src/middleware/_utils.ts
  • src/middleware/web-incoming.ts
  • src/middleware/web-outgoing.ts
  • src/server.ts
  • src/types.ts
  • test/_stubs.ts
  • test/_utils.ts
  • test/http-proxy.test.ts
  • test/http2-proxy.test.ts
  • test/https-proxy.test.ts
  • test/middleware/web-incoming.test.ts
  • test/middleware/web-outgoing.test.ts

"sse": "^0.0.8",
"typescript": "^5.9.3",
"unbuild": "^3.6.1",
"undici": "^7.21.0",
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Check if undici 7.21.0 exists and get latest version
echo "=== Checking undici versions ==="
curl -s https://registry.npmjs.org/undici | jq '.dist-tags | {latest, latest_v7: (.["7.21.0"] // "not found")}'

# Get detailed info about 7.21.0
curl -s https://registry.npmjs.org/undici/7.21.0 | jq '{version: .version, name: .name, deprecated: .deprecated}'

# Get latest version info
curl -s https://registry.npmjs.org/undici/latest | jq '{version: .version, name: .name, deprecated: .deprecated}'

Repository: unjs/httpxy

Length of output: 386


🏁 Script executed:

#!/bin/bash
# Check for known vulnerabilities in undici
echo "=== Checking for security advisories ==="
curl -s https://registry.npmjs.org/-/npm/v1/security/advisories?package=undici | jq '.advisories // empty'

# Alternative: check npm audit for this package
npm view undici@7.21.0 --json | jq '{version: .version, keywords: .keywords[0:5], description: .description}'

Repository: unjs/httpxy

Length of output: 282


🌐 Web query:

undici HTTP/2 support Node.js

💡 Result:

Undici (and therefore Node.js’s built-in fetch) is primarily an HTTP/1.1 client, but it does have HTTP/2 client support as an experimental, opt-in feature. [1] [2]

How to use HTTP/2 with undici

Enable it by using an Agent/Client with allowH2: true (HTTP/2 is negotiated via ALPN). The docs also note constraints like “TLS-only” and “h2c is not supported” (so in practice you typically need https:// + a server that negotiates h2). [2]

import { Agent, setGlobalDispatcher, fetch } from 'undici'

setGlobalDispatcher(new Agent({ allowH2: true }))

const res = await fetch('https://example.com/')
console.log(res.status)

Node.js built-in fetch and HTTP/2

Node’s built-in fetch() uses the undici version bundled with your Node release, and HTTP/2 is not enabled by default—there’s an open request to support HTTP/2 by default in fetch. [3] [4]

Sources: [1] undici repo README, [2] undici Client API docs (UNPKG) incl. allowH2 + HTTP/2 notes, [3] undici discussion mentioning enabling H2 via allowH2: true for fetch, [4] undici issue requesting HTTP/2-by-default for fetch.


LGTM - undici is suitable for HTTP/2 testing with proper configuration.

The undici 7.21.0 package is stable and has no known security vulnerabilities. However, HTTP/2 support is an experimental, opt-in feature that requires explicit configuration (allowH2: true on the Agent/Client). As a devDependency, it won't affect production bundles. Note that version 7.22.0 is available if you want to stay current with the latest patch.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@package.json` at line 56, The package entry for undici is fine but HTTP/2 is
opt-in; when you create or configure undici's Agent or Client (Agent, Client)
for HTTP/2 tests set the allowH2 option to true (e.g., pass allowH2: true to
Agent/Client constructor or options) so HTTP/2 is actually enabled, and
optionally bump the package version to 7.22.0 if you want the latest patch
release.

Comment on lines +82 to +88
if (this.options.http2) {
this._server = http2.createSecureServer({ ...this.options.ssl, allowHTTP1: true }, closure);
} else if (this.options.ssl) {
this._server = https.createServer(this.options.ssl, closure);
} else {
this._server = http.createServer(closure);
}
Copy link

@coderabbitai coderabbitai bot Mar 1, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

🧩 Analysis chain

🏁 Script executed:

# First, let's look at the server.ts file around the mentioned lines
cat -n src/server.ts | sed -n '1,100p'

Repository: unjs/httpxy

Length of output: 4435


🏁 Script executed:

# Check for any validation around http2 and ssl options
rg -n "http2|ssl" src/server.ts -B2 -A2 | head -100

Repository: unjs/httpxy

Length of output: 2305


🏁 Script executed:

# Check the type definitions and options interface
rg -n "options\." src/server.ts | head -30

Repository: unjs/httpxy

Length of output: 450


🏁 Script executed:

# Look for any tests that show how http2 is used
fd -e test.ts -e test.js -e spec.ts | xargs rg -l "http2" 2>/dev/null | head -5

Repository: unjs/httpxy

Length of output: 79


🏁 Script executed:

# Check the test file to see how http2 is used
cat -n test/http2-proxy.test.ts

Repository: unjs/httpxy

Length of output: 6341


🏁 Script executed:

# Check the ProxyServerOptions type definition
rg -n "ProxyServerOptions" src/types.ts -A20 | head -60

Repository: unjs/httpxy

Length of output: 793


🏁 Script executed:

# Look for any comments or documentation about http2 requirement
rg -n "http2|HTTP/2" --type=ts -C2 | head -100

Repository: unjs/httpxy

Length of output: 5729


HTTP/2 requires SSL configuration, which is already documented but lacks runtime validation.

The implementation uses http2.createSecureServer which requires SSL options. While the ProxyServerOptions JSDoc already documents this requirement ("Object to be passed to https.createServer() or http2.createSecureServer() if the http2 option is enabled"), there is no runtime validation. If a user sets http2: true without providing ssl, the code will fail at runtime with a cryptic error from createSecureServer.

Adding validation would prevent configuration mistakes:

Proposed runtime validation
     if (this.options.http2) {
+      if (!this.options.ssl) {
+        throw new Error("HTTP/2 requires SSL configuration (options.ssl)");
+      }
       this._server = http2.createSecureServer({ ...this.options.ssl, allowHTTP1: true }, closure);
     } else if (this.options.ssl) {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if (this.options.http2) {
this._server = http2.createSecureServer({ ...this.options.ssl, allowHTTP1: true }, closure);
} else if (this.options.ssl) {
this._server = https.createServer(this.options.ssl, closure);
} else {
this._server = http.createServer(closure);
}
if (this.options.http2) {
if (!this.options.ssl) {
throw new Error("HTTP/2 requires SSL configuration (options.ssl)");
}
this._server = http2.createSecureServer({ ...this.options.ssl, allowHTTP1: true }, closure);
} else if (this.options.ssl) {
this._server = https.createServer(this.options.ssl, closure);
} else {
this._server = http.createServer(closure);
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/server.ts` around lines 82 - 88, The server currently calls
http2.createSecureServer when this.options.http2 is true but does not validate
that this.options.ssl is provided; add a runtime check in the server
initialization (before calling http2.createSecureServer) that if
this.options.http2 is true and this.options.ssl is falsy, throw or reject with a
clear error explaining that HTTP/2 requires SSL (referencing ProxyServerOptions
in the message), otherwise proceed to call http2.createSecureServer; ensure the
check is placed alongside the existing branch that inspects this.options.http2 /
this.options.ssl so the error occurs early with a helpful message rather than a
cryptic createSecureServer failure.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Currently, the option.ssl is typed as any, and I don't want to touch it right now. Implementing a complex types that read allowH2 and then determine ssl type could be a breaking change.

Copy link

Choose a reason for hiding this comment

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

@SukkaW, I understand your concern about type complexity and breaking changes. Just to clarify, the suggestion was for runtime validation (a simple if check that throws an error) rather than type-level enforcement, so it wouldn't require changing the ssl: any type or introducing conditional types.

However, I completely respect your decision to keep the implementation as-is. The JSDoc already documents the requirement clearly, and users who set http2: true without SSL will get an error from Node.js anyway (though it will be less explicit). This is a reasonable trade-off to avoid additional complexity.

Feel free to proceed with the current implementation!

Comment on lines +16 to +27
export function proxyListen(
proxy: ReturnType<typeof httpProxy.createProxyServer>,
): Promise<number> {
return new Promise((resolve, reject) => {
proxy.listen(0, "127.0.0.1");
const server = (proxy as any)._server as net.Server;
server.once("error", reject);
server.once("listening", () => {
resolve((server.address() as AddressInfo).port);
});
});
}
Copy link

@coderabbitai coderabbitai bot Mar 1, 2026

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Potential race condition: error listener attached after listen() call.

The error listener on line 22 is attached after proxy.listen() is called on line 20. If the server emits an error synchronously or very quickly, the reject handler may not be registered in time.

Proposed fix to attach error listener before listen
 export function proxyListen(
   proxy: ReturnType<typeof httpProxy.createProxyServer>,
 ): Promise<number> {
   return new Promise((resolve, reject) => {
-    proxy.listen(0, "127.0.0.1");
     const server = (proxy as any)._server as net.Server;
+    proxy.listen(0, "127.0.0.1");
+    // Note: _server is created inside listen(), so we must access it after
     server.once("error", reject);
     server.once("listening", () => {
       resolve((server.address() as AddressInfo).port);
     });
   });
 }

Actually, since _server is created inside listen(), the current order is necessary. However, this means errors during initial binding could be missed. Consider wrapping in a try-catch or accepting this as a test utility limitation.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/_utils.ts` around lines 16 - 27, The proxyListen helper can miss
synchronous errors because proxy.listen(0, "127.0.0.1") is called before
attaching the server error listener; to fix, in proxyListen wrap the call to
proxy.listen(...) in a try/catch and immediately reject on synchronous throw,
then grab (proxy as any)._server and attach server.once("error", reject) and
server.once("listening", ...) as before; keep the existing resolve logic in the
"listening" handler for port resolution.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

very quickly

It is impossible. That's not how JavaScript or the event loop works.

synchronously

What's the point anyway? Throwing in new Promise() will result in an automatic rejection.

Copy link

Choose a reason for hiding this comment

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

@SukkaW You're absolutely right on both points. I apologize for the confusion.

The event loop guarantees that all synchronous code within the Promise executor completes before any asynchronous events can be processed, so the error and listening listeners will always be attached before those events can fire. Additionally, any synchronous throw within the Promise executor is automatically converted to a rejection by the Promise constructor.

The code is correct as written. Thank you for the clarification!

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