0%

Node.js-express中间件

Node.js-express中间件

业务流程的中间处理环节

中间件的调用流程

当一个请求到达Express的服务器之后,可以连续调用多个中间件,从而对这次请求进行预处理。

image-20220731175328641

中间件的格式

express的中间件本质上就是一个function处理函数,express中间件的格式如下

1
2
3
app.get('/',function(req,res,next){ next() })
//function就是中间件
//注意:中间件函数的形参列表中,必须包含next 参数。而路由处理函数中只包含req和res。

next函数的作用

next函数是实现多个中间件连续调用的关键,它表示把流转关系转交给下一个中间件或路由。

定义中间件函数

1
2
3
4
5
6
7
//常量nw所指向的就是中间件函数
const mw = function(req,res,next){
console.log("这是一个最简单的中间件函数")
next()
//注意:在当前中间件的业务处理完毕后,必须调用next()函数
//表示把流转关系转交给下一个中间件
}

全局生效的中间件

客户端发起的任何请求,到达服务器之后,都会触发的中间件,叫做全局生效的中间件。

通过调用app.use(中间件函数),即可定义一个全局生效的中间件,示例代码如下:

1
2
3
4
5
6
7
const mw = function(req,res,next){
console.log("这是一个最简单的中间件函数")
next()
//注意:在当前中间件的业务处理完毕后,必须调用next()函数
//表示把流转关系转交给下一个中间件
}
app.use(mw)

简化形式:

1
2
3
4
5
6
app.use(function(req,res,next){
console.log("这是一个最简单的中间件函数")
next()
//注意:在当前中间件的业务处理完毕后,必须调用next()函数
//表示把流转关系转交给下一个中间件
})

中间件的作用

多个中间件之间,共享同一份req和res。基于这样的特性,我们可以在上游的中间件中,统一为req或res 对象添加自定义的属性或方法,供下游的中间件或路由进行使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//中间件的作用
const express = require("express")
const app = express()
const mw = function(req,res,next){
//获取请求到达服务器的时间
const time = Date.now()
// 11为req对象,挂载自定义属性,从而把时间共享给后面的所有路由
req.startTime = time
next()
//注意:在当前中间件的业务处理完毕后,必须调用next()函数
//表示把流转关系转交给下一个中间件
}
//将mw注册为全局生效的中问件
app.use(mw)

app.get('/',(req,res)=>{
res.send("home page" + req.startTime)
})
app.get('/user',(req,res)=>{
res.send("user page" + req.startTime)
})
app.listen(80,() => {
console.log("http://127.0.0.1")
})

定义对个全局中间件

可以使用app.use()连续定义多个全局中间件。客户端请求到达服务器之后,会按照中间件定义的先后顺序依次进行调用。示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const express = require("express")
const app = express()
//定义第一个中间件
app.use(function(req,res,next){
console.log("调用了第一个中间件")
next()
//注意:在当前中间件的业务处理完毕后,必须调用next()函数
//表示把流转关系转交给下一个中间件
})

//定义第二个中间件
app.use(function(req,res,next){
console.log("调用了第二个中间件")
next()
//注意:在当前中间件的业务处理完毕后,必须调用next()函数
//表示把流转关系转交给下一个中间件
})
app.get('/user',(req,res)=>{
res.send("user page")
})
app.listen(80,()=>{
console.log('http://127.0.0.1')
})

局部生效的中间件

