likes
comments
collection
share

从Express开始学习node.js(三)中间件

作者站长头像
站长
· 阅读数 20

从Express开始学习node.js(三)中间件

引言

在本专栏的上一篇文章从Express开始学习node.js(二)静态资源中,我们学习了如何提供静态资源、提供多个静态资源、设置虚拟路径前缀。

接下来,我们将要学习中间件这一重要概念,继续充实我们的技术积累。

本篇文章作为node.js系列的第三章节,主要内容有:

  • 什么是中间件

  • 如何编写中间件

  • 常用的中间件

  • 错误处理

注:本章节所有的内容,都需要node.js环境,请确保自己已安装node环境

一、中间件简介

在express官网中是这样定义中间件的:

Express 是一个路由和中间件 Web 框架,其自身只具有最低程度的功能:Express 应用程序基本上是一系列中间件函数调用

中间件,又名中间件函数。它能够访问请求对象 (req)、响应对象 (res), 以及应用程序的请求/响应循环中的下一个中间件函数,下一个中间件函数通常由名为 next 的变量来表示。

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

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

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

其实,我们在之前的学习中已经使用了中间件:

  • 在处理post请求时,我们使用了bodyParser.json()这个第三方中间件

  • 在提供静态资源时,我们使用了express.static这个内置中间件

  • 甚至于我们匹配路由时用到的res.send('Hello China!')本身也仅仅是一个中间件,这个中间件返回一个字符串,结束了请求/响应的循环。

注: 关于第三方中间件、内置中间件,我们将在本文的第三部分:常用的中间件部分 进行具体介绍

二、编写中间件

中间件函数是一个具有reqresnext三个参数的函数:

  • req:请求对象

  • res:响应对象

  • next:应用程序的请求/响应循环中的下一个中间件函数

示例如下:

var doSomething = function(req, res, next) {
    // 在这里执行需要的操作
    consonle.log('Do something in there');
    // 将控制权传递给下一个中间件函数
    next();
}

在装入中间件函数时,我们一般有以下两种方式:

  • app.use()

  • app.METHOD()

app.use()app.METHOD()的使用基本相同,都接受一个可选的路径,以及一系列的中间件函数作为参数。

我们用app.use()添加一个针对于/china/:province路径的logChinaDate中间件,这个中间件会对/china/:province路由的所有请求执行logChinaDate中间件函数:

...
var logChinaDate = function(req, res, next) {
    console.log(`China\'s Date is ${new Date().toLocaleDateString()}`);
    next();
}

app.use("/china/:province", logChinaDate);
...

我们用postman发送一个/china/shanghaiGET请求,可以发现:

  • 返回了Hello, I am China's shanghai
  • 在控制台中打印了China's Date is 2023/6/2

我们再用postman发送一个/china/shanghaiPOST请求,我们没有针对/china/:province路由做过POST请求的请求。可以发现:

  • 我们得到了一个错误(关于如何处理错误信息,我们会在本文的第四部分进行介绍)。
  • 在控制台中仍然打印了China's Date is 2023/6/2

app.use()不同的是,app.METHOD()会额外指定针对于GETPOSTDELETE等具体某种请求来指定执行某些中间件函数。

我们用app.METHOD()添加一个针对于/age路径的addReqTime中间件,这个中间件会对/age路由下的POST请求执行addReqTime中间件函数:

...

var addReqTime = function(req, res, next) {
    const now = new Date();
    req.request_time = now;
    next();
}
app.post('/age', addReqTime);
...
// 以下代码是在之前已经编写过的,放在这里仅仅是为了展示如何处理多个中间件函数
app.post('/age', jsonParser, (req, res) => {
    console.log({request_time: req.request_time});
    
    const {name, birthday = 2000} = req.body;
    const age = new Date().getFullYear() - Number(birthday);
    res.send(`Hello ${name}, Your age is ${age}`)
})

...

我们用postman发送一个agePOST请求,可以发现:

  • 返回了Hello Zhang Chulan, Your age is 24
  • 在控制台中打印了{ request_time: 2023-06-02T07:57:37.620Z }

我们在前面提到:app.use()和 app.METHOD()可以接收一系列的中间件函数作为参数,我们在实际使用时,针对于一些特定请求的(如刚才的示例),我们通常会按照期望的顺序 传递多个中间件函数 作为参数

express实际上是利用了函数柯里化的方式,来处理中间件函数,感兴趣的朋友可以去了解一下

我们来改造一下刚才的中间件装入代码,删除掉app.post('/age', addReqTime);,将addReqTime作为参数传递给之前的响应函数:

app.post('/age', addReqTime, jsonParser, (req, res) => {
    console.log({request_time: req.request_time});
    
    const {name, birthday = 2000} = req.body;
    const age = new Date().getFullYear() - Number(birthday);
    res.send(`Hello ${name}, Your age is ${age}`)
})

