0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2025-01-27 22:19:04 -05:00

fix: avoid response rewrite inside the dev server (#11477)

* fix: avoid response rewrite inside the dev server

* breakdown logic of reroute and rewrite
This commit is contained in:
Emanuele Stoppa 2024-07-17 17:01:07 +01:00 committed by GitHub
parent 1df7c8489e
commit 7e9c4a134c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 68 additions and 6 deletions

View file

@ -0,0 +1,5 @@
---
'astro': patch
---
Fixes an issue where the development server was emitting a 404 status code when the user uses a rewrite that emits a 200 status code.

View file

@ -22,6 +22,14 @@ export const ASTRO_VERSION = process.env.PACKAGE_VERSION ?? 'development';
*/
export const REROUTE_DIRECTIVE_HEADER = 'X-Astro-Reroute';
/**
* Header and value that are attached to a Response object when a **user rewrite** occurs.
*
* This metadata is used to determine the origin of a Response. If a rewrite has occurred, it should be prioritised over other logic.
*/
export const REWRITE_DIRECTIVE_HEADER_KEY = 'X-Astro-Rewrite';
export const REWRITE_DIRECTIVE_HEADER_VALUE = 'yes';
/**
* The name for the header used to help i18n middleware, which only needs to act on "page" and "fallback" route types.
*/

View file

@ -25,6 +25,8 @@ import {
clientAddressSymbol,
clientLocalsSymbol,
responseSentSymbol,
REWRITE_DIRECTIVE_HEADER_KEY,
REWRITE_DIRECTIVE_HEADER_VALUE,
} from './constants.js';
import { AstroCookies, attachCookiesToResponse } from './cookies/index.js';
import { getCookiesFromResponse } from './cookies/response.js';
@ -184,13 +186,12 @@ export class RenderContext {
// Signal to the i18n middleware to maybe act on this response
response.headers.set(ROUTE_TYPE_HEADER, 'page');
// Signal to the error-page-rerouting infra to let this response pass through to avoid loops
if (
this.routeData.route === '/404' ||
this.routeData.route === '/500' ||
this.isRewriting
) {
if (this.routeData.route === '/404' || this.routeData.route === '/500') {
response.headers.set(REROUTE_DIRECTIVE_HEADER, 'no');
}
if (this.isRewriting) {
response.headers.set(REWRITE_DIRECTIVE_HEADER_KEY, REWRITE_DIRECTIVE_HEADER_VALUE);
}
break;
}
case 'fallback': {
@ -381,6 +382,7 @@ export class RenderContext {
}
#astroPagePartial?: Omit<AstroGlobal, 'props' | 'self' | 'slots'>;
/**
* The Astro global is sourced in 3 different phases:
* - **Static**: `.generator` and `.glob` is printed by the compiler, instantiated once per process per astro file
@ -512,6 +514,7 @@ export class RenderContext {
* So, it is computed and saved here on creation of the first APIContext and reused for later ones.
*/
#currentLocale: APIContext['currentLocale'];
computeCurrentLocale() {
const {
url,
@ -537,6 +540,7 @@ export class RenderContext {
}
#preferredLocale: APIContext['preferredLocale'];
computePreferredLocale() {
const {
pipeline: { i18n },
@ -547,6 +551,7 @@ export class RenderContext {
}
#preferredLocaleList: APIContext['preferredLocaleList'];
computePreferredLocaleList() {
const {
pipeline: { i18n },

View file

@ -4,6 +4,7 @@ import {
DEFAULT_404_COMPONENT,
REROUTE_DIRECTIVE_HEADER,
clientLocalsSymbol,
REWRITE_DIRECTIVE_HEADER_KEY,
} from '../core/constants.js';
import { AstroErrorData, isAstroError } from '../core/errors/index.js';
import { req } from '../core/messages.js';
@ -218,8 +219,12 @@ export async function handleRoute({
});
let response;
let isReroute = false;
let isRewrite = false;
try {
response = await renderContext.render(mod);
isReroute = response.headers.has(REROUTE_DIRECTIVE_HEADER);
isRewrite = response.headers.has(REWRITE_DIRECTIVE_HEADER_KEY);
} catch (err: any) {
const custom500 = getCustom500Route(manifestData);
if (!custom500) {
@ -264,7 +269,10 @@ export async function handleRoute({
}
// We remove the internally-used header before we send the response to the user agent.
if (response.headers.has(REROUTE_DIRECTIVE_HEADER)) {
if (isReroute) {
response.headers.delete(REROUTE_DIRECTIVE_HEADER);
}
if (isRewrite) {
response.headers.delete(REROUTE_DIRECTIVE_HEADER);
}
@ -272,6 +280,14 @@ export async function handleRoute({
await writeWebResponse(incomingResponse, response);
return;
}
// This check is important in case of rewrites.
// A route can start with a 404 code, then the rewrite kicks in and can return a 200 status code
if (isRewrite) {
await writeSSRResult(request, response, incomingResponse);
return;
}
// We are in a recursion, and it's possible that this function is called itself with a status code
// By default, the status code passed via parameters is computed by the matched route.
//

View file

@ -520,6 +520,34 @@ describe('Runtime error in SSR, custom 500', () => {
});
});
describe('Runtime error in dev, custom 500', () => {
/** @type {import('./test-utils').Fixture} */
let fixture;
let devServer;
before(async () => {
fixture = await loadFixture({
root: './fixtures/rewrite-i18n-manual-routing/',
});
devServer = await fixture.startDevServer();
});
after(async () => {
await devServer.stop();
});
it('should return a status 200 when rewriting from the middleware to the homepage', async () => {
const response = await fixture.fetch('/reroute');
assert.equal(response.status, 200);
const html = await response.text();
const $ = cheerioLoad(html);
assert.equal($('h1').text(), 'Expected http status of index page is 200');
});
});
describe('Runtime error in SSR, custom 500', () => {
/** @type {import('./test-utils').Fixture} */
let fixture;