fix: apply middleware request-header overrides to App Route request objects#809
Conversation
|
/bigbonk review this PR |
There was a problem hiding this comment.
Clean, well-scoped fix. The approach — clone _mwCtx.headers before applyMiddlewareRequestHeaders / processMiddlewareHeaders mutate it, then thread the preserved set into the App Route handler to rebuild the request — is correct and minimally invasive.
Verified:
buildRequestHeadersFromMiddlewareResponseis idempotent over the preserved header set (it reads onlyx-middleware-request-*/x-middleware-override-headersfrom the snapshot), so theclone()path inwrapRequestis safe — calling it on already-modified headers produces identical results.executeAppRouteHandleris only called from the generated RSC entry, which serves both dev and prod App Router. No dev/prod parity gap.- The dev server (
index.ts) already hadmiddlewareRequestHeadersfor Pages Router; for App Router dev, middleware context is forwarded viax-vinext-mw-ctxto the RSC entry, hitting the same new code path. - Test passes:
vp test run tests/app-router.test.ts -t "middleware request header overrides also apply to App Route"— green.
Two non-blocking observations:
-
rebuildRequestWithHeaderscopies allRequestInitproperties (cache,credentials,integrity,keepalive,mode,redirect,referrer,referrerPolicy). On Cloudflare Workers, some of these are unsupported on theRequestconstructor and may throw instead of being silently ignored (unlike Node). Worth a quick Workers smoke test to confirm, or defensively omit the properties that Workers doesn't support. Not a blocker — the existing test fixture covers the Node/dev path, and Workers may simply ignore them. -
Minor inefficiency on
clone():wrapRequest(target.clone())(line 204) re-runsbuildRequestHeadersFromMiddlewareResponseon the already-rebuilt request. The result is correct (idempotent), but the clone already carries the right headers, so the rebuild is redundant work. Negligible cost, just noting it.
Test fixture and integration test are well-structured — covering both access paths (request.headers and headers()) with header deletion + injection is exactly the right shape.
| init.duplex = "half"; | ||
| } | ||
|
|
||
| return new Request(input.url, init); |
There was a problem hiding this comment.
Nit: On Cloudflare Workers, Request constructor doesn't support all standard RequestInit properties — mode, credentials, integrity, and keepalive can throw in workerd. Since this code runs in the RSC entry (which also runs on Workers in prod), consider either:
- Wrapping the constructor in a try/catch that falls back to a minimal init
- Only copying the properties that Workers supports (
method,headers,redirect,signal,body,duplex)
Not a blocker since the existing deploy pipeline + smoke tests should catch this if it's an issue, but worth keeping in mind.
| const wrapRequest = (input: Request): NextRequest => { | ||
| const requestHeaders = options.middlewareHeaders | ||
| ? buildRequestHeadersFromMiddlewareResponse(input.headers, options.middlewareHeaders) | ||
| : null; |
There was a problem hiding this comment.
Nit: When clone() is called, wrapRequest(target.clone()) re-runs buildRequestHeadersFromMiddlewareResponse on the cloned request that already has the middleware-modified headers. It's idempotent (the function reads x-middleware-request-* from the preserved snapshot, not from baseHeaders), so this is correct — just redundant work. Not worth optimizing unless clone is hot-pathed.
|
Review posted. Here's a summary: PR #809 — Approved The fix correctly addresses the inconsistency where App Route handlers received pre-middleware What it does:
Verified:
Two non-blocking inline comments posted:
|
…bjects App Route handlers received a tracked NextRequest built from the original request object even when middleware had supplied request-header overrides. That meant headers() saw the middleware-modified view via ALS context, but request.headers inside the route handler still exposed the original Authorization/Cookie headers. Fix: - preserve a clone of the middleware response headers before internal x-middleware-* headers are stripped for response safety - thread that preserved header set into the typed App Route execution module - rebuild the underlying Request with buildRequestHeadersFromMiddlewareResponse() before constructing the tracked NextRequest Also adds an App Route fixture and integration test proving that request.headers and headers() now agree after middleware deletes credential headers and injects a trusted internal header.
69b1cd6 to
cfcfeb8
Compare
|
/bigbonk Rebased onto latest main and resolved the conflict cleanly. Focused integration coverage still passes locally for all three middleware override cases: App page, App route, and mixed Pages fallback. Please re-review. |
commit: |
|
There was a problem hiding this comment.
Clean, well-targeted fix. The data flow is correct:
_mwCtx.requestHeaders = new Headers(_mwCtx.headers)captures the middleware response headers beforeapplyMiddlewareRequestHeaders/processMiddlewareHeadersstrip thex-middleware-*entries- The preserved snapshot is threaded through
executeAppRouteHandler→runAppRouteHandler→createTrackedAppRouteRequest buildRequestHeadersFromMiddlewareResponsereadsx-middleware-override-headersandx-middleware-request-*from the snapshot to reconstruct the intended request headers, thenrebuildRequestWithHeaderscreates a newRequestwith those headers before wrapping inNextRequest
This correctly closes the gap where headers() (via ALS) already reflected middleware mutations but request.headers in App Route handlers did not.
Verified: Test passes — vp test run tests/app-router.test.ts -t "middleware request header overrides also apply to App Route" is green. The fixture covers both access paths (request.headers.get(...) and (await headers()).get(...)) with header deletion + injection, which is the right shape.
Agreeing with the two non-blocking observations from the prior review:
-
rebuildRequestWithHeaderscopiesmode/credentials/integrity/keepalive— these may throw on workerd'sRequestconstructor. The deploy previews and smoke tests should catch this, but a defensive subset (method,headers,redirect,signal,body,duplex) would be safer. Worth a follow-up if smoke tests surface issues. -
clone()redundancy —wrapRequest(target.clone())at line 204 re-runsbuildRequestHeadersFromMiddlewareResponseon the already-rebuilt request. Idempotent and correct, just unnecessary work. Not worth blocking on.
No other concerns. Test fixture and middleware matcher update are well-structured.
|
Reviewed and approved PR #809. The fix correctly addresses the inconsistency where App Route handlers' |
Summary
App Route handlers now receive a request object rebuilt from middleware request-header overrides, so
request.headersandheaders()agree after middleware mutations.Details
Middleware request-header overrides were already applied to the
next/headersALS context, soheaders()reflected the middleware-modified request state. But App Route handlers still received a trackedNextRequestbuilt from the original request object, sorequest.headersexposed the pre-middleware Authorization/Cookie headers.This change:
x-middleware-*headers are stripped for response safetyRequestwithbuildRequestHeadersFromMiddlewareResponse()before constructing the trackedNextRequestTests
Adds an App Route fixture and integration test proving that after middleware deletes credential headers and injects
x-from-middleware, both:request.headersheaders()see the same middleware-modified values.