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" + } + }) + } +} diff --git a/packages/start/src/server/handler.ts b/packages/start/src/server/handler.ts index 58d69aa87..cdcacc600 100644 --- a/packages/start/src/server/handler.ts +++ b/packages/start/src/server/handler.ts @@ -38,7 +38,8 @@ export function createBaseHandler( if (pathname.startsWith(serverFunctionTest)) { const serverFnResponse = await handleServerFunction(e); - if (serverFnResponse instanceof Response) return serverFnResponse; + if (serverFnResponse instanceof Response) + return produceResponseWithEventHeaders(serverFnResponse); return new Response(serverFnResponse as any, { headers: e.res.headers, @@ -56,7 +57,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) return produceResponseWithEventHeaders(res) + + return res; + } if (event.request.method !== "GET") { throw new Error( `API handler for ${event.request.method} "${event.request.url}" did not return a response.`, @@ -130,7 +135,7 @@ export function createBaseHandler( const app = new H3(); - app.use(handler); + app.use(handler); return app; } @@ -222,3 +227,24 @@ function handleStreamCompleteRedirect(context: PageEvent) { to && write(``); }; } + +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) { + ret.headers.set(name, value); + } + + return ret +}