从Express开始学习node.js(三)中间件
从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!')
本身也仅仅是一个中间件,这个中间件返回一个字符串,结束了请求/响应的循环。
注: 关于第三方中间件、内置中间件,我们将在本文的第三部分:常用的中间件部分 进行具体介绍
二、编写中间件
中间件函数是一个具有req
、res
、next
三个参数的函数:
-
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/shanghai
的GET
请求,可以发现:
- 返回了
Hello, I am China's shanghai
- 在控制台中打印了
China's Date is 2023/6/2
。
我们再用postman发送一个/china/shanghai
的POST
请求,我们没有针对/china/:province
路由做过POST
请求的请求。可以发现:
- 我们得到了一个错误(关于如何处理错误信息,我们会在本文的第四部分进行介绍)。
- 在控制台中仍然打印了
China's Date is 2023/6/2
。
与app.use()
不同的是,app.METHOD()
会额外指定针对于GET
、POST
、DELETE
等具体某种请求来指定执行某些中间件函数。
我们用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发送一个age
的POST
请求,可以发现:
- 返回了
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 something
andnext的=
的函数,仅此而已。所谓的分类,只是针对于应用场景的不同做的区分罢了。
应用层中间件
我们在前面使用到的大部分的中间件都是应用层的中间件,我们可以理解为:除了一些特殊的中间件,其它的都是应用层中间件。我们不做赘述。
路由器层中间件
路由器层中间件的工作方式与应用层中间件基本相同,差异之处在于它绑定到 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将会按照默认的逻辑返回响应,有兴趣的朋友可以尝试一下。
总结
在这一节,我们学习了什么是中间件、编写一个简单的中间件、常用的中间件,以及如何处理错误。接下来我们会继续介绍模板引擎、接入数据库等知识,让我们一起来领略编程的乐趣吧!
我是何以庆余年,如果文章对你起到了帮助,希望可以点个赞,谢谢!
如有问题,欢迎在留言区一起讨论。
转载自:https://juejin.cn/post/7239996748319113271