Skip to content

中间件

中间件允许你拦截请求和响应,并在每次页面或端点即将渲染时动态注入行为。此渲染在构建时发生在所有预渲染页面,但在请求按需渲染页面的路由时发生,从而使 其他 SSR 功能,如 cookie 和标头 可用。

¥Middleware allows you to intercept requests and responses and inject behaviors dynamically every time a page or endpoint is about to be rendered. This rendering occurs at build time for all prerendered pages, but occurs when the route is requested for pages rendered on demand, making additional SSR features like cookies and headers available.

中间件还允许你通过改变所有 Astro 组件和 API 端点中可用的 locals 对象来设置和共享端点和页面之间的请求特定信息。即使此中间件在构建时运行,此对象也可用。

¥Middleware also allows you to set and share request-specific information across endpoints and pages by mutating a locals object that is available in all Astro components and API endpoints. This object is available even when this middleware runs at build time.

¥Basic Usage

  1. Create src/middleware.js|ts (Alternatively, you can create src/middleware/index.js|ts.)

  2. Inside this file, export an onRequest() function that can be passed a context object and next() function. This must not be a default export.

    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();
    };
  3. Inside any .astro file, access response data using Astro.locals.

    src/components/Component.astro
    ---
    const data = Astro.locals;
    ---
    <h1>{data.title}</h1>
    <p>This {data.property} is from middleware.</p>

¥The context object

context 对象包括渲染过程中可供其他中间件、API 路由和 .astro 路由使用的信息。

¥The context object includes information to be made available to other middleware, API routes and .astro routes during the rendering process.

这是传递给 onRequest() 的可选参数,它可能包含 locals 对象以及渲染期间要共享的任何其他属性。例如,context 对象可以包括用于身份验证的 cookie。

¥This is an optional argument passed to onRequest() that may contain the locals object as well as any additional properties to be shared during rendering. For example, the context object may include cookies used in authentication.

将数据存储在 context.locals

标题部分 将数据存储在 context.locals 中

¥Storing data in context.locals

context.locals 是一个可以在中间件内部操作的对象。

¥context.locals is an object that can be manipulated inside the middleware.

locals 对象在请求处理过程中转发,并可作为 APIContextAstroGlobal 的属性使用。这允许在中间件、API 路由和 .astro 页面之间共享数据。这对于在渲染步骤中存储特定于请求的数据(例如用户数据)非常有用。

¥This locals object is forwarded across the request handling process and is available as a property to APIContext and AstroGlobal. This allows data to be shared between middlewares, API routes, and .astro pages. This is useful for storing request-specific data, such as user data, across the rendering step.

你可以在 locals 中存储任何类型的数据:字符串、数字,甚至复杂的数据类型,例如函数和映射。

¥You can store any type of data inside locals: strings, numbers, and even complex data types such as functions and maps.

src/middleware.js
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.locals.astro 文件中使用此信息。

¥Then you can use this information inside any .astro file with Astro.locals.

src/pages/orders.astro
---
const title = Astro.locals.welcomeTitle();
const orders = Array.from(Astro.locals.orders.entries());
---
<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 is an object that lives and dies within a single Astro route; when your route page is rendered, locals won’t exist anymore and a new one will be created. Information that needs to persist across multiple page requests must be stored elsewhere.

示例:编辑敏感信息

标题部分 示例:编辑敏感信息

¥Example: redacting sensitive information

下面的示例使用中间件将 “私有信息” 替换为单词 “REDACTED”,以允许你在页面上渲染修改后的 HTML:

¥The example below uses middleware to replace “PRIVATE INFO” with the word “REDACTED” to allow you to render modified HTML on your page:

src/middleware.js
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
});
};

¥Middleware types

你可以导入并使用实用函数 defineMiddleware() 来利用类型安全:

¥You can import and use the utility function defineMiddleware() to take advantage of type safety:

src/middleware.ts
import { defineMiddleware } from "astro:middleware";
// `context` and `next` are automatically typed
export const onRequest = defineMiddleware((context, next) => {
});

相反,如果你使用 JsDoc 来利用类型安全,则可以使用 MiddlewareHandler

¥Instead, if you’re using JsDoc to take advantage of type safety, you can use MiddlewareHandler:

src/middleware.js
/**
* @type {import("astro").MiddlewareHandler}
*/
// `context` and `next` are automatically typed
export const onRequest = (context, next) => {
};

