mirror of
https://github.com/cloudflare/vinext.git
synced 2026-09-14 19:04:59 +08:00
2b67523e9e
## Summary - Drop RFC 7540 §8.1.2.1 pseudo-headers (`:method`, `:authority`, `:path`, `:scheme`) before constructing WHATWG `Headers` from Node's `req.headers`. - Fixed at every `Headers`-from-`req.headers` site: the Pages Router middleware snapshot (index.ts), the edge API request builder (api-handler.ts), and the shared `appendWebHeader` helper used by `nodeToWebRequest` (prod-server.ts). - Also excluded pseudo-headers from the `reportRequestError` header metadata in both api-handler.ts and dev-server.ts so error reports reflect real request headers only. - Added a regression unit test driving `nodeToWebRequest` with a fake HTTP/2 request. ## Root Cause Over HTTP/2, Node's `http2` server exposes the request pseudo-headers as `:`-prefixed keys on `req.headers`. The WHATWG `Headers` constructor/`append`/`set` reject any header name containing `:` (`TypeError: Headers.set: ":method" is an invalid header name`), so building a `Headers` object directly from `req.headers` threw and returned a 500 on every HTTP/2 request. Per RFC 7540 §8.1.2.1, pseudo-headers must not be treated as regular header fields, so they are stripped at each construction site. ## References - Issue: cloudflare/vinext#2013 - RFC 7540 §8.1.2.1 (HTTP/2 pseudo-header fields) - Node `http2` request `:`-prefixed headers; WHATWG Fetch `Headers` name validation ## Verification - `node -e "new Headers({':method':'GET'})"` → throws (repro). - `CI=true pnpm test tests/http2-pseudo-headers.test.ts` → red before fix, green after. - `CI=true npx vp check` on all changed files → pass (format + lint + types). Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
48 lines
1.6 KiB
TypeScript
48 lines
1.6 KiB
TypeScript
import type { IncomingMessage } from "node:http";
|
|
import { describe, expect, it } from "vitest";
|
|
import { nodeToWebRequest } from "../packages/vinext/src/server/prod-server.js";
|
|
|
|
/**
|
|
* Regression test for #2013.
|
|
*
|
|
* When a request arrives over HTTP/2, Node populates `req.headers` with
|
|
* RFC 7540 §8.1.2.1 pseudo-headers (`:method`, `:authority`, `:path`,
|
|
* `:scheme`). The WHATWG `Headers` constructor/append/set rejects any header
|
|
* name containing `:`, so building a `Headers` object directly from
|
|
* `req.headers` threw `TypeError: ... is an invalid header name` and returned
|
|
* a 500 on every HTTP/2 request. Pseudo-headers must be stripped before a
|
|
* `Headers` object is constructed.
|
|
*/
|
|
describe("HTTP/2 pseudo-header stripping (#2013)", () => {
|
|
function fakeHttp2Request(): IncomingMessage {
|
|
return {
|
|
method: "GET",
|
|
url: "/",
|
|
headers: {
|
|
":method": "GET",
|
|
":authority": "example.com",
|
|
":path": "/",
|
|
":scheme": "https",
|
|
host: "example.com",
|
|
"user-agent": "vitest",
|
|
},
|
|
} as unknown as IncomingMessage;
|
|
}
|
|
|
|
it("does not throw and strips pseudo-headers while keeping real headers", () => {
|
|
const req = fakeHttp2Request();
|
|
let request!: Request;
|
|
expect(() => {
|
|
request = nodeToWebRequest(req, "/");
|
|
}).not.toThrow();
|
|
|
|
// Pseudo-headers must be absent. (Note: `Headers.has(":method")` would
|
|
// itself throw, so enumerate the names instead.)
|
|
const names = [...request.headers.keys()];
|
|
expect(names.some((n) => n.startsWith(":"))).toBe(false);
|
|
|
|
// Real headers must be preserved.
|
|
expect(request.headers.get("user-agent")).toBe("vitest");
|
|
});
|
|
});
|