From 121b2586736bef4a7f2bdeef6709f841d99b69ca Mon Sep 17 00:00:00 2001 From: Brendan Allan Date: Sun, 23 Nov 2025 12:56:44 +0800 Subject: [PATCH 1/4] write event.response.headers to Responses returned from server functions & api routes --- packages/start/src/server/handler.ts | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/packages/start/src/server/handler.ts b/packages/start/src/server/handler.ts index 090c5484a..dd1285c56 100644 --- a/packages/start/src/server/handler.ts +++ b/packages/start/src/server/handler.ts @@ -38,7 +38,10 @@ export function createBaseHandler( if (pathname.startsWith(serverFunctionTest)) { const serverFnResponse = await handleServerFunction(e); - if (serverFnResponse instanceof Response) return serverFnResponse; + if (serverFnResponse instanceof Response) { + writeEventResponseHeaders(serverFnResponse.headers) + return serverFnResponse; + } return new Response(serverFnResponse as any, { headers: e.res.headers, @@ -56,7 +59,11 @@ export function createBaseHandler( // @ts-expect-error sharedConfig.context = { event }; const res = await fn!(event); - if (res !== undefined) return res; + if (res !== undefined) { + if(res instanceof Response) writeEventResponseHeaders(res.headers) + + return res; + } if (event.request.method !== "GET") { throw new Error( `API handler for ${event.request.method} "${event.request.url}" did not return a response.`, @@ -222,3 +229,11 @@ function handleStreamCompleteRedirect(context: PageEvent) { to && write(``); }; } + +function writeEventResponseHeaders(target: Headers) { + const event = getRequestEvent()!; + + for(const [name, value] of event.response.headers) { + if(!target.has(name)) target.set(name, value); + } +} From 8992c20cb08c4b058e7332e5c3f6ff70e7e48972 Mon Sep 17 00:00:00 2001 From: Brendan Allan Date: Sun, 23 Nov 2025 13:12:34 +0800 Subject: [PATCH 2/4] clone response on redirects --- packages/start/src/server/handler.ts | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/packages/start/src/server/handler.ts b/packages/start/src/server/handler.ts index dd1285c56..e3dec04fb 100644 --- a/packages/start/src/server/handler.ts +++ b/packages/start/src/server/handler.ts @@ -38,10 +38,8 @@ export function createBaseHandler( if (pathname.startsWith(serverFunctionTest)) { const serverFnResponse = await handleServerFunction(e); - if (serverFnResponse instanceof Response) { - writeEventResponseHeaders(serverFnResponse.headers) - return serverFnResponse; - } + if (serverFnResponse instanceof Response) + return produceResponseWithEventHeaders(serverFnResponse); return new Response(serverFnResponse as any, { headers: e.res.headers, @@ -60,7 +58,7 @@ export function createBaseHandler( sharedConfig.context = { event }; const res = await fn!(event); if (res !== undefined) { - if(res instanceof Response) writeEventResponseHeaders(res.headers) + if(res instanceof Response) return produceResponseWithEventHeaders(res) return res; } @@ -230,10 +228,23 @@ function handleStreamCompleteRedirect(context: PageEvent) { }; } -function writeEventResponseHeaders(target: Headers) { +function produceResponseWithEventHeaders(res: Response) { const event = getRequestEvent()!; + let ret = res; + + // Response.redirect returns an immutable value, so we clone on any redirect just in case + if(300 <= res.status && res.status < 400) { + ret = new Response(res.body, { + status: res.status, + statusText: res.statusText, + headers: Object.fromEntries(res.headers.entries()) + }); + } + for(const [name, value] of event.response.headers) { - if(!target.has(name)) target.set(name, value); + if(!ret.headers.has(name)) ret.headers.set(name, value); } + + return ret } From 124a78df4bb1049d5576b82a1b69045261638a2a Mon Sep 17 00:00:00 2001 From: Brendan Allan Date: Sun, 23 Nov 2025 18:12:35 +0800 Subject: [PATCH 3/4] align header overwriting with h3 behaviour --- packages/start/src/server/handler.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/start/src/server/handler.ts b/packages/start/src/server/handler.ts index e3dec04fb..1e975ab01 100644 --- a/packages/start/src/server/handler.ts +++ b/packages/start/src/server/handler.ts @@ -135,7 +135,7 @@ export function createBaseHandler( const app = new H3(); - app.use(handler); + app.use(handler); return app; } @@ -243,7 +243,7 @@ function produceResponseWithEventHeaders(res: Response) { } for(const [name, value] of event.response.headers) { - if(!ret.headers.has(name)) ret.headers.set(name, value); + ret.headers.set(name, value); } return ret From 96ad77535b24c5ae18710448c1df63b0d1f1b39b Mon Sep 17 00:00:00 2001 From: Brendan Allan Date: Sun, 23 Nov 2025 18:12:43 +0800 Subject: [PATCH 4/4] add header merging tests with and without redirect --- apps/tests/src/e2e/api-call.test.ts | 14 ++++++++++ apps/tests/src/routes/api/header-merging.ts | 29 +++++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 apps/tests/src/routes/api/header-merging.ts diff --git a/apps/tests/src/e2e/api-call.test.ts b/apps/tests/src/e2e/api-call.test.ts index dea2403c3..07dbd2a82 100644 --- a/apps/tests/src/e2e/api-call.test.ts +++ b/apps/tests/src/e2e/api-call.test.ts @@ -5,4 +5,18 @@ test.describe("api calls", () => { const response = await fetch("http://localhost:3000/api/text-plain"); expect(await response.text()).toBe("test"); }); + + test("should include headers from both event and returned request", async () => { + const okResp = await fetch("http://localhost:3000/api/header-merging?status=ok"); + expect(okResp.headers.get("Set-Cookie")).toBeTruthy(); + expect(okResp.headers.get("x-event-header")).toBe("value"); + expect(okResp.headers.get("x-return-header")).toBe("value"); + expect(okResp.headers.get("x-shared-header")).toBe("event"); + + const redirectResp = await fetch("http://localhost:3000/api/header-merging?status=redirect", { redirect: "manual" }); + expect(redirectResp.headers.get("Set-Cookie")).toBeTruthy(); + expect(redirectResp.headers.get("x-event-header")).toBe("value"); + expect(redirectResp.headers.get("x-return-header")).toBe("value"); + expect(redirectResp.headers.get("x-shared-header")).toBe("event"); + }) }); diff --git a/apps/tests/src/routes/api/header-merging.ts b/apps/tests/src/routes/api/header-merging.ts new file mode 100644 index 000000000..0b85a77ea --- /dev/null +++ b/apps/tests/src/routes/api/header-merging.ts @@ -0,0 +1,29 @@ +import { getRequestURL, setHeader, useSession } from "@solidjs/start/http"; + +export async function GET() { + const url = getRequestURL(); + + const s = await useSession({ password: "0".repeat(32) }); + await s.update(d => ({count: (d.count || 0) + 1})) + + setHeader("x-event-header", "value"); + setHeader("x-shared-header", "event"); + + if(url.searchParams.get("status") === "redirect") { + return new Response(null, { + status: 301, + headers: { + location: "http://::/abc", + "x-return-header": "value", + "x-shared-header": "return" + } + }) + } else { + return new Response(null, { + headers: { + "x-return-header": "value", + "x-shared-header": "return" + } + }) + } +}