要在 Astro.locals 中键入信息(这会在 .astro 文件和中间件代码中提供自动补齐功能),请在 env.d.ts 文件中声明全局命名空间:

¥To type the information inside Astro.locals, which gives you autocompletion inside .astro files and middleware code, declare a global namespace in the env.d.ts file:

src/env.d.ts
declare namespace App {
interface Locals {
user: {
name: string
},
welcomeTitle: () => string,
orders: Map<string, object>
}
}

然后,在中间件文件内,你可以利用自动补齐和类型安全。

¥Then, inside the middleware file, you can take advantage of autocompletion and type safety.

¥Chaining middleware

可以使用 sequence() 按指定顺序连接多个中间件:

¥Multiple middlewares can be joined in a specified order using sequence():

src/middleware.js
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);

这将导致以下控制台命令:

¥This will result in the following console order:

终端窗口
validation request
auth request
greeting request
greeting response
auth response
validation response

¥Rewriting

Added in: astro@4.13.0

APIContext 公开了一种名为 rewrite() 的方法,其工作方式与 Astro.rewrite 相同。

¥The APIContext exposes a method called rewrite() which works the same way as Astro.rewrite.

在中间件中使用 context.rewrite() 显示不同页面的内容,而无需将访问者引导到新页面。这将触发新的渲染阶段,导致任何中间件被重新执行。

¥Use context.rewrite() inside middleware to display a different page’s content without redirecting your visitor to a new page. This will trigger a new rendering phase, causing any middleware to be re-executed.

src/middleware.js
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 提供:

¥You can also pass the next() function an optional URL path parameter to rewrite the current Request without retriggering a new rendering phase. The location of the rewrite path can be provided as a string, URL, or Request:

src/middleware.js
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 提供。

¥The next() function accepts the same payload of the Astro.rewrite() function. The location of the rewrite path can be provided as a string, URL, or Request.

当你通过 sequence() 链接多个中间件函数时,向 next() 提交路径将在原地重写 Request,中间件将不会再次执行。链中的下一个中间件函数将接收新的 Request 及其更新的 context

¥When you have multiple middleware functions chained via sequence(), submitting a path to next() will rewrite the Request in place and the middleware will not execute again. The next middleware function in the chain will receive the new Request with its updated context:

使用此签名调用 next() 将使用旧的 ctx.request 创建一个新的 Request 对象。这意味着尝试在重写之前或之后使用 Request.body 将引发运行时错误。此错误通常由 使用 HTML 表单的 Astro 操作 引发。在这些情况下,我们建议使用 Astro.rewrite() 而不是中间件来处理 Astro 模板的重写。

¥Calling next() with this signature will create a new Request object using the old ctx.request. This means that trying to consume Request.body, either before or after this rewrite, will throw a runtime error. This error is often raised with Astro Actions that use HTML forms. In these cases, we recommend handling rewrites from your Astro templates using Astro.rewrite() instead of using middleware.

src/middleware.js
// Current URL is https://example.com/blog
// First middleware function
async 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 function
async function second(context, next) {
// Receives updated `context`
console.log(context.url.pathname) // this will log "/"
return next()
}
export const onRequest = sequence(first, second);

¥Error pages

即使找不到匹配的路由,中间件也会尝试运行所有按需渲染的页面。这包括 Astro 的默认(空白)404 页面和任何自定义 404 页面。但是,由 adapter 决定该代码是否运行。某些适配器可能会改为提供特定于平台的错误页面。

¥Middleware will attempt to run for all on-demand rendered pages, even when a matching route cannot be found. This includes Astro’s default (blank) 404 page and any custom 404 pages. However, it is up to the adapter to decide whether that code runs. Some adapters may serve a platform-specific error page instead.

除非服务器错误发生在中间件本身的执行过程中,否则中间件还会尝试在提供 500 错误页面(包括自定义 500 页面)之前运行。如果你的中间件无法成功运行,那么你将无法访问 Astro.locals 来渲染你的 500 页。

¥Middleware will also attempt to run before serving a 500 error page, including a custom 500 page, unless the server error occurred in the execution of the middleware itself. If your middleware does not run successfully, then you will not have access to Astro.locals to render your 500 page.

Astro 中文网 - 粤ICP备13048890号