使用您最喜欢的AI助手总结文档,并引用此页面和AI提供商
此页面的内容已使用 AI 翻译。
查看英文原文的最新版本如果您有改善此文档的想法,请随时通过在GitHub上提交拉取请求来贡献。
文档的 GitHub 链接复制文档 Markdown 到剪贴板
import PackageManagerTabs from '/components/tabs/PackageManagerTabs.astro';
import { Steps } from '@astrojs/starlight/components';
import Since from '/components/Since.astro';
中间件允许你拦截请求和响应,并在即将渲染页面或端点时动态注入行为。对于所有预渲染的页面,这种渲染发生在构建时,但对于按需渲染的页面,这种渲染发生在请求路由时,这时可以使用 额外的 SSR 功能如 cookie 和标头。
这也允许你通过修改在所有 Astro 组件和 API 端点中可用的 locals 对象,设置和共享跨端点和页面的请求特定信息。即使在构建时运行这个中间件时,这个对象也是可用的。
基本用法
-
在这个文件中,导出一个接收 context 对象 的 onRequest() 函数。注意这里不能是默认导出。
js复制代码复制代码到剪贴板
export function onRequest (context, next) { // 拦截一个请求里的数据 // 可选地修改 `locals` 中的属性 context.locals.title = "New title"; // 返回一个 Response 或者调用 `next()` 的结果 return next();};
-
在任何 .astro 文件中,使用 Astro.locals 访问响应数据。
astro复制代码复制代码到剪贴板
---const data = Astro.locals;---<h1>{data.title}</h1><p>这个 {data.property} 来自中间件。</p>
context 对象
context 对象包含需要对其他中间件、API 路由和 .astro 路由在渲染过程中可用的信息。
它是一个传入 onRequest() 的可选参数,可能包含 locals 对象以及其他在渲染期间共享的任何属性。例如,context 对象可能包含用于认证的 cookies。
在 context.locals 中存储数据
context.locals 是一个可以在中间件中操作、包含来自 Response 的数据的对象。
这个 locals 对象在请求处理过程中被传递,并作为 APIContext 和 AstroGlobal 的属性可用。这使得中间件,API 路由和 .astro 页面之间可以共享数据。这对于在渲染步骤中存储请求特定的数据(例如用户数据)很有用。
:::tip[集成属性] 集成可能会在 locals 对象中设置属性和提供一些功能。如果你在使用一个集成,检查它的文档并确认你没有覆盖掉它的属性或者在做重复的工作。 :::
你可以在 locals 中存储任何类型的数据:字符串,数字,甚至复杂的数据类型,如函数和映射。
复制代码到剪贴板
export function onRequest (context, next) { // 拦截一个请求里的数据 // 可选地修改 `locals` 中的属性 context.locals.user.name = "John Wick"; context.locals.welcomeTitle = () => { return "Welcome back " + locals.user.name; }; // 返回一个 Response 或者调用 `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>这个 {data.property} 来自中间件。</p><ul> {orders.map(order => { return <li>{/* do something with each order */}</li>; })}</ul>
locals 是一个在单个 Astro 路由中创建和销毁的对象;当你的路由页面渲染完后,locals 将不再存在,并会创建一个新的。需要在多个页面请求之间保持的信息必须存储在其他地方。
:::note locals 的值不能在运行时被覆盖。这样做会有清除用户存储的所有信息的风险。Astro 会进行检查,如果 locals 被覆盖将会抛出错误。 :::
示例:删除敏感信息
下面的示例使用中间件将"私人信息"替换为"已删除",以便你在页面上渲染修改后的 HTML:
复制代码到剪贴板
export const onRequest = async (context, next) => { const response = await next(); const html = await response.text(); const redactedHtml = html.replaceAll("私人信息", "已删除"); return new Response(redactedHtml, { status: 200, headers: response.headers });};
中间件类型
你可以导入并使用 defineMiddleware() 实用函数来提供类型安全:
复制代码到剪贴板
// src/middleware.tsimport { defineMiddleware } from "astro:middleware";// `context` 和 `next` 会自动被类型化export const onRequest = defineMiddleware((context, next) => {});
或者,如果你使用 JsDoc 来提供类型安全,你可以使用 MiddlewareHandler:
复制代码到剪贴板
// src/middleware.js/** * @type {import("astro").MiddlewareHandler} */// `context` 和 `next` 会自动被类型化export 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("验证请求"); const response = await next(); console.log("验证响应"); return response;}async function auth(_, next) { console.log("授权请求"); const response = await next(); console.log("授权响应"); return response;}async function greeting(_, next) { console.log("问候请求"); const response = await next(); console.log("问候响应"); return response;}export const onRequest = sequence(validation, auth, greeting);
控制台打印结果顺序如下:
复制代码到剪贴板
验证请求授权请求问候请求问候响应授权响应验证响应
重写
APIContext 中暴露了一个名为 rewrite() 的方法,它的工作方式与 Astro.rewrite 相同。
使用 context.rewrite() 在中间件中显示不同页面的内容,而不会将访问者 重定向 到新页面。这将触发一个新的渲染阶段,导致任何中间件被重新执行。
复制代码到剪贴板
import { isLoggedIn } from "~/auth.js"export function onRequest (context, next) { if (!isLoggedIn(context)) { // 如果用户未登录,则更新请求来渲染 `/login` 路由, // 并添加标头以指示在成功登录应该把用户重定向到何处。 // 重新执行中间件。 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)) { // 如果用户未登录,则更新请求来渲染 `/login` 路由, // 并添加标头以指示在成功登录应该把用户重定向到何处。 // 将新的 `context` 返回给任何后续中间件。 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 模板的重写。
复制代码到剪贴板
// 当前 URL 是 https://example.com/blog// 第一个中间件函数async function first(_, next) { console.log(context.url.pathname) // 这里会打印 "/blog" // 重写到一个新的路由,首页 // 返回更新的 `context`,传递给下一个函数 return next("/")}// 当前 URL 仍然是 https://example.com/blog// 第二个中间件函数async function second(context, next) { // 接收更新后的 `context` console.log(context.url.pathname) // 这里会打印 "/" return next()}export const onRequest = sequence(first, second);
错误页面
即使找不到匹配的路由,中间件也会尝试为所有按需渲染的页面运行。这包括 Astro 的默认(空白)404 页面和任何自定义 404 页面。然而,是否运行该代码取决于适配器。一些适配器可能会提供特定平台的错误页面。
在提供 500 错误页面之前,中间件也会尝试运行,包括自定义的 500 页面,除非服务器错误发生在中间件本身的执行中。如果你的中间件没有成功运行,那么你将无法访问 Astro.locals 来渲染你的 500 页面。