中间件是不直接接收请求,也不直接发送响应,而是在这之间处理一个中间过程的组件。
当一个请求到来,会经过多个中间件进行处理,后一个中间件拿到前一个中间件的处理结果,进行再处理,处理完毕后发给下一个中间件,以此类推。直到所有任务执行完毕,最后得到一个响应,再发送回给客户端。
在express
中,所谓的中间件不过就是一个函数,接收参数返回结果。
全局中间件函数定义:
function (req, res, next){
next()
}
其接收三个参数,前两个参数与请求的响应函数一致,next
是中间件必须有的参数,并且在中间件函数的末尾,必须调用next()
方法,这样才会调用下一个中间件函数。
全局中间件
定义好中间件函数后,可以通过app.use
将其注册到服务中。
app.use(Middleware)
其中Middleware
是一个中间件函数。
这种被直接注册到app.use
上的中间件,称为全局生效中间件
,客户端发起的任何请求,都会触发全局中间件。
app.use(function (req, res, next){
console.log("Middleware running...")
next()
})
app.get('/', function (req, res){
console.log("get / success")
})
app.get('/index.html', function (req, res){
console.log("get /index.html success")
})
app.listen(80, () => {
console.log("create web server success")
})
以上服务,定义了一个匿名的中间件函数,并且注册到app.use
中,两个响应函数分别响应/
和/index.html
。
在浏览器中访问这两个地址,查看控制台:
Middleware running...
get / success
Middleware running...
get /index.html success
两个请求都触发了中间件,并且中间件比路由先执行。
中间件之间又要如何传递参数?在从收到请求到发送响应期间,所有的中间件共享同一个req
和res
对象!
因此上游的中间件可以把属性或方法添加到这两个对象中,然后下游的中间件只需要访问这两个对象就可以拿到参数。
示例:
app.use(function (req, res, next){
console.log("Middleware running...")
req.sendStr = 'hello world!'
next()
})
app.get('/', function (req, res){
console.log("get / success")
res.send(req.sendStr)
})
app.get('/index.html', function (req, res){
console.log("get /index.html success")
res.send(req.sendStr)
})
这个代码,在第一个中间件处,给req
添加了一个对象sendStr = 'hello world!'
,在最后的路由函数中,就可以直接获取req.sendStr
并发送出去。
如果要定义多个中间件,只需要多次使用app.use
注册即可:
app.use(function (req, res, next){
console.log("Middleware 1 running...")
next()
})
app.use(function (req, res, next){
console.log("Middleware 2 running...")
next()
})
app.get('/', function (req, res){
console.log("get / success")
})
多个中间件会以定义的顺序依次执行,访问/
的输出结果:
Middleware 1 running...
Middleware 2 running...
get / success
可以看到,先执行了Middleware 1
后执行Middleware 2
,最后执行路由函数。
局部中间件
如果不使用app.use
注册中间件,而是把中间件注册到某个路由上,称为局部中间件
,这种中间件只在某个路由触发时执行。
注册局部中间件直接将中间件函数写入到get
,post
方法中:
app.get('url', Middleware, function(){})
app.post('url', Middleware, function(){})
示例:
const vm1 = function(req, res, next){
console.log("Middleware 1 running...")
next()
}
app.get('/', vm1, function (req, res){
console.log("get / success")
})
app.get('/index.html', function (req, res){
console.log("get /index.html success")
})
app.listen(80, () => {
console.log("create web server success")
})
以上代码为get /
路由绑定了中间件vm1
,但是get /index.html
没有绑定。
访问get /
:
Middleware 1 running...
get / success
访问get /index.html
:
get /index.html success
此时只有get /
触发了局部中间件。
如果要定义多个局部中间件,有两种形式:
app.get('url', Middleware1, Middleware2, function(){})
app.post('url', [Middleware1, Middleware2], function(){})
第一种是直接传入多个中间件函数,第二种是把多个中间件函数作为一个数组进行传入。执行顺序从前往后。
一些中间件的注意事项:
- 中间件必须在路由之前注册
- 所有中间件必须调用
next()
方法 -
next()
方法后面不要再写其他逻辑,作为整个函数的结尾
分类
Express
官方将中间件的用法,分为了五大类:
- 应用级中间件
- 路由级中间件
- 错误级中间件
-
Express
内置中间件 - 第三方中间件
应用级中间件:
只要中间件被绑定到app
上,就是应用级中间件,先前讲解的两个全局和局部中间件,都属于应用级中间件。
路由级中间件:
如果中间件被绑定到express.Router
对象上,那么就是路由级中间件。
示例:
const app = express()
const router = express.Router()
// 路由级中间件
router.use(function (req, res, next){
next()
})
app.use('/', router)
在博客 [Node.js:Express 服务 & 路由] 讲解路由模块化时,讲解过这个对象,如果想把路由进行模块化,就在一个新的模块中专门绑定路由到这个Router
对象上,然后再把这个对象共享给外部。
错误级中间件
错误级中间件专门用于捕获整个项目发送的异常错误,防止项目崩溃。
函数格式:
function (err, req, res, next){
next()
}
在基本的中间件函数上,第一个参数增加一个err
参数,用于捕获全局的异常。
示例:
const express = require('express')
const app = express()
app.get('/', function (req, res){
throw new Error(' / create a error!') // 抛出异常
res.send('success')
})
// 注册错误级中间件
app.use(function (err, req, res, next){
res.send('something happen: ' + err.message)
})
app.listen(80, () => {
console.log("create web server success")
})
以上代码,在访问get /
时,会抛出一个异常,如果不处理项目就崩溃了。
随后为该服务注册了一个错误级中间件,在中间件内部err
就是异常对象,直接把异常信息发送回给客户端。
注意:只有错误级别的中间件才可以在路由之后注册,其余的中间件都必须在路由前注册。
输出结果:
可以看到,此处得到的结果是错误信息,说明错误被处理了。
内置中间件
Express
内置了三个中间件,这些中间件可以快速完成某些功能:
-
express.static
:托管静态资源 -
express.json
:解析json
格式的请求数据 -
express.urlencoded
:解析URL-encoded
格式的请求数据
其中第一个中间件已经在之前详细讲解过了,接下来看看后两个中间件的功能:
启动如下服务:
const express = require('express')
const app = express()
app.post('/user', function (req, res){
console.log(req.body)
})
app.listen(80, () => {
console.log("create web server success")
})
其中post /user
路由,会把收到的请求的请求体输出到控制台。
使用postman
发送一个POST
请求,请求内容为一个json
字符串:
{
"name": "张三",
"age": 18
}
控制台输出结果:
undefined
奇怪了,明明发送了一个json
字符串,为什么请求体得到的是一个undefined
?
如果不配置解析数据的中间件,那么req.body = undefined
。
而这个解析数据的中间件,就是express.json
或者express.urlencoding
。
express.json:
想要解析刚才的json
格式数据,只需要将express.json
注册到服务上即可:
const express = require('express')
const app = express()
app.use(express.json()) // 注册处理数据的中间件
app.post('/user', function (req, res){
console.log(req.body)
})
app.listen(80, () => {
console.log("create web server success")
})
再次发送相同的请求,控制台输出结果就是正确的字符串了。
express.urlencoded:
在postman
发送以下数据:
以键值对的形式发送数据,如果依然使用express.json
进行解析,虽然req.body
不是undefined
了,但是由于检测不到json
字符串,最后会得到一个空对象。
这种键值对形式的数据,就需要express.urlencoded
中间件了:
const express = require('express')
const app = express()
app.use(express.urlencoded({ extended: false }))
app.post('/user', function (req, res){
console.log(req.body)
})
app.listen(80, () => {
console.log("create web server success")
})
使用urlencoded
时,要传入一个对象,属性值固定为extended: false
。
发起同样的请求,输出结果:
最后发送的数据,就被转化为了一个对象。