如何使用和编写 Express 中间件

中间件是一个经常被误解的话题,因为它无论听起来还是看起来都非常复杂,但实际上中间件其实非常简单。 中间件的整个思想是在服务器从客户端获取请求之后和发送响应的控制器操作之前执行一些代码。 本质上,它是在你的请求的中间执行的代码,因此被称为中间件。 不过,在深入了解中间件的细节之前,我想设置一个具有两个路由的基本的 Express 服务器。

设置 Express 服务器

要开始开发 Node.js 项目,你需要运行 npm init -y。 这将创建一个基本的 package.json 文件,其中为你填写了所有默认值。 接下来要做的是通过运行 npm i express 来安装 Express。 最后,我们需要使用以下代码创建一个 server.js 文件。

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

app.get('/', (req, res) => {
  res.send('Home Page')
})

app.get('/users', (req, res) => {
  res.send('Users Page')
})

app.listen(3000, () => console.log('Server Started'))

这个 server.js 文件简单地在端口 3000 上设置了一个服务器,它有两个路由,一个 home page 路由和一个 users page 路由。 最后要做的是运行 node server.js 来启动应用程序,如果一切正常,你将在控制台中看到一条消息,说服务器已启动。 然后,你可以打开任何浏览器访问 localhost:3000,你应该会看到 Home Page 消息。 如果你转到 localhost:3000/users,你应该会看到 Users Page 消息。

这就是本文其余部分所需的全部基本设置。 当我们进行更改时,你需要在控制台中重新启动服务器才能看到更改生效。

什么是中间件?

我简单地谈到了中间件作为在服务器收到请求之后和控制器动作发送响应之前执行的函数,但还有一些特定于中间件的东西。 最重要的是中间件函数可以访问响应(res)和请求(req)变量,并且可以根据需要修改或使用它们。 中间件函数还有第三个参数:next 函数。 这个函数很重要,因为它必须被中间件调用才能执行下一个中间件。 如果未调用此 next 函数,则包括控制器操作在内的任何其他中间件都不会被调用。

仅从文字理解这些有点困难,因此在下一节中,我们将创建一个日志中间件,该中间件将记录用户发出的请求的 url。

如何创建日志中间件

正如我在上一节中提到的,中间件需要三个参数,reqresnext,因此为了创建中间件,我们就需要创建一个具有这 3 个输入的函数。

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

app.get('/', (req, res) => {
  res.send('Home Page')
})

app.get('/users', (req, res) => {
  res.send('Users Page')
})

function loggingMiddleware(req, res, next) {
  console.log('Inside Middleware')
}

app.listen(3000, () => console.log('Server Started'))

我们现在拥有了一个基本的中间件函数的外壳,这个中间件只定义了一些占位符内容。但应用程序没有使用这个中间件。 Express 有几种可以用来定义中间件的不同方法。 但在本示例中,我们将通过将中间件添加到应用程序级别,使该中间件在每个控制器操作之前被执行,通过像下面这样在 app 变量上使用 use 函数就可以做到这一点。

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

app.use(loggingMiddleware)

app.get('/', (req, res) => {
  res.send('Home Page')
})

app.get('/users', (req, res) => {
  res.send('Users Page')
})

function loggingMiddleware(req, res, next) {
  console.log('Inside Middleware')
}

app.listen(3000, () => console.log('Server Started'))

该应用程序现在正在使用我们定义的中间件,如果我们重新启动服务器并导航到我们应用程序中的任何页面,你会注意到在控制台中出现了 Inside Middleware 。 这很好,但有一个小问题,应用程序现在永远处于加载状态,一直无法真正完成请求。 这是因为在我们的中间件中,我们没有调用 next 函数,因此控制器操作永远不会被调用。 我们可以通过在 logging 操作后调用 next 来解决这个问题。

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

app.use(loggingMiddleware)

app.get('/', (req, res) => {
  res.send('Home Page')
})

app.get('/users', (req, res) => {
  res.send('Users Page')
})

function loggingMiddleware(req, res, next) {
  console.log('Inside Middleware')
  next()
}

