编写用于 Express 应用的中间件

¥Writing middleware for use in Express apps

概述

中间件函数是可以访问应用请求-响应周期中的 请求对象req)、响应对象res)和 next 函数的函数。next 函数是 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. The next function is a function in the Express router which, when invoked, executes the middleware succeeding the current middleware.

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

¥Middleware functions can perform the following tasks:

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

¥If the current middleware function does not end the request-response cycle, it must call next() to pass control to the next middleware function. Otherwise, the request will be left hanging.

下图显示了中间件函数调用的元素:

¥The following figure shows the elements of a middleware function call:

中间件函数适用的 HTTP 方法。 ¥HTTP method for which the middleware function applies.
中间件函数适用的路径(路由)。 ¥Path (route) for which the middleware function applies.
中间件函数。 ¥The middleware function.
中间件函数的回调参数,按约定称为 "next"。 ¥Callback argument to the middleware function, called "next" by convention.
中间件函数的 HTTP response 参数,按照惯例称为 "res"。 ¥HTTP response argument to the middleware function, called "res" by convention.
中间件函数的 HTTP request 参数,按照惯例称为 "req"。 ¥HTTP request argument to the middleware function, called "req" by convention.

从 Express 5 开始,返回 Promise 的中间件函数在拒绝或抛出错误时将调用 next(value)next 将使用被拒绝的值或抛出的错误来调用。

¥Starting with Express 5, middleware functions that return a Promise will call next(value) when they reject or throw an error. next will be called with either the rejected value or the thrown Error.

示例

这是一个简单的 “你好世界” Express 应用示例。本文的其余部分将定义三个中间件函数并将其添加到应用:一个称为 myLogger 的打印简单日志消息,一个称为 requestTime 的显示 HTTP 请求的时间戳,一个称为 validateCookies 的验证传入的 cookie。

¥Here is an example of a simple “Hello World” Express application. The remainder of this article will define and add three middleware functions to the application: one called myLogger that prints a simple log message, one called requestTime that displays the timestamp of the HTTP request, and one called validateCookies that validates incoming cookies.

const express = require('express')
const app = express()

app.get('/', (req, res) => {
  res.send('Hello World!')
})

app.listen(3000)

Middleware function myLogger

Here is a simple example of a middleware function called “myLogger”. This function just prints “LOGGED” when a request to the app passes through it. The middleware function is assigned to a variable named myLogger.

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

请注意上面对 next() 的调用。调用此函数会调用应用中的下一个中间件函数。next() 函数不是 Node.js 或 Express API 的一部分,而是传递给中间件函数的第三个参数。next() 函数可以命名为任何名称,但按照惯例,它始终命名为 “next”。为了避免混淆,请始终使用此约定。

¥Notice the call above to next(). Calling this function invokes the next middleware function in the app. The next() function is not a part of the Node.js or Express API, but is the third argument that is passed to the middleware function. The next() function could be named anything, but by convention it is always named “next”. To avoid confusion, always use this convention.

要加载中间件函数,调用 app.use(),指定中间件函数。例如,以下代码在路由到根路径 (/) 之前加载 myLogger 中间件函数。

¥To load the middleware function, call app.use(), specifying the middleware function. For example, the following code loads the myLogger middleware function before the route to the root path (/).

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” 打印到终端。

¥Every time the app receives a request, it prints the message “LOGGED” to the terminal.

中间件加载的顺序很重要:先加载的中间件函数也先执行。

¥The order of middleware loading is important: middleware functions that are loaded first are also executed first.

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

¥If myLogger is loaded after the route to the root path, the request never reaches it and the app doesn’t print “LOGGED”, because the route handler of the root path terminates the request-response cycle.

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

¥The middleware function myLogger simply prints a message, then passes on the request to the next middleware function in the stack by calling the next() function.

中间件函数请求时间

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

¥Next, we’ll create a middleware function called “requestTime” and add a property called requestTime to the request object.

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

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

¥The app now uses the requestTime middleware function. Also, the callback function of the root path route uses the property that the middleware function adds to req (the request object).

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)

当你向应用的根发出请求时,应用现在会在浏览器中显示你的请求的时间戳。

¥When you make a request to the root of the app, the app now displays the timestamp of your request in the browser.

中间件函数 validateCookies

最后,我们将创建一个中间件函数来验证传入的 cookie 并在 cookie 无效时发送 400 响应。

¥Finally, we’ll create a middleware function that validates incoming cookies and sends a 400 response if cookies are invalid.

这是一个使用外部异步服务验证 cookie 的示例函数。

¥Here’s an example function that validates cookies with an external async service.

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

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

¥Here, we use the cookie-parser middleware to parse incoming cookies off the req object and pass them to our cookieValidator function. The validateCookies middleware returns a Promise that upon rejection will automatically trigger our error handler.

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)

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

¥Note how next() is called after await cookieValidator(req.cookies). This ensures that if cookieValidator resolves, the next middleware in the stack will get called. If you pass anything to the next() function (except the string 'route' or 'router'), Express regards the current request as being an error and will skip any remaining non-error handling routing and middleware functions.

因为你可以访问请求对象、响应对象、堆栈中的下一个中间件函数以及整个 Node.js API,所以中间件函数的可能性是无穷无尽的。

¥Because you have access to the request object, the response object, the next middleware function in the stack, and the whole Node.js API, the possibilities with middleware functions are endless.

有关 Express 中间件的更多信息,请参阅:使用 Express 中间件

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

可配置的中间件

如果你需要可配置的中间件,请导出一个接受选项对象或其他参数的函数,然后根据输入参数返回中间件实现。

¥If you need your middleware to be configurable, export a function which accepts an options object or other parameters, which, then returns the middleware implementation based on the input parameters.

文件:my-middleware.js

¥File: my-middleware.js

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

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

¥The middleware can now be used as shown below.

const mw = require('./my-middleware.js')

app.use(mw({ option1: '1', option2: '2' }))

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

¥Refer to cookie-session and compression for examples of configurable middleware.