mirror of
https://github.com/withastro/astro.git
synced 2025-02-03 22:29:08 -05:00
feat: implement the next(payload)
feature for rerouting
This commit is contained in:
parent
242ba26103
commit
dbb47d62eb
12 changed files with 137 additions and 10 deletions
|
@ -2613,6 +2613,11 @@ export interface APIContext<
|
|||
*/
|
||||
redirect: AstroSharedContext['redirect'];
|
||||
|
||||
/**
|
||||
* TODO: docs
|
||||
*/
|
||||
reroute: AstroSharedContext['reroute'];
|
||||
|
||||
/**
|
||||
* An object that middlewares can use to store extra information related to the request.
|
||||
*
|
||||
|
|
|
@ -43,13 +43,17 @@ import { AstroError, AstroErrorData } from '../errors/index.js';
|
|||
export async function callMiddleware(
|
||||
onRequest: MiddlewareHandler,
|
||||
apiContext: APIContext,
|
||||
responseFunction: (reroutePayload?: ReroutePayload) => Promise<Response> | Response
|
||||
responseFunction: (
|
||||
apiContext: APIContext,
|
||||
reroutePayload?: ReroutePayload
|
||||
) => Promise<Response> | Response
|
||||
): Promise<Response> {
|
||||
let nextCalled = false;
|
||||
let responseFunctionPromise: Promise<Response> | Response | undefined = undefined;
|
||||
const next: MiddlewareNext = async (payload) => {
|
||||
nextCalled = true;
|
||||
responseFunctionPromise = responseFunction(payload);
|
||||
// We need to pass the APIContext pass to `callMiddleware` because it can be mutated across middleware functions
|
||||
responseFunctionPromise = responseFunction(apiContext, payload);
|
||||
return responseFunctionPromise;
|
||||
};
|
||||
|
||||
|
|
|
@ -30,6 +30,8 @@ export type CreateContext = {
|
|||
* A list of locales that are supported by the user
|
||||
*/
|
||||
userDefinedLocales?: string[];
|
||||
|
||||
site?: URL;
|
||||
};
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
import type { APIContext, MiddlewareHandler, ReroutePayload } from '../../@types/astro.js';
|
||||
import { defineMiddleware } from './index.js';
|
||||
import { AstroCookies } from '../cookies/cookies.js';
|
||||
|
||||
// From SvelteKit: https://github.com/sveltejs/kit/blob/master/packages/kit/src/exports/hooks/sequence.js
|
||||
/**
|
||||
|
@ -10,12 +11,16 @@ export function sequence(...handlers: MiddlewareHandler[]): MiddlewareHandler {
|
|||
const filtered = handlers.filter((h) => !!h);
|
||||
const length = filtered.length;
|
||||
if (!length) {
|
||||
return defineMiddleware((context, next) => {
|
||||
return defineMiddleware((_context, next) => {
|
||||
return next();
|
||||
});
|
||||
}
|
||||
|
||||
return defineMiddleware((context, next) => {
|
||||
/**
|
||||
* This variable is used to carry the rerouting payload across middleware functions.
|
||||
*/
|
||||
let carriedPayload: ReroutePayload | undefined = undefined;
|
||||
return applyHandle(0, context);
|
||||
|
||||
function applyHandle(i: number, handleContext: APIContext) {
|
||||
|
@ -25,9 +30,27 @@ export function sequence(...handlers: MiddlewareHandler[]): MiddlewareHandler {
|
|||
// 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: ReroutePayload) => {
|
||||
if (i < length - 1) {
|
||||
if (payload) {
|
||||
let newRequest;
|
||||
if (payload instanceof Request) {
|
||||
newRequest = payload;
|
||||
} else if (payload instanceof URL) {
|
||||
newRequest = new Request(payload, handleContext.request);
|
||||
} else {
|
||||
handleContext.request;
|
||||
newRequest = new Request(
|
||||
new URL(payload, handleContext.url.origin),
|
||||
handleContext.request
|
||||
);
|
||||
}
|
||||
carriedPayload = payload;
|
||||
handleContext.request = newRequest;
|
||||
handleContext.url = new URL(newRequest.url);
|
||||
handleContext.cookies = new AstroCookies(newRequest);
|
||||
}
|
||||
return applyHandle(i + 1, handleContext);
|
||||
} else {
|
||||
return next(payload);
|
||||
return next(payload ?? carriedPayload);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
|
|
|
@ -111,7 +111,7 @@ export class RenderContext {
|
|||
statusText: 'Loop Detected',
|
||||
});
|
||||
}
|
||||
const lastNext: MiddlewareNext = async (payload) => {
|
||||
const lastNext = async (ctx: APIContext, payload?: ReroutePayload) => {
|
||||
if (payload) {
|
||||
if (this.pipeline.manifest.reroutingEnabled) {
|
||||
try {
|
||||
|
@ -135,7 +135,7 @@ export class RenderContext {
|
|||
}
|
||||
switch (this.routeData.type) {
|
||||
case 'endpoint':
|
||||
return renderEndpoint(componentInstance as any, apiContext, serverLike, logger);
|
||||
return renderEndpoint(componentInstance as any, ctx, serverLike, logger);
|
||||
case 'redirect':
|
||||
return renderRedirect(this);
|
||||
case 'page': {
|
||||
|
@ -174,9 +174,7 @@ export class RenderContext {
|
|||
}
|
||||
};
|
||||
|
||||
const response = this.isRerouting
|
||||
? await lastNext()
|
||||
: await callMiddleware(middleware, apiContext, lastNext);
|
||||
const response = await callMiddleware(middleware, apiContext, lastNext);
|
||||
if (response.headers.get(ROUTE_TYPE_HEADER)) {
|
||||
response.headers.delete(ROUTE_TYPE_HEADER);
|
||||
}
|
||||
|
|
|
@ -4,5 +4,6 @@ import { defineConfig } from 'astro/config';
|
|||
export default defineConfig({
|
||||
experimental: {
|
||||
rerouting: true
|
||||
}
|
||||
},
|
||||
site: "https://example.com"
|
||||
});
|
||||
|
|
29
packages/astro/test/fixtures/reroute/src/middleware.js
vendored
Normal file
29
packages/astro/test/fixtures/reroute/src/middleware.js
vendored
Normal file
|
@ -0,0 +1,29 @@
|
|||
import { sequence } from 'astro:middleware';
|
||||
|
||||
export const first = async (context, next) => {
|
||||
if (context.url.pathname.includes('/auth')) {
|
||||
}
|
||||
|
||||
return next();
|
||||
};
|
||||
|
||||
export const second = async (context, next) => {
|
||||
if (context.url.pathname.includes('/auth')) {
|
||||
if (context.url.pathname.includes('/auth/dashboard')) {
|
||||
return await context.reroute('/');
|
||||
}
|
||||
if (context.url.pathname.includes('/auth/base')) {
|
||||
return await next('/');
|
||||
}
|
||||
}
|
||||
return next();
|
||||
};
|
||||
|
||||
export const third = async (context, next) => {
|
||||
if (context.url.pathname.startsWith('/')) {
|
||||
context.locals.auth = 'Third function called';
|
||||
}
|
||||
return next();
|
||||
};
|
||||
|
||||
export const onRequest = sequence(first, second, third);
|
10
packages/astro/test/fixtures/reroute/src/pages/auth/base.astro
vendored
Normal file
10
packages/astro/test/fixtures/reroute/src/pages/auth/base.astro
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
---
|
||||
<html>
|
||||
<head>
|
||||
<title>Base</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Base</h1>
|
||||
</body>
|
||||
</html>
|
10
packages/astro/test/fixtures/reroute/src/pages/auth/dashboard.astro
vendored
Normal file
10
packages/astro/test/fixtures/reroute/src/pages/auth/dashboard.astro
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
---
|
||||
<html>
|
||||
<head>
|
||||
<title>Dashboard</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Dashboard</h1>
|
||||
</body>
|
||||
</html>
|
10
packages/astro/test/fixtures/reroute/src/pages/auth/settings.astro
vendored
Normal file
10
packages/astro/test/fixtures/reroute/src/pages/auth/settings.astro
vendored
Normal file
|
@ -0,0 +1,10 @@
|
|||
---
|
||||
---
|
||||
<html>
|
||||
<head>
|
||||
<title>Settings</title>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Settings</h1>
|
||||
</body>
|
||||
</html>
|
|
@ -1,4 +1,5 @@
|
|||
---
|
||||
const auth = Astro.locals.auth;
|
||||
---
|
||||
<html>
|
||||
<head>
|
||||
|
@ -6,5 +7,6 @@
|
|||
</head>
|
||||
<body>
|
||||
<h1>Index</h1>
|
||||
{auth ? <p>Called auth</p>: ""}
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -165,3 +165,36 @@ describe('SSR reroute', () => {
|
|||
assert.equal($('h1').text(), 'Index');
|
||||
});
|
||||
});
|
||||
|
||||
describe('Middleware', () => {
|
||||
/** @type {import('./test-utils').Fixture} */
|
||||
let fixture;
|
||||
let devServer;
|
||||
|
||||
before(async () => {
|
||||
fixture = await loadFixture({
|
||||
root: './fixtures/reroute/',
|
||||
});
|
||||
devServer = await fixture.startDevServer();
|
||||
});
|
||||
|
||||
after(async () => {
|
||||
await devServer.stop();
|
||||
});
|
||||
|
||||
it('should render a locals populated in the third middleware function, because we use next("/")', async () => {
|
||||
const html = await fixture.fetch('/auth/base').then((res) => res.text());
|
||||
const $ = cheerioLoad(html);
|
||||
|
||||
assert.equal($('h1').text(), 'Index');
|
||||
assert.equal($('p').text(), 'Called auth');
|
||||
});
|
||||
|
||||
it('should NOT render locals populated in the third middleware function, because we use ctx.reroute("/")', async () => {
|
||||
const html = await fixture.fetch('/auth/dashboard').then((res) => res.text());
|
||||
const $ = cheerioLoad(html);
|
||||
|
||||
assert.equal($('h1').text(), 'Index');
|
||||
assert.equal($('p').text(), '');
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Add table
Reference in a new issue