app.listen(3000, () => console.log('Server Started'))

现在,如果你重启服务器,你会注意到 logging 在完全地正常工作,并且网页能够正确加载。 接下来要做的是实际记录用户在中间件内部访问的 URL,这就是 req 变量会派上用场的地方。

const express = require('express')
const app = express()
app.use(loggingMiddleware)
app.get('/', (req, res) => {
  res.send('Home Page')
})

app.get('/users', (req, res) => {
  res.send('Users Page')
})

function loggingMiddleware(req, res, next) {
  console.log(`${new Date().toISOString()}: ${req.originalUrl}`)
  next()
}

app.listen(3000, () => console.log('Server Started'))

日志中间件现在在应用程序中的所有路由上都 100% 正确工作,但我们只是接触到了中间件有用性的皮毛。 在下一个示例中,我们将看看为用户页面创建一个简单的授权中间件。

高级中间件示例

首先,我们需要创建另一个函数以用作中间件。

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

app.use(loggingMiddleware)

app.get('/', (req, res) => {
  res.send('Home Page')
})

app.get('/users', (req, res) => {
  res.send('Users Page')
})

function loggingMiddleware(req, res, next) {
  console.log(`${new Date().toISOString()}: ${req.originalUrl}`)
  next()
}

function authorizeUsersAccess(req, res, next) {
  console.log('authorizeUsersAccess Middleware')
  next()
}

app.listen(3000, () => console.log('Server Started'))

这只是用作中间件的函数的外壳,但我们现在可以将其添加到我们的用户页面路由中,以确保我们的中间件仅在用户页面路由上执行。 这可以通过将该函数作为参数添加到用户页面的 app.get 函数来完成。

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

app.use(loggingMiddleware)

app.get('/', (req, res) => {
  res.send('Home Page')
})

app.get('/users', authorizeUsersAccess, (req, res) => {
  res.send('Users Page')
})

function loggingMiddleware(req, res, next) {
  console.log(`${new Date().toISOString()}: ${req.originalUrl}`)
  next()
}

function authorizeUsersAccess(req, res, next) {
  console.log('authorizeUsersAccess Middleware')
  next()
}

app.listen(3000, () => console.log('Server Started'))

现在,如果你重新启动服务器并转到用户页面,你应该会看到消息authorizeUsersAccess Middleware,但如果你转到主页,则不会显示此消息。 我们现在有只在应用程序中的单个路由上执行的中间件。 接下来要做的是填写此函数的逻辑,以便如果用户无权访问该页面,他们将收到错误消息。

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

app.use(loggingMiddleware)

app.get('/', (req, res) => {
  res.send('Home Page')
})

app.get('/users', authorizeUsersAccess, (req, res) => {
  res.send('Users Page')
})

function loggingMiddleware(req, res, next) {
  console.log(`${new Date().toISOString()}: ${req.originalUrl}`)
  next()
}

function authorizeUsersAccess(req, res, next) {
  if (req.query.admin === 'true') {
    next()
  } else {
    res.send('ERROR: You must be an admin')
  }
}

app.listen(3000, () => console.log('Server Started'))

这个中间件现在检查查询参数 admin=true 是否在 URL 中,如果不是,则向用户显示错误消息。 你可以通过访问 http://localhost:3000/users 进行测试,你将看到一条错误消息,说明你不是管理员。 如果你改为访问 http://localhost:3000/users?admin=true,你将看到普通用户页面,因为你将 admin 的查询参数设置为 true

中间件真正有用的另一件事是在中间件之间发送数据的能力。 使用 next 函数无法做到这一点,但你可以修改 reqres 变量来设置你自己的自定义数据。 例如,在前面的示例中,如果我们希望在用户是管理员的情况下将变量设置为 true,我们可以轻松地做到这一点。

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

app.use(loggingMiddleware)

app.get('/', (req, res) => {
  res.send('Home Page')
})

app.get('/users', authorizeUsersAccess, (req, res) => {
  console.log(req.admin)
  res.send('Users Page')
})

