Skip to content

Commit

Permalink
fix: handle immutable response object (#12106)
Browse files Browse the repository at this point in the history
* fix: handle immutable response object

* changeset
  • Loading branch information
ascorbic authored Oct 3, 2024
1 parent fbe1bc5 commit d3a74da
Show file tree
Hide file tree
Showing 4 changed files with 36 additions and 12 deletions.
5 changes: 5 additions & 0 deletions .changeset/poor-doors-listen.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'astro': patch
---

Handles case where an immutable Response object is returned from an endpoint
20 changes: 15 additions & 5 deletions packages/astro/src/runtime/server/endpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ export async function renderEndpoint(
return new Response(null, { status: 500 });
}

const response = await handler.call(mod, context);
let response = await handler.call(mod, context);

if (!response || response instanceof Response === false) {
throw new AstroError(EndpointDidNotReturnAResponse);
Expand All @@ -59,10 +59,20 @@ export async function renderEndpoint(
// Endpoints explicitly returning 404 or 500 response status should
// NOT be subject to rerouting to 404.astro or 500.astro.
if (REROUTABLE_STATUS_CODES.includes(response.status)) {
// Only `Response.redirect` headers are immutable, therefore a `try..catch` is not necessary.
// Note: `Response.redirect` can only be called with HTTP status codes: 301, 302, 303, 307, 308.
// Source: https://developer.mozilla.org/en-US/docs/Web/API/Response/redirect_static#parameters
response.headers.set(REROUTE_DIRECTIVE_HEADER, 'no');
try {
response.headers.set(REROUTE_DIRECTIVE_HEADER, 'no');
} catch (err) {
// In some cases the response may have immutable headers
// This is the case if, for example, the user directly returns a `fetch` response
// There's no clean way to check if the headers are immutable, so we just catch the error
// Note that response.clone() still has immutable headers!
if((err as Error).message?.includes('immutable')) {
response = new Response(response.body, response);
response.headers.set(REROUTE_DIRECTIVE_HEADER, 'no');
} else {
throw err;
}
}
}

return response;
Expand Down
3 changes: 3 additions & 0 deletions packages/astro/test/fixtures/ssr-api-route/src/pages/fail.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export async function GET({ request }) {
return fetch("https://http.im/status/500", request)
}
20 changes: 13 additions & 7 deletions packages/astro/test/ssr-api-route.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -137,23 +137,29 @@ describe('API routes in SSR', () => {
assert.equal(count, 2, 'Found two separate set-cookie response headers');
});

it('can return an immutable response object', async () => {
const response = await fixture.fetch('/fail');
const text = await response.text();
assert.equal(response.status, 500);
assert.equal(text, '');
});

it('Has valid api context', async () => {
const response = await fixture.fetch('/context/any');
assert.equal(response.status, 200);
const data = await response.json();
assert.equal(data.cookiesExist, true);
assert.equal(data.requestExist, true);
assert.equal(data.redirectExist, true);
assert.equal(data.propsExist, true);
assert.ok(data.cookiesExist);
assert.ok(data.requestExist);
assert.ok(data.redirectExist);
assert.ok(data.propsExist);
assert.deepEqual(data.params, { param: 'any' });
assert.match(data.generator, /^Astro v/);
assert.equal(
assert.ok(
['http://[::1]:4321/blog/context/any', 'http://127.0.0.1:4321/blog/context/any'].includes(
data.url,
),
true,
);
assert.equal(['::1', '127.0.0.1'].includes(data.clientAddress), true);
assert.ok(['::1', '127.0.0.1'].includes(data.clientAddress));
assert.equal(data.site, 'https://mysite.dev/subsite/');
});
});
Expand Down

0 comments on commit d3a74da

Please sign in to comment.