Express 路由与中间件
一、路由的概念
1、路由就是映射关系
生活实例:在我们拨打10086客服电话时,对应的每个按键有不同的功能,比如按键1-> 服务查询,按键2-> 手机充值,在这里,路由是按键
与服务
之间的映射关系。
在Express中,路由指的是客户端的请求
与服务器处理函数
之间的函数关系。Express中路由由3部分组成,分别是请求的类型、请求的URL地址、处理函数,
格式如下:
app.method(path,handler)
express中路由的例子
//匹配 get 请求,且请求URL为/
app.get('/',function(req,res)=>{
res.send('hello express')
})
//匹配 post 请求,且请求URL为/
app.post('/',function(req,res)=>{
res.send('hello listpage')
})
2、路由的匹配过程
每当一个请求到达服务器之后,需要先经过路由的匹配,只有匹配成功之后,才会调用对应的处理函数。在匹配时,会按照路由的先后顺序
进行匹配,如果请求类型
和请求的URL
同时匹配成功,则Express 会将这次请求,转交给对应的 function 函数进行处理。
二、路由的使用
1、路由的简单用法
//1导入 express
const .express = require('express')
//2.创建 web 服务器
const app = express()
//挂载路由
app.get('/',(req,res)=>{
res.send('hello')
})
app.post('/',(req,res)=>{
res.send('post request')
})
//3.启动 web 服务器.
app.listen(80,()=>{
console.log('服务器运行在80端口');
})
如果挂载多个路由这个方式会很不友好。
2、模块化路由
为了方便对路由进行模块化管理,Express不建议
将路由直接挂载到app上,而是推荐将路由抽离为单独的模块。
1)创建路由模块
- 创建路由模块对应的js文件。
- 调用
express.Router()
函数创建路由对象。 - 向路由对象上挂载具体的路由。
- 使用
module.exports
向外共享路由对象。 - 使用
app.use()
函数注册路由模块。
//1.导入 express
const express = require('express')
//2.创建路由对象
const router = express.Router()
//3.挂载获取用户列表的路由
router.get('/user/list',function(req,res){
res.send('get user list')
})
//4.挂载添加用户的路由
router.post('/user/add',function(req,res){
res.send('add new user')
})
//5.向外导出路由对象
module.exports = router;
如何用外部服务器使用路由模块正常工作?步骤就是先导入在注册,代码如下
2)注册路由模块
const express = require('express');
const app = express()
//导入路由模块
const router = require('./express')
//使用app.use()注册路由模块
app.use(router)
app.listen(80,()=>{
console.log('http://127.0.0.1');
})
打开postman 进行 get 测试,输入http://127.0.0.1/user/list
打开postman 进行 post 测试,输入http://127.0.0.1/user/add
注意:app.use()函数的作用,就是来注册全局中间件
3)为路由模块添加前缀
类似于托管静态资源为静态资源统一挂载访问前缀一样,路由模块添加前缀的方式如下:
const express = require('express');
const app = express()
//导入路由模块
const router = require('./express')
//注册路由模块
app.use('api',router) //在调用app.use 前添加'api'
app.listen(80,()=>{
console.log('http://127.0.0.1');
})
再次使用 postman 访问时便发生了变化
三、Express 中间件
1、中间件的概念与格式
1)什么是中间件
中间件(Middleware),特指业务流程的中间处理环节。
现实生活实例:在处理污水的时候,一般都要经过三个处理环节,从而保证处理过后的废水,达到排放标准。
处理污水的这三个中间处理环节,就可以叫做中间件。
Express 中间件的调用流程:当一个请求到达express 的服务器之后,可以连续调用多个中间件,从而对这次请求进行预处理。
2)Express 中间件的格式
Express的中间件,本质上就是一个 function 处理函数
,express 中间件的格式如下:
app.get('/',function(req,res,next){
next();
))
app.listen(3000);
注意
:中间件函数的形参列表中,必须包含next 参数
,而路由处理函数中只包含 req 和 res 。
3)next 函数的作用
next 函数
是实现多个中间件连续调用
的关键,它表示把流转关系转交
给下一个中间件或路由
。
4)定义简单中间件函数
//常量 mw 所指向的,就是一个中间件函数
const mw = function(req,res,next){
console.log('hello');
//注意:在当前中间件的业务处理完毕后,必须调用 next() 函数
//表示把流转关系转交给下一个中间件或路由
next()
}
2、全局生效的中间件
1)中间件函数
客户端发起的任何请求,到达服务器之后,都会被触发的中间件,叫做全局生效的中间件。通过调用app.use(中间件函数),即可定义一个全局生效的中间件,代码如下:
const express = require('express')
const app = express();
//定义中间件函数
const mw = function(req,res,next){
console.log('hello');
//把流转关系转交给下一个中间件路由
next()
}
//全局生效的中间件
app.use(mw)
app.get('/',(req,res)=>{ //调用 ‘/’ 这个路由
console.log('hi~')
res.send('home page')
})
app.get('/user',(req,res)=>{ //调用‘/user’ 这个路由
console.log('success')
res.send('user page')
})
app.listen(80,()=>{
console.log('http://127.0.0.1');
})
通过postman 测试一下第一个路由:输入http://127.0.0.1/
经过中间件处理之后通过next 函数
把流转关系转交给‘/’ 这个路由`。服务端可以得到以下结果
通过res.send()响应客户端的请求,从而在客户端得到这样的结果。
通过postman 测试一下第二个路由:输入http://127.0.0.1/user
同样经过中间件处理之后通过next 函数
把流转关系转交给‘/user’ 这个路由`。服务端可以得到以下结果
同样通过res.send()响应客户端的请求,从而在客户端响应一个文本消息。
2)全局中间件的简化形式
//这是定义全局中间件的简化形式
app.use(function(req,res,next){
console.log('最简单的中间件函数');
next()
})
同样能得到上述结果。
3)中间件的作用
多个中间件之间,共享一份 req 和 res
。 基于这样的特性,我们可以在上游
的中间件中,统一
为req 和 res 对象添加自定义的属性或方法,供下游
的中间件或路由进行使用。如下图示
举一个例子:如果我们想要获取到请求到达服务器的时间,传统方法在每个路由里面都添加对现在时间的定义,但是因为用路由的方式需要定义很多次,所以可以使用中间件来简化这个操作。代码如下:
const express = require('express')
const app = express();
app.use(function(req,res,next){
//获取到请求到达服务器的时间
const time = Date.now()
//为req对象,挂载自定义属性,从而把时间共享给后面的所有路由
req.startTime = time
next()
})
app.get('/',(req,res)=>{
// 传统定义时间的方式,需要在每一个路由中都在定义
//const time = Date.now()
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');
})
启动终端之后,在postman 中测试,客户端得到了如下结果。
4)定义多个全局中间件
使用app.use()
连续定义多个全局中间件。客户端请求到达服务器之后,会按照中间件定义的先后顺序
依次进行调用。
const express = require('express')
const app = express();
// 定义第一个全局中间件
app.use(function(req,res,next){
console.log('调用了第一个全局中间件');
next()
})
// 定义第二个全局中间件
app.use(function(req,res,next){
console.log('调用了第二个全局中间件');
next()
})
//定义一个路由
app.get('/user',(req,res)=>{
res.send('user page')
})
app.listen(80,()=>{
console.log('http://127.0.0.1');
})
启动终端之后,在postman 中测试,输入http://127.0.0.1/user 服务器端得到了如下结果。
5)局部生效的中间件
不使用 app.use() 定义的中间件,叫做局部生效的中间件,用法是在“当前路由中生效”。代码如下:
const express = require('express')
const app = express();
//1、定义中间件函数
const mv1 = (req,res,next)=>{
console.log('调用局部生效的中间件');
next()
}
//创建路由
app.get('/', mv1,mv2, (req,res)=>{ //加上mv1 就相当于在这个路由中使用了中间件
res.send('home page')
})
app.get('/user',(req,res)=>{
res.send('user page')
})
app.listen(80,()=>{
console.log('http://127.0.0.1');
})
启动终端之后,在postman 中测试,输入http://127.0.0.1/user
服务器并不能打印出“调用局部生效的中间件”
当输入http://127.0.0.1/ 客户端得到了如下结果。
6)定义多个中间件
有两种方式,代码如下:
app.get('/', mv1,mv2, (req,res)=>{ res.send('home page')})
app.get('/', [mv1,mv2], (req,res)=>{ res.send('home page')})
7)了解中间件的5个注意事项
- 一定要在
路由之前
注册中间件 - 客户端发送过来的请求,
可以连续调用多个
中间件进行处理 - 执行完中间件的业务代码之后,
不要忘记调用next()函数
- 为了
防止代码逻辑混乱
,调用next()函数后不要再写额外的代码 - 连续调用多个中间件时,多个中间件之间,
共享
req 和 rest 对象。
四、中间件分类
1、应用级别的中间件
通过app.use() 或app.get()或 app.post(),绑定到app实例上的中间件,叫做应用级别的中间件。
2、路由级别的中间件
绑定到express.Router()
实例上的中间件,叫做路由级别的中间件,它的用法和应用级别中间件没有任何区别,只不过,应用级别中间件是绑定到app实例上,路由级别中间件事件绑定到 router 实例上。
3、错误级别的中间件
错误级别中间件的作用
:专门用来捕获整个项目中发生的异常错误,从而防止项目异常崩溃的问题。
格式:错误级别中间件的function 处理函数中,必须有 4 个形参
,形参顺序从前到后,分别为(err,req,res,next
)
//1.路由
app.get('/',function(req,res){
//1.1 抛出一个自定义的错误
throw new Error('服务器内部发生了错误!')
res.send('home page')
})
app.use((err,req,res,next)=>{
console.log('发生了错误:' + err.message);
res.send('Error!'+ err.message)
})
注意:错误级别的中间件,必须注册在所有路由之后
4、Express 内置的中间件
三个常见的内置中间件:
express.static
快速托管静态资源的内置中间件,例如:HTML文件,图片,CSS样式等(无兼容)express.json
解析JSON 格式的请求体数据(有兼容性)express.urlencoded
解析URL-encoded 格式的请求体数据(有兼容性)
//解析JSON 格式的请求体数据
app.use(express.json())
//解析URL-encoded 格式的请求体数据
app.use(express.urlencoded({extended:false}))
1)express.json内置中间件的使用:
当我们通过 postman 中成功发送了一部分关于JSON的数据之后,将怎么传输到服务器上进行使用那,比如这种数据
在服务器中,可以使用 req.body 这个属性,来接收客户端发送过来的请求体数据。
注意
: 默认情况下,如果不配置解析表单数据的中间件,则req.body 默认等于undefined
const express = require('express')
const app = express();
//通过express.json这个中间件,解析表单中的JSON格式的数据
app.use(express.json());
//1.路由
app.post('/user',function(req,res){
//默认情况下,如果不配置解析表单数据的中间件,则req.body 默认等于undefined
console.log(req.body);
res.send('ok')
})
app.listen(80,function(){
console.log('http://127.0.0.1');
})
再次根据postman 请求发送数据,这时候在服务器上就可以看到发送过来的数据了。
2)express.urlencoded 内置中间件的使用:
当我们通过 postman 中成功发送了一些URL-encoded 格式的数据之后,将怎么传输到服务器上进行使用那, 在服务器端,通过express.urlencoded这个中间件,解析表单中的url-encoded格式,然后数据就可以挂载到 req.body 上。
const express = require('express')
const app = express();
//通过express.urlencoded这个中间件,解析表单中的url-encoded格式的数据//解析URL-encoded 格式的请求体数据
app.use(express.urlencoded({extended:false}))
//路由
app.post('/book',function(req,res){
//在服务器端,可以通过 req.body 来获取 JSON 格式的表单数据和URL-encoded 格式的数据,
console.log(req.body);
res.send('ok')
})
app.listen(80,function(){
console.log('http://127.0.0.1');
})
再次根据postman 请求发送数据,这时候在服务器上就可以看到发送过来的数据了。
5、第三方的中间件
非Express 官方配置的,而是由于第三方开发出来的中间件,叫做第三方中间件。例如 body-parser 这个中间件,来解析请求体数据。使用步骤:
- 运行
npm install body-parser
安装中间件 - 使用
require
导入中间件 - 调用
app.use()
注册并使用中间件
const express = require('express')
const app = express();
//导入解析表单数据的中间件 body-parser
const parser = require('body-parser');
//使用 app.use()注册中间件
app.use(parser.urlencoded({extended:false}))
app.post('/user',function(req,res){
//默认情况下,如果不配置解析表单数据的中间件,则req.body 默认等于undefined
console.log(req.boby)
res.send('ok')
})
app.listen(80,function(){
console.log('http://127.0.0.1');
})
在 postman 中输入请求体数据如下,
服务器端通过 body-parser 中间件来解析请求体数据。Express 内置的express.urlencoded 中间件,就是基于body-parser 这个第三方中间件进一步封装出来的。
6、自定义中间件
1)需求描述与实现步骤
自己动手模拟一个类似于 express.urlencoded 这样的中间件来解析post提交到服务器的表单数据。
实现步骤:
- 定义中间件
- 监听 req 的 data 事件
- 监听 req 的 end 事件
- 使用 querystring 模块解析请求体数据
- 将解析出来的数据对象挂载为 req.body
- 将自定义中间件封装为模块
2)定义中间件
使用app.use()来定义全局生效的中间件;
app.use(function(req,res,next){
//中间件的业务逻辑
})
3)监听 req 的data 事件
在中间件中,需要监听 req 对象的 data
事件,来获取客户端发送到服务器的数据,如果数据量比较大,无法一次性发送完毕,则客户端会把数据切割好后
,分批发送到服务器
。所有data 事件可能会被触发多次,每一次触发data事件时,获取到数据只是完整数据的一部分
,需要手动对接收到的数据进行拼接。
//1.定义一个str字符串,专门用来存储客户端发送过来的请求体数据
let str=''
//2.监听req的data事件
req.on('data',(chunk)=>{
str += chunk
})
4)监听 req 的 end 事件
当请求体数据接收完毕
之后,会自动触发 req 的 end 事件。因此,我们可以在req 的 end 事件中,拿到并处理完整的请求体数据
。
//监听req 的 end 事件
req.on('end',()=>{
// 把str 中存放的完整的请求体数据
console.log(str);
// 把字符串格式的请求体数据,解析成对象格式
})
5) 使用 querystring 模块解析请求体数据
nodejs内置了一个querystring模块,专门用来处理查询字符串,通过这个模块的parse()函数,可以把查询到的字符串解析成对象的格式。
//1.导入处理 querystring 的node.js 内置模块
const qs = require('querystring')
//2.调用 qs.parse()方法,把查询字符串解析为对象
const body = qs.parse(str)
完整代码:
const express = require('express')
const app = express();
const qs = require('querystring')
//解析表单的中间件
app.use((req,res,next)=>{
//1.定义一个str字符串,专门用来存储客户端发送过来的请求体数据
let str=''
//2.监听req的data事件
req.on('data',(chunk)=>{
str += chunk
})
//3.监听req 的 end 事件
req.on('end',()=>{
//3.1 把str 中存放的完整的请求体数据
console.log(str);
//3.2 把字符串格式的请求体数据,解析成对象格式
const body = qs.parse(str)
console.log(body);
})
})
app.post('/user',(req,res)=>{
res.send('ok')
})
app.listen(80,function(){
console.log('http://127.0.0.1');
})
6)将解析处理的数据对象挂载为 req.body
上游的中间件和下游的中间件及路由之间,共享一份 req 和 res.因此,我们可以将解析出来的数据,挂载为req的自定义属性,命名为req.body,供下游使用。
req.on('end',()=>{
//把str 中存放的完整的请求体数据
console.log(str);
// 把字符串格式的请求体数据,解析成对象格式
const body = qs.parse(str)
//将解析出来的请求体对象,挂载为req.body属性
req.body = body
//最后,调用next()属性,执行后续的业务逻辑
next()
})
})
7)将自定义中间件封装为模块
第一步,新建自定义模块,命名为customerBodyParser,通过module.exports把bodyparse给暴露出去,然后将自定义的中间件函数注册为全局可用的中间件。
//导入nodejs内置的querystring 模块
const qs = require('querystring')
const bodyparser=(req,res,next)=>{
//1.定义一个str字符串,专门用来存储客户端发送过来的请求体数据
let str=''
//2.监听req的data事件
req.on('data',(chunk)=>{
str += chunk
})
//3.监听req 的 end 事件
req.on('end',()=>{
//把str 中存放的完整的请求体数据
console.log(str);
// 把字符串格式的请求体数据,解析成对象格式
const body = qs.parse(str)
//将解析出来的请求体对象,挂载为req.body属性
req.body = body
//最后,调用next()属性,执行后续的业务逻辑
next()
})
}
module.exports = bodyparser
const express = require('express')
const app = express()
//1.导入自己封装的中间件模块
const customerBodyParser = require('./use')
//2.将自定义的中间件函数注册为全局可用的中间件
app.use(customerBodyParser )
app.post('/user',(req,res)=>{
res.send(req.body)
})
app.listen(80,function(){
console.log('http://127.0.0.1');
})
在postman 中进行测试,输入以下请求内容
客户端可以得到以下数据:
服务器中最终也能得到相关数据: