fix(rerouting): check that the new route is different (#8648)

* fix(rerouting): check that the new route is different

* add tests

* changeset grammar
This commit is contained in:
Arsh 2023-09-28 17:16:15 +00:00 committed by GitHub
parent eada8ab8fa
commit cfd895d877
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
22 changed files with 152 additions and 2 deletions

View file

@ -0,0 +1,5 @@
---
'astro': patch
---
Fixed an issue where a response with status code 404 led to an endless loop of implicit rerouting in dev mode.

View file

@ -220,6 +220,7 @@ export async function handleRoute({
let response = await pipeline.renderRoute(renderContext, mod); let response = await pipeline.renderRoute(renderContext, mod);
if (response.status === 404 && has404Route(manifestData)) { if (response.status === 404 && has404Route(manifestData)) {
const fourOhFourRoute = await matchRoute('/404', manifestData, pipeline); const fourOhFourRoute = await matchRoute('/404', manifestData, pipeline);
if (fourOhFourRoute?.route !== options.route)
return handleRoute({ return handleRoute({
...options, ...options,
matchedRoute: fourOhFourRoute, matchedRoute: fourOhFourRoute,
@ -342,6 +343,6 @@ function getStatus(matchedRoute?: MatchedRoute): 404 | 500 | undefined {
if (matchedRoute.route.route === '/500') return 500; if (matchedRoute.route.route === '/500') return 500;
} }
function has404Route(manifest: ManifestData): RouteData | undefined { function has404Route(manifest: ManifestData): boolean {
return manifest.routes.find((route) => route.route === '/404'); return manifest.routes.some((route) => route.route === '/404');
} }

View file

@ -0,0 +1,57 @@
import { expect } from 'chai';
import { loadFixture } from './test-utils.js';
for (const caseNumber of [ 1, 2, 3, 4 ]) {
describe(`Custom 404 with implicit rerouting - Case #${caseNumber}`, () => {
/** @type Awaited<ReturnType<typeof loadFixture>> */
let fixture;
/** @type Awaited<ReturnType<typeof fixture['startDevServer']>> */
let devServer
before(async () => {
fixture = await loadFixture({
root: `./fixtures/custom-404-loop-case-${caseNumber}/`,
site: 'http://example.com'
});
devServer = await fixture.startDevServer();
});
// sanity check
it('dev server handles normal requests', async () => {
const resPromise = fixture.fetch('/');
const result = await withTimeout(resPromise, 1000);
expect(result).to.not.equal(timeout);
expect(result.status).to.equal(200);
});
it('dev server stays responsive', async () => {
const resPromise = fixture.fetch('/alvsibdlvjks');
const result = await withTimeout(resPromise, 1000);
expect(result).to.not.equal(timeout);
expect(result.status).to.equal(404);
});
after(async () => {
await devServer.stop();
});
});
}
/***** UTILITY FUNCTIONS *****/
const timeout = Symbol("timeout")
/** @template Res */
function withTimeout(
/** @type Promise<Res> */
responsePromise,
/** @type number */
timeLimit
) {
/** @type Promise<typeof timeout> */
const timeoutPromise = new Promise(resolve => setTimeout(() => resolve(timeout), timeLimit))
return Promise.race([ responsePromise, timeoutPromise ]);
}

View file

@ -0,0 +1 @@
export default {}

View file

@ -0,0 +1,8 @@
{
"name": "@test/custom-404-loop-case-1",
"version": "0.0.0",
"private": true,
"dependencies": {
"astro": "workspace:*"
}
}

View file

@ -0,0 +1,4 @@
---
Astro.response.status = 404
---
<p>four oh four route</p>

View file

@ -0,0 +1 @@
<p>all good here... or is it?</p>

View file

@ -0,0 +1 @@
export default {}

View file

@ -0,0 +1,8 @@
{
"name": "@test/custom-404-loop-case-2",
"version": "0.0.0",
"private": true,
"dependencies": {
"astro": "workspace:*"
}
}

View file

@ -0,0 +1,4 @@
---
return new Response(null, { status: 404 })
---
<p>four oh four route</p>

View file

@ -0,0 +1 @@
<p>all good here... or is it?</p>

View file

@ -0,0 +1 @@
export default {}

View file

@ -0,0 +1,8 @@
{
"name": "@test/custom-404-loop-case-3",
"version": "0.0.0",
"private": true,
"dependencies": {
"astro": "workspace:*"
}
}

View file

@ -0,0 +1,7 @@
export async function onRequest(ctx, next) {
if (ctx.url.pathname !== '/') {
const response = await next()
return new Response(response.body, { ...response, status: 404 })
}
return next();
}

View file

@ -0,0 +1 @@
<p>four oh four route</p>

View file

@ -0,0 +1 @@
<p>all good here... or is it?</p>

View file

@ -0,0 +1 @@
export default {}

View file

@ -0,0 +1,8 @@
{
"name": "@test/custom-404-loop-case-4",
"version": "0.0.0",
"private": true,
"dependencies": {
"astro": "workspace:*"
}
}

View file

@ -0,0 +1,6 @@
export function onRequest(ctx, next) {
if (ctx.url.pathname !== '/') {
return new Response(null, { status: 404 });
}
return next();
}

View file

@ -0,0 +1 @@
<p>four oh four route</p>

View file

@ -0,0 +1 @@
<p>all good here... or is it?</p>

View file

@ -2561,6 +2561,30 @@ importers:
specifier: workspace:* specifier: workspace:*
version: link:../../.. version: link:../../..
packages/astro/test/fixtures/custom-404-loop-case-1:
dependencies:
astro:
specifier: workspace:*
version: link:../../..
packages/astro/test/fixtures/custom-404-loop-case-2:
dependencies:
astro:
specifier: workspace:*
version: link:../../..
packages/astro/test/fixtures/custom-404-loop-case-3:
dependencies:
astro:
specifier: workspace:*
version: link:../../..
packages/astro/test/fixtures/custom-404-loop-case-4:
dependencies:
astro:
specifier: workspace:*
version: link:../../..
packages/astro/test/fixtures/custom-404-md: packages/astro/test/fixtures/custom-404-md:
dependencies: dependencies:
astro: astro: