Skip to content

fix(http): Node http client must not follow redirects#6487

Merged
proggeramlug merged 1 commit into
PerryTS:mainfrom
proggeramlug:fix/node-http-client-no-redirect-follow
Jul 18, 2026
Merged

fix(http): Node http client must not follow redirects#6487
proggeramlug merged 1 commit into
PerryTS:mainfrom
proggeramlug:fix/node-http-client-no-redirect-follow

Conversation

@proggeramlug

@proggeramlug proggeramlug commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

Problem

Node's http.request / http.get / https.request never follow HTTP redirects — a 3xx response is delivered to the caller verbatim (only fetch follows, per its WHATWG redirect mode). Perry's Node http client is built on reqwest, whose default redirect::Policy silently follows up to 10 hops.

That auto-follow is observably wrong (the caller sees the final 200 instead of the 307 + location), and it breaks Next.js's proxyRequest:

  • proxyRequest runs Next's bundled http-proxy over this very client.
  • A proxied sub-request that 307-redirects back to the entry path was auto-followed here instead of relayed as a transparent response.
  • With a locale middleware where / internally rewrites to /en and /en 307s back to /, the router re-resolved the same rewrite forever — the request hung (no response, connection leak) instead of returning the 307.

Reproduction

import { createServer, get } from "node:http";
const PORT = 18993; let hits = 0;
const server = createServer((req, res) => {
  hits++;
  if (req.url === "/target") { res.statusCode = 200; res.end("FINAL"); return; }
  res.statusCode = 307; res.setHeader("location", `http://127.0.0.1:${PORT}/target`); res.end("redir");
});
server.listen(PORT, () => get(`http://127.0.0.1:${PORT}/start`, (res) => {
  res.on("data", () => {}); res.on("end", () => {
    console.log(res.statusCode, hits);   // node: 307 1   perry(before): 200 2
    server.close();
  });
}));

Node: 307, server hit once. Perry (before): 200, server hit twice (followed).

Fix

Disable redirect-following (reqwest::redirect::Policy::none()) on both Node-http client backends so a 3xx flows back to the caller exactly as Node delivers it:

  • perry-ext-httpapply_node_proxy_policy (the external-http-client-pump path). Every real client build funnels through it: the shared HTTP_CLIENT, the per-Agent client, the TLS-customized client, and default_client.
  • perry-stdlibjs_http_client_request_end's reqwest builder (the non-external-http-client-pump path).

fetch has its own client and redirect handling and is unaffected.

Test

test-files/test_gap_http_client_no_redirect_follow.ts — starts a server that 307-redirects, does http.get, and asserts the client reports the 307 + location and the server is hit exactly once. Byte-for-byte identical to node --experimental-strip-types.

https://claude.ai/code/session_01KD4GTmiwZjRrxShzQydJpF

Summary by CodeRabbit

  • Bug Fixes

    • Updated the Perry Node HTTP client to stop automatically following HTTP redirects, returning 3xx responses directly to the caller.
    • Redirect status codes and Location headers are now preserved and exposed as received, for Node-compatible behavior.
  • Tests

    • Added a regression test covering 307 responses to ensure the client returns the original redirect (not the final destination) and does not hit the redirected endpoint.

@coderabbitai

coderabbitai Bot commented Jul 16, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 2bdade9d-6929-4c73-b2c1-3780ad33d867

📥 Commits

Reviewing files that changed from the base of the PR and between 466673e and dcf64f1.

📒 Files selected for processing (3)
  • crates/perry-ext-http/src/lib.rs
  • crates/perry-stdlib/src/http.rs
  • test-files/test_gap_http_client_no_redirect_follow.ts
🚧 Files skipped from review as they are similar to previous changes (3)
  • test-files/test_gap_http_client_no_redirect_follow.ts
  • crates/perry-stdlib/src/http.rs
  • crates/perry-ext-http/src/lib.rs

📝 Walkthrough

Walkthrough

Reqwest redirect following is disabled in Perry’s Node HTTP request paths. A regression test verifies that a 307 response is returned directly, including its location header, without requesting the redirect target.

Changes

HTTP redirect handling

Layer / File(s) Summary
Redirect policy enforcement
crates/perry-ext-http/src/lib.rs, crates/perry-stdlib/src/http.rs
Reqwest builders now use redirect::Policy::none(), preserving 3xx responses for Node HTTP callers while retaining existing proxy configuration.
Redirect regression coverage
test-files/test_gap_http_client_no_redirect_follow.ts
A local 307-to-200 server flow verifies that the caller receives the 307 and the server is contacted only once.