我们可以发现,得到的结果与刚才完全一致(除了时间,时间是根据请求时间返回的):

  • 返回了Hello Zhang Chulan, Your age is 24
  • 在控制台中打印了{ request_time: 2023-06-02T07:59:24.620Z }

需要注意的是,中间件的装入顺序决定了中间件的执行顺序,先装入的中间件先执行,装入在路由之后的中间件将不会执行,除非路由处理程序没有匹配到任何的路径。

三、常用的中间件

在Express官方文档中,把中间件分为了5个类型:

  • 应用层中间件
  • 路由器层中间件
  • 错误处理中间件
  • 内置中间件
  • 第三方中间件:

但在实际使用中,其实并没有严格意义上的分类,中间件本质就是一个do somethingandnext的=的函数,仅此而已。所谓的分类,只是针对于应用场景的不同做的区分罢了。

应用层中间件

我们在前面使用到的大部分的中间件都是应用层的中间件,我们可以理解为:除了一些特殊的中间件,其它的都是应用层中间件。我们不做赘述。

路由器层中间件

路由器层中间件的工作方式与应用层中间件基本相同,差异之处在于它绑定到 express.Router() 的实例。

在使用过程中,路由器中间件与普通的应用层中间没有什么区别,只是创建了一个express.Router() 的实例,在这个实例中配置中间件,最后将这个实例挂载到我们的服务器应用上。

// 创建了一个`express.Router()` 的实例
var router = express.Router();

// 这里只是一个例子,router的用法与前面生成的express实例app的用法完全一致
router.get('/', (req, res) => {
    res.send('Hello World!')
})

// 将实例router挂载到我们的服务器应用上
app.use('/', router);

错误处理中间件

相比于普通的中间件函数,错误处理中间件函数额外具有一个err参数:

app.use(function(err, req, res, next) {
  console.error(err.stack);
  res.status(500).send('Something broke!');
});

错误处理中间件函数用于处理在服务器异常时,如何给客户端进行响应。一般来说,我们会返回一个5xx的错误码,用来标识服务器错误。

内置中间件

自 V4.x 起,Express只提供express.static这一个内置中间件,之前版本中Express随附的其它中间件函数现在以单独模块的形式提供。

express.static是用来处理静态资源的中间件,在本专栏的上一篇文章从Express开始学习node.js(二)静态资源中已详解介绍,在这里就不做赘述,如有需要,欢迎考古。

如有需要使用之前版本的其它内置中间件,请查看中间件函数的列表

第三方中间件

第三方中间件,其实就是封装好、发布到npm包管理系统上的中间件处理函数,与普通的中间件处理函数用法完全一致,只是多了一步安装依赖罢了。

我们在之前处理post请求时,就引入了一个新的依赖body-parser,用来处理post请求的body中的参数,类似的还有用于解析cookie的中间件函数 cookie-parser等。

善于使用第三方工具,可以帮助我们少写一些中间件代码,建议大家熟悉一些常用的中间件,提高自己的开发效率。

关于Express的第三方中间件还是比较多的,大家如有需要,请参阅:有关 Express 常用的第三方中间件函数的部分列表

四、错误处理

在前面的错误处理中间件中,我们处理了服务器异常时的情况(500错误),但是错误处理中间件并不能处理客户端请求一个不存在资源的情况,也就是我们常说的404错误

在 Express 中,404 响应不是错误的结果,所以错误处理程序中间件不会将其捕获。

404 响应其实是:Express 执行了所有中间件函数和路由,且发现它们都没有响应。

针对于404响应的情况,我们只需要在堆栈的最底部(在其他所有函数之下)添加一个中间件函数来处理 404 响应,我们添加一个简单的中间件函数,来处理客户端请求一个不存在资源的情况

...
// 这个中间件需要添加在所有函数之后
app.use(function(req, res, next) {
    res.status(404).send('Sorry cant find that!');
});

当我们请求一个不存在的资源/images/balabala.jpg时,服务器会按照我们装入的中间件函数,返回一个状态码为404、message为Sorry cant find that!的响应。

备注:如果不在所有函数的最后设置一个用来处理404响应的中间件函数,express将会按照默认的逻辑返回响应,有兴趣的朋友可以尝试一下。

总结

在这一节,我们学习了什么是中间件、编写一个简单的中间件、常用的中间件,以及如何处理错误。接下来我们会继续介绍模板引擎、接入数据库等知识,让我们一起来领略编程的乐趣吧!

我是何以庆余年,如果文章对你起到了帮助,希望可以点个赞,谢谢!

如有问题,欢迎在留言区一起讨论。