Skip to content

Commit

Permalink
fix: avoid response rewrite inside the dev server (#11477)
Browse files Browse the repository at this point in the history
* fix: avoid response rewrite inside the dev server

* breakdown logic of reroute and rewrite
  • Loading branch information
ematipico authored Jul 17, 2024
1 parent 1df7c84 commit 7e9c4a1
Show file tree
Hide file tree
Showing 5 changed files with 68 additions and 6 deletions.
5 changes: 5 additions & 0 deletions .changeset/chilled-impalas-dance.md
Original file line number Diff line number Diff line change
@@ -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.
8 changes: 8 additions & 0 deletions packages/astro/src/core/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down
15 changes: 10 additions & 5 deletions packages/astro/src/core/render-context.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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': {
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -537,6 +540,7 @@ export class RenderContext {
}

#preferredLocale: APIContext['preferredLocale'];

computePreferredLocale() {
const {
pipeline: { i18n },
Expand All @@ -547,6 +551,7 @@ export class RenderContext {
}

#preferredLocaleList: APIContext['preferredLocaleList'];

computePreferredLocaleList() {
const {
pipeline: { i18n },
Expand Down
18 changes: 17 additions & 1 deletion packages/astro/src/vite-plugin-astro-server/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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';
Expand Down Expand Up @@ -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) {
Expand Down Expand Up @@ -264,14 +269,25 @@ 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);
}

if (route.type === 'endpoint') {
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.
//
Expand Down
28 changes: 28 additions & 0 deletions packages/astro/test/rewrite.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down

0 comments on commit 7e9c4a1

Please sign in to comment.