中间件
中间件允许你拦截请求和响应,并在每次要渲染页面或端点时动态注入行为。对于所有预渲染的页面,这个渲染过程发生在构建时,但对于按需渲染的页面,它发生在请求路由时,这使得像 cookie 和请求头等额外的 SSR 功能可用。
中间件还允许你通过修改 locals
对象来设置和共享请求特定的信息,该对象在所有 Astro 组件和 API 端点中都可用。即使此中间件在构建时运行,该对象也可用。
基本用法
标题为“基本用法”的部分-
创建
src/middleware.js|ts
(或者,你也可以创建src/middleware/index.js|ts
。) -
在此文件中,导出一个
onRequest()
函数,该函数可以接收一个context
对象和next()
函数。这不能是默认导出。src/middleware.js export function onRequest (context, next) {// intercept data from a request// optionally, modify the properties in `locals`context.locals.title = "New title";// return a Response or the result of calling `next()`return next();}; -
在任何
.astro
文件中,使用Astro.locals
访问响应数据。src/components/Component.astro ---const data = Astro.locals;---<h1>{data.title}</h1><p>This {data.property} is from middleware.</p>
context
对象
标题为“context 对象”的部分context
对象包含在渲染过程中提供给其他中间件、API 路由和 .astro
路由的信息。
这是传递给 onRequest()
的一个可选参数,其中可能包含 locals
对象以及在渲染期间共享的任何其他属性。例如,context
对象可能包含用于身份验证的 cookie。
在 context.locals
中存储数据
标题为“在 context.locals 中存储数据”的部分context.locals
是一个可以在中间件内部操作的对象。
这个 locals
对象在请求处理过程中被转发,并作为 APIContext
和 AstroGlobal
的一个属性可用。这允许在中间件、API 路由和 .astro
页面之间共享数据。这对于在整个渲染步骤中存储请求特定的数据(例如用户数据)非常有用。
集成可以通过 locals
对象设置属性和提供功能。如果你正在使用集成,请查阅其文档以确保你没有覆盖任何其属性或进行不必要的工作。
你可以在 locals
中存储任何类型的数据:字符串、数字,甚至像函数和映射这样的复杂数据类型。
export function onRequest (context, next) { // intercept data from a request // optionally, modify the properties in `locals` context.locals.user.name = "John Wick"; context.locals.welcomeTitle = () => { return "Welcome back " + locals.user.name; };
// return a Response or the result of calling `next()` return next();};
然后你可以在任何 .astro
文件中使用 Astro.locals
来使用这些信息。
---const title = Astro.locals.welcomeTitle();const orders = Array.from(Astro.locals.orders.entries());const data = Astro.locals;---<h1>{title}</h1><p>This {data.property} is from middleware.</p><ul> {orders.map(order => { return <li>{/* do something with each order */}</li>; })}</ul>
locals
是一个在单个 Astro 路由中存在和消亡的对象;当你的路由页面渲染完成时,locals
将不再存在,并且会创建一个新的。需要在多个页面请求之间持久化的信息必须存储在其他地方。
locals
的值不能在运行时被覆盖。这样做可能会导致用户存储的所有信息被清除。Astro 会执行检查,并在 locals
被覆盖时抛出错误。
示例:隐藏敏感信息
标题为“示例:隐藏敏感信息”的部分下面的示例使用中间件将“PRIVATE INFO”替换为“REDACTED”,以允许你在页面上渲染修改后的 HTML。
export const onRequest = async (context, next) => { const response = await next(); const html = await response.text(); const redactedHtml = html.replaceAll("PRIVATE INFO", "REDACTED");
return new Response(redactedHtml, { status: 200, headers: response.headers });};
中间件类型
标题为“中间件类型”的部分你可以导入并使用工具函数 defineMiddleware()
来利用类型安全。
import { defineMiddleware } from "astro:middleware";
// `context` and `next` are automatically typedexport const onRequest = defineMiddleware((context, next) => {
});
或者,如果你使用 JSDoc 来利用类型安全,你可以使用 MiddlewareHandler
。
/** * @type {import("astro").MiddlewareHandler} */// `context` and `next` are automatically typedexport const onRequest = (context, next) => {
};
要为 Astro.locals
中的信息添加类型,以便在 .astro
文件和中间件代码中获得自动补全功能,请在 env.d.ts
文件中声明一个全局命名空间。
type User = { id: number; name: string;};
declare namespace App { interface Locals { user: User; welcomeTitle: () => string; orders: Map<string, object>; session: import("./lib/server/session").Session | null; }}
然后,在中间件文件中,你就可以利用自动补全和类型安全了。
链式中间件
标题为“链式中间件”的部分可以使用 sequence()
按指定顺序将多个中间件连接起来。
import { sequence } from "astro:middleware";
async function validation(_, next) { console.log("validation request"); const response = await next(); console.log("validation response"); return response;}
async function auth(_, next) { console.log("auth request"); const response = await next(); console.log("auth response"); return response;}
async function greeting(_, next) { console.log("greeting request"); const response = await next(); console.log("greeting response"); return response;}
export const onRequest = sequence(validation, auth, greeting);
这将导致以下控制台输出顺序:
validation requestauth requestgreeting requestgreeting responseauth responsevalidation response
新增于: astro@4.13.0
APIContext
暴露了一个名为 rewrite()
的方法,其工作方式与 Astro.rewrite 相同。
在中间件内部使用 context.rewrite()
来显示不同页面的内容,而无需将访问者重定向到新页面。这将触发一个新的渲染阶段,导致任何中间件重新执行。
import { isLoggedIn } from "~/auth.js"export function onRequest (context, next) { if (!isLoggedIn(context)) { // If the user is not logged in, update the Request to render the `/login` route and // add header to indicate where the user should be sent after a successful login. // Re-execute middleware. return context.rewrite(new Request("/login", { headers: { "x-redirect-to": context.url.pathname } })); }
return next();};
你还可以向 next()
函数传递一个可选的 URL 路径参数来重写当前的 Request
,而不会重新触发新的渲染阶段。重写路径的位置可以作为字符串、URL 或 Request
提供。
import { isLoggedIn } from "~/auth.js"export function onRequest (context, next) { if (!isLoggedIn(context)) { // If the user is not logged in, update the Request to render the `/login` route and // add header to indicate where the user should be sent after a successful login. // Return a new `context` to any following middlewares. return next(new Request("/login", { headers: { "x-redirect-to": context.url.pathname } })); }
return next();};
next()
函数接受与Astro.rewrite()
函数相同的负载。重写路径的位置可以作为字符串、URL 或 Request
提供。
当你通过 sequence() 链接了多个中间件函数时,向 next()
提交一个路径将就地重写 Request
,并且中间件不会再次执行。链中的下一个中间件函数将接收到带有更新后 context
的新 Request
。
使用此签名调用 next()
将使用旧的 ctx.request
创建一个新的 Request
对象。这意味着,无论是在重写之前还是之后,尝试消费 Request.body
都会抛出一个运行时错误。这个错误经常在使用 HTML 表单的 Astro Actions 中出现。在这些情况下,我们建议使用 Astro.rewrite()
从你的 Astro 模板中处理重写,而不是使用中间件。
// Current URL is https://example.com/blog
// First middleware functionasync function first(_, next) { console.log(context.url.pathname) // this will log "/blog" // Rewrite to a new route, the homepage // Return updated `context` which is passed to next function return next("/")}
// Current URL is still https://example.com/blog
// Second middleware functionasync function second(context, next) { // Receives updated `context` console.log(context.url.pathname) // this will log "/" return next()}
export const onRequest = sequence(first, second);
错误页面
标题为“错误页面”的部分中间件将尝试为所有按需渲染的页面运行,即使找不到匹配的路由。这包括 Astro 的默认(空白)404 页面和任何自定义 404 页面。但是,是否运行该代码取决于适配器。一些适配器可能会提供特定于平台的错误页面。
中间件还将在提供 500 错误页面(包括自定义 500 页面)之前尝试运行,除非服务器错误发生在中间件本身的执行过程中。如果你的中间件没有成功运行,那么你将无法访问 Astro.locals
来渲染你的 500 页面。