翻译此页面

编写 Express 应用使用的中间件

Middleware functions are functions that have access to the request object (req), the response object (res), and the next function in the application’s request-response cycle. next 函数是 Express 路由器中的一个函数,调用该函数时,会执行当前中间件之后的下一个中间件。

中间件函数可执行下列任务:

  • 执行任意代码。
  • 修改请求对象与响应对象。
  • 终止请求-响应周期。
  • 调用堆栈中的下一个中间件。

如果当前中间件函数没有终止请求-响应周期,它必须调用 next() 以将控制权传递给下一个中间件函数。 否则,请求将会被挂起。

下图展示了中间件函数调用的组成要素:

Elements of a middleware function call

从 Express 5 开始,返回 Promise 的中间件函数在被拒绝或抛出错误时,会自动调用 next(value)next 函数将以被拒绝的值或抛出的错误对象作为参数被调用。

示例

以下是一个简单的 Express “Hello World” 应用示例。 本文的后续部分将为该应用定义并添加三个中间件函数: 一个名为 myLogger,用于打印简单的日志信息;一个名为 requestTime,用于显示 HTTP 请求的时间戳;还有一个名为 validateCookies,用于验证传入的 Cookie。

index.cjs
const express = require('express');
const app = express();
app.get('/', (req, res) => {
res.send('Hello World!');
});
app.listen(3000);

中间件函数 myLogger

以下是一个名为 myLogger 的简单中间件函数示例。 当对应用的请求经过该函数时,它只会打印 “LOGGED”。 该中间件函数被赋值给了一个名为 myLogger 的变量。

const myLogger = function (req, res, next) {
console.log('LOGGED');
next();
};

Here myLogger is defined on its own rather than passed directly to app.use(), so TypeScript has no context to infer its parameters and they are annotated explicitly. You can instead type the whole function as RequestHandler, which types req, res, and next for you. When the middleware is written inline in the app.use() call, Express infers those three parameters and no annotations are needed.

Caution

注意上述代码中对 next() 的调用。 调用该函数会执行应用中的下一个中间件函数。 next() 函数并非 Node.js 或 Express API 的一部分,而是传递给中间件函数的第三个参数。 next() 函数可以被命名为任意名称,但按照惯例,它始终被命名为 next。 为避免混淆,请始终遵循此命名惯例。

加载中间件函数时,需调用 app.use() 并传入该中间件函数。 例如,以下代码在根路径(/)路由之前加载 myLogger 中间件函数。

index.cjs
const express = require('express');
const app = express();
const myLogger = function (req, res, next) {
console.log('LOGGED');
next();
};
app.use(myLogger);
app.get('/', (req, res) => {
res.send('Hello World!');
});
app.listen(3000);

每当应用接收到请求时,都会在终端中打印消息 “LOGGED”。

中间件的加载顺序至关重要:先加载的中间件会先执行。

如果 myLogger 在根路径路由之后加载,请求永远不会到达该中间件,应用也不会打印 “LOGGED”,因为根路径的路由处理程序会终止请求-响应循环。

中间件函数 myLogger 仅打印一条消息,然后通过调用 next() 函数将请求传递给栈中的下一个中间件函数。

中间件函数 requestTime

接下来,我们将创建一个名为 requestTime 的中间件函数,并向请求对象添加一个名为 requestTime 的属性。

Caution

Because the middleware adds a property to req, extend the request type in TypeScript with declaration merging. Declare the property (as optional, since a middleware may not run for every request) in a .d.ts file that is part of your project:

types/express.d.ts
declare global {
namespace Express {
interface Request {
requestTime?: number;
}
}
}
export {};

TypeScript picks up the file automatically, so no tsconfig.json change is needed unless you have set a custom include that does not cover its location. See Extending the API in TypeScript for adding other custom properties and methods.

const requestTime = function (req, res, next) {
req.requestTime = Date.now();
next();
};

该应用现在使用了 requestTime 中间件函数。 此外,根路径路由的回调函数使用了中间件函数挂载到req(请求对象)上的属性。

index.cjs
const express = require('express');
const app = express();
const requestTime = function (req, res, next) {
req.requestTime = Date.now();
next();
};
app.use(requestTime);
app.get('/', (req, res) => {
let responseText = 'Hello World!<br>';
responseText += `<small>Requested at: ${req.requestTime}</small>`;
res.send(responseText);
});
app.listen(3000);

向应用根路径发起请求后,应用便会在浏览器中展示本次请求的时间戳。

中间件函数 validateCookies

最后,我们将创建一个中间件函数来验证传入的 Cookie,如果 Cookie 无效,则返回 400 响应。

下面是一个示例函数,它通过外部异步服务验证 Cookie。

async function cookieValidator(cookies) {
try {
await externallyValidateCookie(cookies.testCookie);
} catch {
throw new Error('Invalid cookies');
}
}

此处我们使用 cookie-parser 中间件从 req 对象中解析传入的 Cookie,并将其传给 cookieValidator 函数。 validateCookies 中间件会返回一个 Promise,该 Promise 被拒绝时将自动触发我们的错误处理程序。

index.cjs
const express = require('express');
const cookieParser = require('cookie-parser');
const cookieValidator = require('./cookieValidator');
const app = express();
async function validateCookies(req, res, next) {
await cookieValidator(req.cookies);
next();
}
app.use(cookieParser());
app.use(validateCookies);
// error handler
app.use((err, req, res, next) => {
res.status(400).send(err.message);
});
app.listen(3000);

Caution

注意 next() 是在 await cookieValidator(req.cookies) 之后调用的。 这能确保如果 cookieValidator 状态变为已兑现,堆栈中的下一个中间件将会被调用。 如果你向 next() 函数传入任何内容(字符串 'route''router' 除外),Express 会将当前请求视为错误,并跳过所有剩余的非错误处理路由和中间件函数。

由于你可以访问请求对象、响应对象、堆栈中的下一个中间件函数以及整个 Node.js API,因此中间件函数的用途是无限的。

For more information about Express middleware, see: Using Express middleware.

可配置中间件

如果需要让你的中间件具备可配置性,可导出一个接收选项对象或其他参数的函数,该函数会根据输入参数返回对应的中间件实现。

my-middleware.cjs
module.exports = function (options) {
return function (req, res, next) {
// Implement the middleware function based on the options object
next();
};
};

该中间件现在可如下所示使用。

index.cjs
const mw = require('./my-middleware.cjs');
app.use(mw({ option1: '1', option2: '2' }));

有关可配置中间件的示例,请参考 cookie-sessioncompression