不使用app.use(定义的中间件,叫做局部生效的中间件,示例代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
//定义中间件函数mw1
const mw1 = function(req, res,next) {
console.log('这是中间件函数')
next()
}
//mw1这个中间件只在"当前路由中生效",这种用法属于"局部生效的中间件"
app.get('/',mw1,function(req. res) {
res.send('Home page.')
)}
//mw1这个中间件不会影响下面这个路由↓↓↓
app.get('/user',function(req,res) { res.send('User page.')
})

使用多个局部生效的中间件

1
2
3
4
5
//以下两种写法是""完全等价""的,可根据自己的喜好,选择任意一种方式进行使用
app.get('/',mw1,mw2,(req,res) => {res.send("Home page.')
})
app.get('/',[mw1,mw2],(req,res) => {res.send("Home page.')
})

中间件使用的注意事项

  1. 一定要在路由之前注册中间件
  2. 客户端发送过来的请求,可以连续调用多个中间件进行处理
  3. 执行完中间件的业务代码之后,不要忘记调用next(函数
  4. 为了防止代码逻辑混乱,调用next()函数后不要再写额外的代码
  5. 连续调用多个中间件时,多个中间件之间,共享req和res对象

中间件的分类

  1. 应用级别的中间件:通过app.use()或app.get()或 app.post(),绑定到app实例上的中间件

    1
    2
    3
    4
    5
    6
    app.use(function(req,res,next){
    next()
    })
    app.get('/',mw1,function(req. res) {
    res.send('Home page.')
    )}
  2. 路由级别的中间件:绑定到 express.Router()实例上的中间件,叫做路由级别的中间件。它的用法和应用级别中间件没有任何区别。只不过,应用级别中间件是绑定到 app实例上,路由级别中间件绑定到 router实例上,代码示例如下:

    1
    2
    3
    4
    5
    6
    7
    8
    const express = require("express")
    const app = express()
    const router = express.Router();

    router.use(function(req,res,next){
    next()
    })
    app.use('\',router)
  3. 错误级别的中间件:错误级别中间件的作用:专门用来捕获整个项目中发生的异常错误,从而防止项目异常崩溃的问题。格式:错误级别中间件的 function处理函数中,必须有4个形参,形参顺序从前到后,分别是(err, req, res, next)。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    app.get( " 7 " , function (req, res)  //路由
    throw new Error("服务器内部发生了错误!')
    //抛出一个自定义的错误
    res.send("Home Page.")
    })
    app.use(function (err, req,res,next)
    //错误极别的十间件
    console.log( '发生了错误:" +err.message)
    //在服务器打H错设消息
    res.send( "Error! ' +err.message)
    })
    //向客户端响应错误相关的内容
  4. Express内置的中间件:自Express 4.16.0版本开始,Express 内置了3个常用的中间件,极大的提高了Express项目的开发效率和体验

    1.express.static快速托管静态资源的内置中间件,例如:HTML文件、图片、CSS样式等(无兼容性)

    2.express.json解析JSON格式的请求体数据(有兼容性,仅在4.16.0+版本中可用)

    3.express.urlencoded解析URL-encoded格式的请求体数据(有兼容性,仅在4.16.0+版本中可用)

    1
    2
    3
    4
    //配置解析application/json格式数据的内置中问件
    app.use(express.json())
    //配置解析 application/x-wow-form-urlencoded格式数署的内置中间件
    app.use(express.urlencoded({extended:false}))
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    const express = require("express")
    const app = express()
    //注意:除了错误级别的中间件,其他的中间件,必须在路由之前进行配置
    app.use(express.json())
    //解析表单中的url-encoded格式数据
    app.use(express.urlencoded({extended:false}))
    app.post('/user',(req,res)=>{
    //在服务器,可以使用 req.body 这个属性,来接收客户端发送过来的请求体数据
    //默认情况下,如果不配置解析表单数据的中间件,则req.body 默认等于undefined
    console.log(req.body)
    res.send("user page")
    })
    app.post('/book',(req,res)=>{
    //在服务器,可以使用 req.body 这个属性,来接收客户端发送过来的请求体数据
    //默认情况下,如果不配置解析表单数据的中间件,则req.body 默认等于undefined
    console.log(req.body)
    res.send("user page")
    })
    app.listen(80,() => {
    console.log("http://127.0.0.1")
    })
  5. 第三方的中间件:非Express 官方内置的,而是由第三方开发出来的中间件,叫做第三方中间件。在项目中,大家可以按需下载并配置第三方中间件,从而提高项目的开发效率。

    1.运行npm install body-parser安装中间件

    2.使用require 导入中间件

    3.调用app.use0注册并使用中间件

    Express内置的 express.urlencoded中间件,就是基于body-parser这个第三方中间件进一步封装出来的。

自定义中间件

自己手动模拟一个类似于express.urlencoded 这样的中间件,来解析 POST提交到服务器的表单数据。

  1. 定义中间件:

    1
    2
    3
    app.use(function(req,res,next){
    //中间件的业务逻辑
    })
  2. 监听req的data事件:在中间件中,需要监听req对象的data事件,来获取客户端发送到服务器的数据。

    如果数据量比较大,无法、次性发送完毕,则客户镐会把数据切割后,分批发送到服务器。所以data事件可能会触发多次,每一次触发data事件时,获取到数据只是完整数据的一部分,需要手动对接收到的数据进行拼接。

    1
    2
    3
    4
    5
    6
    7
    //定义会量,用来存情储名户这发试过来的清求体数据
    let str = ''
    //监听req对象的data事件(客户实发试过来的新的请求体数据)
    req.on('data',(chunk) => {
    // 拼接请求体数据,隐式转换为字符串
    str += chunk
    })
  3. 监听req 的end事件:当请求体数据接收完毕之后,会自动触发req的end 事件。

    因此,我们可以在req的end事件中,拿到并处理完整的请求体数据。示例代码如下:

    1
    2
    3
    4
    5
    6
    7
    //监听req对象的end事件(请求体发送完毕后自动触发)
    req.on('end',() => {
    //打印完整的请求体数据
    console.log(str)
    // TODO:把字符串格式的请求体数据。解析成对象格式

    })
  4. 使用querystring模块解析请求体数据:Node.js 内置了一个querystring 模块,专门用来处理查询字符串。通过这个模块提供的parse()函数,可以轻松把查询字符串,解析成对象的格式。示例代码如下:

    1
    2
    3
    4
    //导入处理querystring的Node.js内置模块
    const qs = require("querystring")
    //调用qs.parse()方法,把查询宁符串解析为对象
    const body = qs.parse(str)
  5. 将解析出来的数据对象挂载为req.body:上游的中间件和下游的中间件及路由之间,共享同一份req和res。因此,我们可以将解析出来的数据,挂载为req的自定义属性,命名为req.body,供下游使用。示例代码如下:

    1
    2
    3
    4
    5
    req.on('end',() => {
    const body = qs.parse(str) //调用qs.parse()方法。把查询字符串解析为对象
    req.body = body //将解析出来的请求体对象,挂载为req.body属性
    next() // 最后,一定要调用next()函数。执行后续的业务逻辑
    })
  6. 将自定义中间件封装为模块:为了优化代码的结构,我们可以把自定义的中间件函数,封装为独立的模块,示例代码如下:

    1
    2
    3
    4
    5
    6
    7
    8
    //custom-body-parser.js模块中的代码
    const s = require('querystring')
    function bodyParser(req,res,next) {/*省略其它代码*/ }
    module.exports = bodyParser //向外导出解析请求体数据的中间件函数
    //导入自定义的中问件模块
    const myBodyParser = require('custom-body-parser')
    //注册自定义的中间件模块
    app.use(myBodyParser)