Estimated code review effort: 2 (Simple) | ~10 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately states the main change: preventing the Node HTTP client from following redirects.
Description check ✅ Passed The description clearly explains the problem, fix, reproduction, and test, though it omits the template's Related issue and checklist sections.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
crates/perry-stdlib/src/http.rs (1)

928-939: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider deduplicating this extensive comment.

This large explanatory comment is duplicated verbatim from crates/perry-ext-http/src/lib.rs. Consider keeping the detailed explanation in one place and adding a brief reference to it here to reduce duplication.

♻️ Proposed refactor
-        // Node's `http.request`/`http.get`/`https.request` NEVER follow
-        // redirects — a 3xx response is delivered to the caller verbatim
-        // (only `fetch` follows, per its WHATWG redirect mode). reqwest's
-        // default policy silently follows up to 10 hops, which is observably
-        // wrong for the Node client and, worse, turned Next.js's
-        // `proxyRequest` (its bundled `http-proxy` runs over this very client)
-        // into an infinite loop: a proxied sub-request that 307-redirects back
-        // to the entry path was auto-followed HERE instead of relayed to the
-        // proxy for a transparent response, so the router re-resolved the same
-        // middleware rewrite forever (`/` rewrites to `/en`, `/en` 307s to `/`).
-        // Disable following so the 3xx flows back to the caller as Node does.
+        // Disable redirect following so the 3xx flows back to the caller as Node does.
+        // See `apply_node_proxy_policy` in `crates/perry-ext-http/src/lib.rs` for detailed reasoning.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/perry-stdlib/src/http.rs` around lines 928 - 939, Deduplicate the
redirect-policy explanation near the reqwest builder in the HTTP implementation:
retain the detailed rationale in one canonical location, and replace the
duplicate block in the surrounding request setup with a brief reference to that
location while preserving builder.redirect(reqwest::redirect::Policy::none()).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@crates/perry-stdlib/src/http.rs`:
- Around line 928-939: Deduplicate the redirect-policy explanation near the
reqwest builder in the HTTP implementation: retain the detailed rationale in one
canonical location, and replace the duplicate block in the surrounding request
setup with a brief reference to that location while preserving
builder.redirect(reqwest::redirect::Policy::none()).

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 27f499d4-3ff9-4ddb-9bc0-5c0fa427515a

📥 Commits

Reviewing files that changed from the base of the PR and between 77def53 and 7c4a1e9.

📒 Files selected for processing (3)
  • crates/perry-ext-http/src/lib.rs
  • crates/perry-stdlib/src/http.rs
  • test-files/test_gap_http_client_no_redirect_follow.ts

@proggeramlug
proggeramlug force-pushed the fix/node-http-client-no-redirect-follow branch from 7c4a1e9 to 466673e Compare July 17, 2026 22:40
Node's `http.request`/`http.get`/`https.request` never follow HTTP
redirects — a 3xx is delivered to the caller verbatim (only `fetch`
follows, per its WHATWG redirect mode). Perry's Node http client is built
on reqwest, whose default `redirect::Policy` silently follows up to 10
hops. That auto-follow is observably wrong (the caller sees the final 200
instead of the 307 + `location`).

Worse, it breaks Next.js's `proxyRequest`: its bundled `http-proxy` runs
over this very client, so a proxied sub-request that 307-redirects back
to the entry path was auto-followed here instead of relayed as a
transparent response. With a locale middleware where `/` rewrites to
`/en` and `/en` 307s back to `/`, the router re-resolved the same rewrite
forever — the request hung (no response, connection leak) instead of
returning the 307.

Disable redirect-following (`Policy::none()`) on both Node-http client
backends:
  - perry-ext-http `apply_node_proxy_policy` (the `external-http-client-
    pump` path — every real client build funnels through it: HTTP_CLIENT,
    the per-Agent client, the TLS-customized client, default_client)
  - perry-stdlib `js_http_client_request_end`'s reqwest builder (the
    non-`external-http-client-pump` path)

so a 3xx flows back to the caller exactly as Node delivers it. `fetch`
has its own client and redirect handling and is unaffected.

Claude-Session: https://claude.ai/code/session_01KD4GTmiwZjRrxShzQydJpF
@proggeramlug
proggeramlug force-pushed the fix/node-http-client-no-redirect-follow branch from 466673e to dcf64f1 Compare July 17, 2026 22:51
@proggeramlug proggeramlug added the ready PR triaged: CodeRabbit feedback + conflicts addressed label Jul 18, 2026
@proggeramlug
proggeramlug merged commit 811d4c4 into PerryTS:main Jul 18, 2026
44 of 49 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ready PR triaged: CodeRabbit feedback + conflicts addressed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant