# 编写中间件

# 概述

中间件函数是在应用程序的请求-响应周期中可以访问 请求对象 (req)、响应对象 (res) 和 next 函数的函数。next 函数是 Express 路由中的一个函数,当被调用时,它会在当前中间件之后执行中间件。

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

  • 执行任何代码。
  • 更改请求和响应对象。
  • 结束请求-响应周期。
  • 调用堆栈中的下一个中间件。

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

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

  • 中间件函数适用的 HTTP 方法。
  • 中间件函数适用的路径(路由)。
  • 中间件函数。
  • 中间件函数的回调参数,按约定称为 "next"。
  • 中间件函数的 HTTP response 参数,按约定称为 "res"。
  • 中间件函数的 HTTP request 参数,按约定称为 "req"。

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

# 示例

这是一个简单的 "Hello World" Express 应用程序示例。本文的其余部分将定义并向应用程序添加三个中间件函数:一个称为 myLogger 打印简单的日志消息,一个称为 requestTime 显示 HTTP 请求的时间戳,另一个称为 validateCookies 验证传入的 cookie。

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()
}

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

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

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 的属性。

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

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

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,在拒绝时会自动触发我们的错误处理程序。

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)

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

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

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

# 可配置的中间件

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

文件:my-middleware.js

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

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

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

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

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

Last Updated: 6/17/2023, 6:57:19 PM