function loggingMiddleware(req, res, next) {
  console.log(`${new Date().toISOString()}: ${req.originalUrl}`)
  next()
}

function authorizeUsersAccess(req, res, next) {
  if (req.query.admin === 'true') {
    req.admin = true
    next()
  } else {
    res.send('ERROR: You must be an admin')
  }
}

app.listen(3000, () => console.log('Server Started'))

此代码在 req 对象上设置一个管理变量,然后在用户页面的控制器操作中访问该变量。
This code sets an admin variable on the req object which is then accessed in the controller action for the users page.

中间件附加信息

这是你需要了解的有关中间件功能的大部分内容,但还有一些额外的事情需要了解。

1. 控制器动作就像中间件

你可能已经注意到的一件事是具有 reqres 变量的控制器操作与中间件非常相似。 那是因为它们本质上是中间件,但在它们之后没有其他中间件。 它们是中间件链的末端,这就是为什么控制器动作中永远不会有任何的对 next 的调用。

2. 调用 next 与调用 return 不同

到目前为止,我看到开发人员在使用中间件时犯的最大错误是他们将next 函数视为从中间件中退出,以下面这个中间件为例。

function middleware(req, res, next) {
  if (req.valid) {
    next()
  }
  res.send('Invalid Request')
}

从表面上看,这段代码看起来是正确的。 如果请求有效,则调用next函数,如果无效,则发送错误消息。 问题是 next 函数实际上并没有从中间件函数返回。 这意味着当 next 被调用时,下一个中间件将执行,并且会一直持续到没有更多的中间件可以执行。 然后在此中间件执行完所有中间件之后,代码将在每个中间件中的 next 调用后立即恢复。 这意味着在此中间件中,错误消息将始终发送给用户,这显然不是你想要的。 防止这种情况的一种简单方法是在你调用 next 时简单地返回

function middleware(req, res, next) {
  if (req.valid) {
    return next()
  }
  res.send('Invalid Request')
}

现在代码在调用 next 后将不再执行,因为它将从函数中返回。 查看此问题的一种简单方法是运行下面的代码。

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

app.get('/', middleware, (req, res) => {
  console.log('Inside Home Page')
  res.send('Home Page')
})

function middleware(req, res, next) {
  console.log('Before Next')
  next()
  console.log('After Next')
}

app.listen(3000, () => console.log('Server Started'))

当你运行此代码并转到主页时,控制台将按顺序打印出以下消息。

Before Next
Inside Home Page
After Next

基本上发生的事情是调用中间件并记录 before 语句。 然后调用 next 以便调用下一组中间件,即记录主页消息的控制器操作。 最后,控制器操作完成执行,中间件随后执行 next 之后的代码,并记录 after 语句。

3. 中间件会按顺序执行

这似乎不言自明,但是当你定义中间件时,它将按使用顺序执行。 以下面的代码为例。

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

app.use(middlewareThree)
app.use(middlewareOne)

app.get('/', middlewareTwo, middlewareFour, (req, res) => {
  console.log('Inside Home Page')
  res.send('Home Page')
})

function middlewareOne(req, res, next) {
  console.log('Middleware One')
  next()
}

function middlewareTwo(req, res, next) {
  console.log('Middleware Two')
  next()
}

function middlewareThree(req, res, next) {
  console.log('Middleware Three')
  next()
}

function middlewareFour(req, res, next) {
  console.log('Middleware Four')
  next()
}

app.listen(3000, () => console.log('Server Started'))

由于 app.use 语句首先出现,这些语句中的中间件将按照添加顺序首先执行。 接下来定义 app.get 中间件,它们将再次按照它们在 app.get 函数中的顺序执行。 运行时将得到以下控制台输出。

Middleware Three
Middleware One
Middleware Two
Middleware Four

结论

这就是关于中间件的全部知识。 中间件精简代码的能力非常强大,
同时使用户授权以及身份验证等变得更加容易。中间件的灵活性令人难以置信,其用途远不止这些。


How To Use And Write Express Middleware
https://blog.webdevsimplified.com/2019-12/express-middleware-in-depth/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值