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

fix(routing): compute APIContext.params when rewriting using next (#12031)

* fix(routing): compute `APIContext.params` when rewriting using `next`

* fix(routing): compute `APIContext.params` when rewriting using `next`
This commit is contained in:
Emanuele Stoppa 2024-09-19 13:57:47 +01:00 committed by GitHub
parent d3bd673392
commit 8c0cae6d1b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
12 changed files with 90 additions and 37 deletions

View file

@ -0,0 +1,5 @@
---
'astro': patch
---
Fixes a bug where the rewrite via `next(/*..*/)` inside a middleware didn't compute the new `APIContext.params`

View file

@ -90,11 +90,7 @@ export class AppPipeline extends Pipeline {
return module.page();
}
async tryRewrite(
payload: RewritePayload,
request: Request,
_sourceRoute: RouteData,
): Promise<TryRewriteResult> {
async tryRewrite(payload: RewritePayload, request: Request): Promise<TryRewriteResult> {
const { newUrl, pathname, routeData } = findRouteToRewrite({
payload,
request,

View file

@ -89,13 +89,8 @@ export abstract class Pipeline {
*
* @param {RewritePayload} rewritePayload The payload provided by the user
* @param {Request} request The original request
* @param {RouteData} sourceRoute The original `RouteData`
*/
abstract tryRewrite(
rewritePayload: RewritePayload,
request: Request,
sourceRoute: RouteData,
): Promise<TryRewriteResult>;
abstract tryRewrite(rewritePayload: RewritePayload, request: Request): Promise<TryRewriteResult>;
/**
* Tells the pipeline how to retrieve a component give a `RouteData`

View file

@ -92,6 +92,10 @@ export class BuildPipeline extends Pipeline {
);
}
getRoutes(): RouteData[] {
return this.options.manifest.routes;
}
static create({
internals,
manifest,
@ -286,11 +290,7 @@ export class BuildPipeline extends Pipeline {
return module.page();
}
async tryRewrite(
payload: RewritePayload,
request: Request,
_sourceRoute: RouteData,
): Promise<TryRewriteResult> {
async tryRewrite(payload: RewritePayload, request: Request): Promise<TryRewriteResult> {
const { routeData, pathname, newUrl } = findRouteToRewrite({
payload,
request,

View file

@ -1,6 +1,8 @@
import type { APIContext, MiddlewareHandler, RewritePayload } from '../../@types/astro.js';
import { AstroCookies } from '../cookies/cookies.js';
import { defineMiddleware } from './index.js';
import { getParams, type Pipeline } from '../render/index.js';
import { apiContextRoutesSymbol } from '../render-context.js';
// From SvelteKit: https://github.com/sveltejs/kit/blob/master/packages/kit/src/exports/hooks/sequence.js
/**
@ -15,7 +17,6 @@ export function sequence(...handlers: MiddlewareHandler[]): MiddlewareHandler {
return next();
});
}
return defineMiddleware((context, next) => {
/**
* This variable is used to carry the rerouting payload across middleware functions.
@ -28,7 +29,7 @@ export function sequence(...handlers: MiddlewareHandler[]): MiddlewareHandler {
// @ts-expect-error
// SAFETY: Usually `next` always returns something in user land, but in `sequence` we are actually
// doing a loop over all the `next` functions, and eventually we call the last `next` that returns the `Response`.
const result = handle(handleContext, async (payload: RewritePayload) => {
const result = handle(handleContext, async (payload?: RewritePayload) => {
if (i < length - 1) {
if (payload) {
let newRequest;
@ -42,10 +43,16 @@ export function sequence(...handlers: MiddlewareHandler[]): MiddlewareHandler {
handleContext.request,
);
}
const pipeline: Pipeline = Reflect.get(handleContext, apiContextRoutesSymbol);
const { routeData, pathname } = await pipeline.tryRewrite(
payload,
handleContext.request,
);
carriedPayload = payload;
handleContext.request = newRequest;
handleContext.url = new URL(newRequest.url);
handleContext.cookies = new AstroCookies(newRequest);
handleContext.params = getParams(routeData, pathname);
}
return applyHandle(i + 1, handleContext);
} else {

View file

@ -37,14 +37,13 @@ import { sequence } from './middleware/index.js';
import { renderRedirect } from './redirects/render.js';
import { type Pipeline, Slots, getParams, getProps } from './render/index.js';
export const apiContextRoutesSymbol = Symbol.for('context.routes');
/**
* Each request is rendered using a `RenderContext`.
* It contains data unique to each request. It is responsible for executing middleware, calling endpoints, and rendering the page by gathering necessary data from a `Pipeline`.
*/
export class RenderContext {
// The first route that this instance of the context attempts to render
originalRoute: RouteData;
private constructor(
readonly pipeline: Pipeline,
public locals: App.Locals,
@ -57,9 +56,7 @@ export class RenderContext {
public params = getParams(routeData, pathname),
protected url = new URL(request.url),
public props: Props = {},
) {
this.originalRoute = routeData;
}
) {}
/**
* A flag that tells the render content if the rewriting was triggered
@ -142,14 +139,24 @@ export class RenderContext {
if (payload) {
pipeline.logger.debug('router', 'Called rewriting to:', payload);
// we intentionally let the error bubble up
const { routeData, componentInstance: newComponent } = await pipeline.tryRewrite(
payload,
this.request,
this.originalRoute,
);
const {
routeData,
componentInstance: newComponent,
pathname,
newUrl,
} = await pipeline.tryRewrite(payload, this.request);
this.routeData = routeData;
componentInstance = newComponent;
if (payload instanceof Request) {
this.request = payload;
} else {
this.request = this.#copyRequest(newUrl, this.request);
}
this.isRewriting = true;
this.url = new URL(this.request.url);
this.cookies = new AstroCookies(this.request);
this.params = getParams(routeData, pathname);
this.pathname = pathname;
this.status = 200;
}
let response: Response;
@ -218,6 +225,7 @@ export class RenderContext {
const context = this.createActionAPIContext();
const redirect = (path: string, status = 302) =>
new Response(null, { status, headers: { Location: path } });
Reflect.set(context, apiContextRoutesSymbol, this.pipeline);
return Object.assign(context, {
props,
@ -237,7 +245,6 @@ export class RenderContext {
const { routeData, componentInstance, newUrl, pathname } = await this.pipeline.tryRewrite(
reroutePayload,
this.request,
this.originalRoute,
);
this.routeData = routeData;
if (reroutePayload instanceof Request) {

View file

@ -199,11 +199,7 @@ export class DevPipeline extends Pipeline {
}
}
async tryRewrite(
payload: RewritePayload,
request: Request,
_sourceRoute: RouteData,
): Promise<TryRewriteResult> {
async tryRewrite(payload: RewritePayload, request: Request): Promise<TryRewriteResult> {
if (!this.manifestData) {
throw new Error('Missing manifest data. This is an internal error, please file an issue.');
}

View file

@ -1,4 +1,5 @@
import { sequence } from 'astro:middleware';
import {defineMiddleware} from "astro/middleware";
let contextReroute = false;
@ -22,16 +23,23 @@ export const second = async (context, next) => {
if (context.url.pathname.includes('/auth/params')) {
return next('/?foo=bar');
}
if (context.url.pathname.includes('/auth/astro-params')) {
return next('/auth/1234');
}
}
return next();
};
export const third = async (context, next) => {
export const third = defineMiddleware(async (context, next) => {
// just making sure that we are testing the change in context coming from `next()`
if (context.url.pathname.startsWith('/') && contextReroute === false) {
context.locals.auth = 'Third function called';
}
if (context.params?.id === '1234') {
context.locals.auth = 'Params changed'
}
return next();
};
});
export const onRequest = sequence(first, second, third);

View file

@ -0,0 +1,23 @@
---
export function getStaticPaths( ) {
return [{
params: {
id: "1234"
}
}]
}
const { id } = Astro.params;
const auth = Astro.locals.auth;
---
<html>
<head>
<title>Index with params</title>
</head>
<body>
<h1>Index with params</h1>
<p id="params">Param: {id}</p>
<p id="locals">Locals: {auth}</p>
</body>
</html>

View file

@ -0,0 +1,3 @@
---
---

View file

@ -1,4 +1,6 @@
---
const { id } = Astro.params;
const auth = Astro.locals.auth;
---
<html>
<head>
@ -6,5 +8,7 @@
</head>
<body>
<h1>Index with params</h1>
<p id="params">Param: {id}</p>
<p id="locals">Locals: {auth}</p>
</body>
</html>

View file

@ -388,6 +388,15 @@ describe('Middleware', () => {
assert.match($('h1').text(), /Index/);
});
it('should render correctly compute the new params next("/auth/1234")', async () => {
const html = await fixture.fetch('/auth/astro-params').then((res) => res.text());
const $ = cheerioLoad(html);
assert.match($('h1').text(), /Index with params/);
assert.match($('#params').text(), /Param: 1234/);
assert.match($('#locals').text(), /Locals: Params changed/);
});
});
describe('Middleware with custom 404.astro and 500.astro', () => {