fix: handle requests for double slash in dev (#12733)

* fix: handle requests for double slash in dev

* Handle base

* Oops

* Snapshots

* Move redirect out of routing

---------

Co-authored-by: Emanuele Stoppa <my.burning@gmail.com>
This commit is contained in:
Matt Kane 2025-01-16 12:40:49 +00:00 committed by GitHub
parent 9b0a624c8c
commit bbf1d8894e
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 39 additions and 4 deletions

View file

@ -0,0 +1,5 @@
---
'astro': patch
---
Fixes a bug that caused the dev server to return an error if requesting "//"

View file

@ -1,5 +1,5 @@
export type RedirectTemplate = {
from: string;
from?: string;
location: string | URL;
status: number;
};
@ -14,6 +14,6 @@ export function redirectTemplate({ status, location, from }: RedirectTemplate) {
<meta name="robots" content="noindex">
<link rel="canonical" href="${location}">
<body>
<a href="${location}">Redirecting from <code>${from}</code> to <code>${location}</code></a>
<a href="${location}">Redirecting ${from ? `from <code>${from}</code> ` : ''}to <code>${location}</code></a>
</body>`;
}

View file

@ -7,7 +7,9 @@ import { appendForwardSlash } from '@astrojs/internal-helpers/path';
import { bold } from 'kleur/colors';
import type { Logger } from '../core/logger/core.js';
import notFoundTemplate, { subpathNotUsedTemplate } from '../template/4xx.js';
import { writeHtmlResponse } from './response.js';
import { writeHtmlResponse, writeRedirectResponse } from './response.js';
const manySlashes = /\/{2,}$/;
export function baseMiddleware(
settings: AstroSettings,
@ -21,7 +23,10 @@ export function baseMiddleware(
return function devBaseMiddleware(req, res, next) {
const url = req.url!;
if (manySlashes.test(url)) {
const destination = url.replace(manySlashes, '/');
return writeRedirectResponse(res, 301, destination);
}
let pathname: string;
try {
pathname = decodeURI(new URL(url, 'http://localhost').pathname);

View file

@ -7,6 +7,7 @@ import { Readable } from 'node:stream';
import { getSetCookiesFromResponse } from '../core/cookies/index.js';
import { getViteErrorPayload } from '../core/errors/dev/index.js';
import notFoundTemplate from '../template/4xx.js';
import { redirectTemplate } from '../core/routing/3xx.js';
export async function handle404Response(
origin: string,
@ -53,6 +54,17 @@ export function writeHtmlResponse(res: http.ServerResponse, statusCode: number,
res.end();
}
export function writeRedirectResponse(res: http.ServerResponse, statusCode: number, location: string) {
const html = redirectTemplate({ status: statusCode, location });
res.writeHead(statusCode, {
Location: location,
'Content-Type': 'text/html',
'Content-Length': Buffer.byteLength(html, 'utf-8'),
});
res.write(html);
res.end();
}
export async function writeWebResponse(res: http.ServerResponse, webResponse: Response) {
const { status, headers, body, statusText } = webResponse;

View file

@ -48,6 +48,19 @@ describe('Development Routing', () => {
assert.equal(response.status, 200);
});
it('redirects when loading double slash', async () => {
const response = await fixture.fetch('//', { redirect: 'manual' });
assert.equal(response.status, 301);
assert.equal(response.headers.get('Location'), '/');
});
it('redirects when loading multiple slashes', async () => {
const response = await fixture.fetch('/////', { redirect: 'manual' });
assert.equal(response.status, 301);
assert.equal(response.headers.get('Location'), '/');
});
it('404 when loading invalid dynamic route', async () => {
const response = await fixture.fetch('/2');
assert.equal(response.status, 404);