0
Fork 0
mirror of https://github.com/withastro/astro.git synced 2025-02-17 22:44:24 -05:00

fix(rewrite): purge old data when rewriting (#11207)

* fix(rewrite): purge old data when rewriting

* remove logs

* Update .changeset/fuzzy-eggs-kneel.md

Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>

---------

Co-authored-by: Sarah Rainsberger <sarah@rainsberger.ca>
This commit is contained in:
Emanuele Stoppa 2024-06-11 12:58:59 +01:00 committed by GitHub
parent bd849a4f62
commit 7d9aac376c
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 168 additions and 60 deletions

View file

@ -0,0 +1,5 @@
---
'astro': patch
---
Fixes an issue in the rewriting logic where old data was not purged during the rewrite flow. This caused some false positives when checking the validity of URL path names during the rendering phase.

View file

@ -13,7 +13,7 @@ import {
createModuleScriptElement,
createStylesheetElementSet,
} from '../core/render/ssr-element.js';
import { default404Page } from '../core/routing/astro-designed-error-pages.js';
import {default404Page, DEFAULT_404_ROUTE} from '../core/routing/astro-designed-error-pages.js';
export class ContainerPipeline extends Pipeline {
/**
@ -70,30 +70,29 @@ export class ContainerPipeline extends Pipeline {
return { links, styles, scripts };
}
async tryRewrite(rewritePayload: RewritePayload): Promise<[RouteData, ComponentInstance]> {
async tryRewrite(payload: RewritePayload, request: Request): Promise<[RouteData, ComponentInstance, URL]> {
let foundRoute: RouteData | undefined;
// options.manifest is the actual type that contains the information
let finalUrl: URL | undefined = undefined;
for (const route of this.manifest.routes) {
const routeData = route.routeData;
if (rewritePayload instanceof URL) {
if (routeData.pattern.test(rewritePayload.pathname)) {
foundRoute = routeData;
break;
if (payload instanceof URL) {
finalUrl = payload;
} else if (payload instanceof Request) {
finalUrl = new URL(payload.url);
} else {
finalUrl = new URL(payload, new URL(request.url).origin);
}
} else if (rewritePayload instanceof Request) {
const url = new URL(rewritePayload.url);
if (routeData.pattern.test(url.pathname)) {
foundRoute = routeData;
if (route.routeData.pattern.test(decodeURI(finalUrl.pathname))) {
foundRoute = route.routeData;
break;
}
} else if (routeData.pattern.test(decodeURI(rewritePayload))) {
foundRoute = routeData;
} else if (finalUrl.pathname === '/404') {
foundRoute = DEFAULT_404_ROUTE;
break;
}
}
if (foundRoute) {
if (foundRoute && finalUrl) {
const componentInstance = await this.getComponentByRoute(foundRoute);
return [foundRoute, componentInstance];
return [foundRoute, componentInstance, finalUrl];
} else {
throw new AstroError(RouteNotFound);
}

View file

@ -87,12 +87,11 @@ export class AppPipeline extends Pipeline {
payload: RewritePayload,
request: Request,
sourceRoute: RouteData
): Promise<[RouteData, ComponentInstance]> {
): Promise<[RouteData, ComponentInstance, URL]> {
let foundRoute;
for (const route of this.#manifestData!.routes) {
let finalUrl: URL | undefined = undefined;
for (const route of this.#manifestData!.routes) {
if (payload instanceof URL) {
finalUrl = payload;
} else if (payload instanceof Request) {
@ -110,13 +109,13 @@ export class AppPipeline extends Pipeline {
}
}
if (foundRoute) {
if (foundRoute && finalUrl) {
if (foundRoute.pathname === '/404') {
const componentInstance = this.rewriteKnownRoute(foundRoute.pathname, sourceRoute);
return [foundRoute, componentInstance];
return [foundRoute, componentInstance, finalUrl];
} else {
const componentInstance = await this.getComponentByRoute(foundRoute);
return [foundRoute, componentInstance];
return [foundRoute, componentInstance, finalUrl];
}
}
throw new AstroError({

View file

@ -88,7 +88,7 @@ export abstract class Pipeline {
rewritePayload: RewritePayload,
request: Request,
sourceRoute: RouteData
): Promise<[RouteData, ComponentInstance]>;
): Promise<[RouteData, ComponentInstance, URL]>;
/**
* Tells the pipeline how to retrieve a component give a `RouteData`

View file

@ -281,12 +281,11 @@ export class BuildPipeline extends Pipeline {
payload: RewritePayload,
request: Request,
sourceRoute: RouteData
): Promise<[RouteData, ComponentInstance]> {
): Promise<[RouteData, ComponentInstance, URL]> {
let foundRoute: RouteData | undefined;
// options.manifest is the actual type that contains the information
for (const route of this.options.manifest.routes) {
let finalUrl: URL | undefined = undefined;
for (const route of this.options.manifest.routes) {
if (payload instanceof URL) {
finalUrl = payload;
} else if (payload instanceof Request) {
@ -303,13 +302,13 @@ export class BuildPipeline extends Pipeline {
break;
}
}
if (foundRoute) {
if (foundRoute && finalUrl) {
if (foundRoute.pathname === '/404') {
const componentInstance = await this.rewriteKnownRoute(foundRoute.pathname, sourceRoute);
return [foundRoute, componentInstance];
return [foundRoute, componentInstance, finalUrl];
} else {
const componentInstance = await this.getComponentByRoute(foundRoute);
return [foundRoute, componentInstance];
return [foundRoute, componentInstance, finalUrl];
}
} else {
throw new AstroError({

View file

@ -20,7 +20,6 @@ import { renderEndpoint } from '../runtime/server/endpoint.js';
import { renderPage } from '../runtime/server/index.js';
import {
ASTRO_VERSION,
DEFAULT_404_COMPONENT,
REROUTE_DIRECTIVE_HEADER,
ROUTE_TYPE_HEADER,
clientAddressSymbol,
@ -46,7 +45,7 @@ export class RenderContext {
readonly pipeline: Pipeline,
public locals: App.Locals,
readonly middleware: MiddlewareHandler,
readonly pathname: string,
public pathname: string,
public request: Request,
public routeData: RouteData,
public status: number,
@ -103,16 +102,17 @@ export class RenderContext {
componentInstance: ComponentInstance | undefined,
slots: Record<string, any> = {}
): Promise<Response> {
const { cookies, middleware, pathname, pipeline } = this;
const { logger, routeCache, serverLike, streaming } = pipeline;
const { cookies, middleware, pipeline } = this;
const { logger, serverLike, streaming } = pipeline;
const props =
Object.keys(this.props).length > 0
? this.props
: await getProps({
mod: componentInstance,
routeData: this.routeData,
routeCache,
pathname,
routeCache: this.pipeline.routeCache,
pathname: this.pathname,
logger,
serverLike,
});
@ -222,7 +222,7 @@ export class RenderContext {
const rewrite = async (reroutePayload: RewritePayload) => {
pipeline.logger.debug('router', 'Called rewriting to:', reroutePayload);
const [routeData, component] = await pipeline.tryRewrite(
const [routeData, component, newURL] = await pipeline.tryRewrite(
reroutePayload,
this.request,
this.originalRoute
@ -231,15 +231,13 @@ export class RenderContext {
if (reroutePayload instanceof Request) {
this.request = reroutePayload;
} else {
this.request = this.#copyRequest(
new URL(routeData.pathname ?? routeData.route, this.url.origin),
this.request
);
this.request = this.#copyRequest(newURL, this.request);
}
this.url = new URL(this.request.url);
this.url = newURL;
this.cookies = new AstroCookies(this.request);
this.params = getParams(routeData, url.toString());
this.params = getParams(routeData, this.url.pathname);
this.isRewriting = true;
this.pathname = this.url.pathname;
return await this.render(component);
};
@ -359,11 +357,17 @@ export class RenderContext {
props: Record<string, any>,
slotValues: Record<string, any> | null
): AstroGlobal {
let astroPagePartial;
// During rewriting, we must recompute the Astro global, because we need to purge the previous params/props/etc.
if (this.isRewriting) {
astroPagePartial = this.#astroPagePartial = this.createAstroPagePartial(result, astroStaticPartial);
} else {
// Create page partial with static partial so they can be cached together.
const astroPagePartial = (this.#astroPagePartial ??= this.createAstroPagePartial(
astroPagePartial = this.#astroPagePartial ??= this.createAstroPagePartial(
result,
astroStaticPartial
));
);
}
// Create component-level partials. `Astro.self` is added by the compiler.
const astroComponentPartial = { props, self: null };
@ -410,7 +414,7 @@ export class RenderContext {
const rewrite = async (reroutePayload: RewritePayload) => {
pipeline.logger.debug('router', 'Calling rewrite: ', reroutePayload);
const [routeData, component] = await pipeline.tryRewrite(
const [routeData, component, newURL] = await pipeline.tryRewrite(
reroutePayload,
this.request,
this.originalRoute
@ -419,14 +423,12 @@ export class RenderContext {
if (reroutePayload instanceof Request) {
this.request = reroutePayload;
} else {
this.request = this.#copyRequest(
new URL(routeData.pathname ?? routeData.route, this.url.origin),
this.request
);
this.request = this.#copyRequest(newURL, this.request);
}
this.url = new URL(this.request.url);
this.cookies = new AstroCookies(this.request);
this.params = getParams(routeData, url.toString());
this.params = getParams(routeData, this.url.pathname);
this.pathname = this.url.pathname;
this.isRewriting = true;
return await this.render(component);
};

View file

@ -195,15 +195,14 @@ export class DevPipeline extends Pipeline {
payload: RewritePayload,
request: Request,
sourceRoute: RouteData
): Promise<[RouteData, ComponentInstance]> {
): Promise<[RouteData, ComponentInstance, URL]> {
let foundRoute;
if (!this.manifestData) {
throw new Error('Missing manifest data. This is an internal error, please file an issue.');
}
for (const route of this.manifestData.routes) {
let finalUrl: URL | undefined = undefined;
for (const route of this.manifestData.routes) {
if (payload instanceof URL) {
finalUrl = payload;
} else if (payload instanceof Request) {
@ -221,13 +220,13 @@ export class DevPipeline extends Pipeline {
}
}
if (foundRoute) {
if (foundRoute && finalUrl) {
if (foundRoute.pathname === '/404') {
const componentInstance = this.rewriteKnownRoute(foundRoute.pathname, sourceRoute);
return [foundRoute, componentInstance];
return [foundRoute, componentInstance, finalUrl];
} else {
const componentInstance = await this.getComponentByRoute(foundRoute);
return [foundRoute, componentInstance];
return [foundRoute, componentInstance, finalUrl];
}
} else {
throw new AstroError({

View file

@ -0,0 +1,10 @@
import { defineConfig } from 'astro/config';
// https://astro.build/config
export default defineConfig({
output: "server",
experimental: {
rewriting: true
},
site: "https://example.com"
});

View file

@ -0,0 +1,8 @@
{
"name": "@test/reroute-server",
"version": "0.0.0",
"private": true,
"dependencies": {
"astro": "workspace:*"
}
}

View file

@ -0,0 +1,14 @@
---
const { slug } = Astro.params;
console.log("is it here???", Astro.params)
export const prerender = false;
---
<html>
<head>
<title>Title</title>
</head>
<body>
<h1>Title</h1>
<p>{slug}</p>
</body>
</html>

View file

@ -0,0 +1,13 @@
---
return Astro.rewrite('/some-slug/title')
export const prerender = false
---
<html>
<head>
<title>Index</title>
</head>
<body>
<h1>Index</h1>
</body>
</html>

View file

@ -62,6 +62,31 @@ describe('Dev reroute', () => {
});
});
describe('Dev rewrite, hybrid/server', () => {
/** @type {import('./test-utils').Fixture} */
let fixture;
let devServer;
before(async () => {
fixture = await loadFixture({
root: './fixtures/rewrite-server/',
});
devServer = await fixture.startDevServer();
});
after(async () => {
await devServer.stop();
});
it('should rewrite the [slug]/title ', async () => {
const html = await fixture.fetch('/').then((res) => res.text());
const $ = cheerioLoad(html);
assert.match($('h1').text(), /Title/);
assert.match($('p').text(), /some-slug/);
});
});
describe('Build reroute', () => {
/** @type {import('./test-utils').Fixture} */
let fixture;
@ -220,6 +245,35 @@ describe('SSR reroute', () => {
});
});
describe('SSR rewrite, hybrid/server', () => {
/** @type {import('./test-utils').Fixture} */
let fixture;
let app;
before(async () => {
fixture = await loadFixture({
root: './fixtures/rewrite-server/',
output: 'server',
adapter: testAdapter(),
});
await fixture.build();
app = await fixture.loadTestAdapterApp();
});
it('should rewrite the [slug]/title ', async () => {
const request = new Request('http://example.com/');
const response = await app.render(request);
const html = await response.text();
const $ = cheerioLoad(html);
console.log(html);
assert.match($('h1').text(), /Title/);
assert.match($('p').text(), /some-slug/);
});
});
describe('Middleware', () => {
/** @type {import('./test-utils').Fixture} */
let fixture;

6
pnpm-lock.yaml generated
View file

@ -3487,6 +3487,12 @@ importers:
specifier: workspace:*
version: link:../../..
packages/astro/test/fixtures/rewrite-server:
dependencies:
astro:
specifier: workspace:*
version: link:../../..
packages/astro/test/fixtures/root-srcdir-css:
dependencies:
astro: