0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2025-02-10 22:38:53 -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'; 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. * 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, clientAddressSymbol,
clientLocalsSymbol, clientLocalsSymbol,
responseSentSymbol, responseSentSymbol,
REWRITE_DIRECTIVE_HEADER_KEY,
REWRITE_DIRECTIVE_HEADER_VALUE,
} from './constants.js'; } from './constants.js';
import { AstroCookies, attachCookiesToResponse } from './cookies/index.js'; import { AstroCookies, attachCookiesToResponse } from './cookies/index.js';
import { getCookiesFromResponse } from './cookies/response.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 // Signal to the i18n middleware to maybe act on this response
response.headers.set(ROUTE_TYPE_HEADER, 'page'); response.headers.set(ROUTE_TYPE_HEADER, 'page');
// Signal to the error-page-rerouting infra to let this response pass through to avoid loops // Signal to the error-page-rerouting infra to let this response pass through to avoid loops
if ( if (this.routeData.route === '/404' || this.routeData.route === '/500') {
this.routeData.route === '/404' ||
this.routeData.route === '/500' ||
this.isRewriting
) {
response.headers.set(REROUTE_DIRECTIVE_HEADER, 'no'); response.headers.set(REROUTE_DIRECTIVE_HEADER, 'no');
} }
if (this.isRewriting) {
response.headers.set(REWRITE_DIRECTIVE_HEADER_KEY, REWRITE_DIRECTIVE_HEADER_VALUE);
}
break; break;
} }
case 'fallback': { case 'fallback': {
@ -381,6 +382,7 @@ export class RenderContext {
} }
#astroPagePartial?: Omit<AstroGlobal, 'props' | 'self' | 'slots'>; #astroPagePartial?: Omit<AstroGlobal, 'props' | 'self' | 'slots'>;
/** /**
* The Astro global is sourced in 3 different phases: * 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 * - **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. * So, it is computed and saved here on creation of the first APIContext and reused for later ones.
*/ */
#currentLocale: APIContext['currentLocale']; #currentLocale: APIContext['currentLocale'];
computeCurrentLocale() { computeCurrentLocale() {
const { const {
url, url,
@ -537,6 +540,7 @@ export class RenderContext {
} }
#preferredLocale: APIContext['preferredLocale']; #preferredLocale: APIContext['preferredLocale'];
computePreferredLocale() { computePreferredLocale() {
const { const {
pipeline: { i18n }, pipeline: { i18n },
@ -547,6 +551,7 @@ export class RenderContext {
} }
#preferredLocaleList: APIContext['preferredLocaleList']; #preferredLocaleList: APIContext['preferredLocaleList'];
computePreferredLocaleList() { computePreferredLocaleList() {
const { const {
pipeline: { i18n }, pipeline: { i18n },

View file

@ -4,6 +4,7 @@ import {
DEFAULT_404_COMPONENT, DEFAULT_404_COMPONENT,
REROUTE_DIRECTIVE_HEADER, REROUTE_DIRECTIVE_HEADER,
clientLocalsSymbol, clientLocalsSymbol,
REWRITE_DIRECTIVE_HEADER_KEY,
} from '../core/constants.js'; } from '../core/constants.js';
import { AstroErrorData, isAstroError } from '../core/errors/index.js'; import { AstroErrorData, isAstroError } from '../core/errors/index.js';
import { req } from '../core/messages.js'; import { req } from '../core/messages.js';
@ -218,8 +219,12 @@ export async function handleRoute({
}); });
let response; let response;
let isReroute = false;
let isRewrite = false;
try { try {
response = await renderContext.render(mod); response = await renderContext.render(mod);
isReroute = response.headers.has(REROUTE_DIRECTIVE_HEADER);
isRewrite = response.headers.has(REWRITE_DIRECTIVE_HEADER_KEY);
} catch (err: any) { } catch (err: any) {
const custom500 = getCustom500Route(manifestData); const custom500 = getCustom500Route(manifestData);
if (!custom500) { 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. // 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); response.headers.delete(REROUTE_DIRECTIVE_HEADER);
} }
@ -272,6 +280,14 @@ export async function handleRoute({
await writeWebResponse(incomingResponse, response); await writeWebResponse(incomingResponse, response);
return; 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 // 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. // 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', () => { describe('Runtime error in SSR, custom 500', () => {
/** @type {import('./test-utils').Fixture} */ /** @type {import('./test-utils').Fixture} */
let fixture; let fixture;