From f4ad607a4f3c92eb9f3a3e3b169c160bb4e40a59 Mon Sep 17 00:00:00 2001 From: Jakob Linskeseder Date: Tue, 11 Jul 2023 11:09:07 +0200 Subject: [PATCH] fix: don't fail on immutable headers when using native fetch According to the `fetch()`-specification, a header can have an `immutable`-guard which will throw a `TypeError` if the header is changed: https://fetch.spec.whatwg.org/#headers-class When using `fetch()`, the spec requires the response header to be `immutable` (see step 12/4): https://fetch.spec.whatwg.org/#fetch-method This is implemented in undici (used by Node.js for native fetch() under the hood): https://github.com/nodejs/undici/blob/22bdbd8c7820035276b4e876daccef513c29f5c4/lib/fetch/headers.js#L234-L239 --- .changeset/violet-pans-scream.md | 5 +++++ packages/kit/src/runtime/server/respond.js | 7 +++++++ .../test/apps/basics/src/routes/immutable-headers/+page.js | 4 ++++ .../apps/basics/src/routes/immutable-headers/+server.js | 7 +++++++ packages/kit/test/apps/basics/test/server.test.js | 6 ++++++ 5 files changed, 29 insertions(+) create mode 100644 .changeset/violet-pans-scream.md create mode 100644 packages/kit/test/apps/basics/src/routes/immutable-headers/+page.js create mode 100644 packages/kit/test/apps/basics/src/routes/immutable-headers/+server.js diff --git a/.changeset/violet-pans-scream.md b/.changeset/violet-pans-scream.md new file mode 100644 index 000000000000..1605265abd13 --- /dev/null +++ b/.changeset/violet-pans-scream.md @@ -0,0 +1,5 @@ +--- +'@sveltejs/kit': patch +--- + +fix: gracefully handle server endpoints that return `Response`s with immutable `Headers` diff --git a/packages/kit/src/runtime/server/respond.js b/packages/kit/src/runtime/server/respond.js index f97baa2e6526..c6e02da8c9bc 100644 --- a/packages/kit/src/runtime/server/respond.js +++ b/packages/kit/src/runtime/server/respond.js @@ -440,6 +440,13 @@ export async function respond(request, options, manifest, state) { ?.split(',') ?.map((v) => v.trim().toLowerCase()); if (!(vary?.includes('accept') || vary?.includes('*'))) { + // the returned response might have immutable headers, + // so we have to clone them before trying to mutate them + response = new Response(response.body, { + status: response.status, + statusText: response.statusText, + headers: new Headers(response.headers) + }); response.headers.append('Vary', 'Accept'); } } diff --git a/packages/kit/test/apps/basics/src/routes/immutable-headers/+page.js b/packages/kit/test/apps/basics/src/routes/immutable-headers/+page.js new file mode 100644 index 000000000000..d62912c27ead --- /dev/null +++ b/packages/kit/test/apps/basics/src/routes/immutable-headers/+page.js @@ -0,0 +1,4 @@ +// This file will trigger the vary-header code-path in `src/runtime/server/respond.js` +export function load() { + return { foo: 'bar' }; +} diff --git a/packages/kit/test/apps/basics/src/routes/immutable-headers/+server.js b/packages/kit/test/apps/basics/src/routes/immutable-headers/+server.js new file mode 100644 index 000000000000..24e6f4ad88e2 --- /dev/null +++ b/packages/kit/test/apps/basics/src/routes/immutable-headers/+server.js @@ -0,0 +1,7 @@ +export const GET = () => { + const response = new Response('foo'); + // this simulates immutable Response Headers, like those returned by undici + Object.defineProperty(response.headers, 'append', { value: null }); + Object.defineProperty(response.headers, 'set', { value: null }); + return response; +}; diff --git a/packages/kit/test/apps/basics/test/server.test.js b/packages/kit/test/apps/basics/test/server.test.js index 87d0a5fe4071..e614050a55af 100644 --- a/packages/kit/test/apps/basics/test/server.test.js +++ b/packages/kit/test/apps/basics/test/server.test.js @@ -569,4 +569,10 @@ test.describe('Miscellaneous', () => { const headers = response.headers(); expect(headers['cache-control'] || '').not.toContain('immutable'); }); + + test('handles responses with immutable headers', async ({ request }) => { + const response = await request.get('/immutable-headers'); + expect(response.status()).toBe(200); + expect(await response.text()).toBe('foo'); + }); });