Node.js
- 路由的概念
- 什么是路由
- Express中的路由
- 路由的匹配过程
- 路由的使用
- 创建路由模块文件
- 注册路由模块文件
- 为路由模块添加前缀
- Express中间件
- Express中间件格式
- 定义中间件函数
- 定义全局生效的中间件函数
- 中间件的作用
- 定义多个全局中间件
- 局部生效的中间件
- 定义多个局部生效的中间件
- 了解中间件的使用注意事项
- 中间件的分类
- 应用级别的中间件
- 路由级别的中间件
- 错误级别的中间件
- expressd的内置中间件
- 第三方的中间件
- 自定义中间件案例
路由的概念
什么是路由
- 广义上说,路由就是映射关系
- 类似按键和按键导致的不同服务之间的映射关系
Express中的路由
- express中的路由指的是客户端的请求与服务器函数之间的映射关系
- 由三部分组成
- 请求的类型
- 请求的URl地址
- 处理函数
// 导入express
const express = require('express')
// 创建web服务器
const app = express()
// 启动文本服务器
app.listen(80, () => {
console.log("80服务器启动http://127.0.0.1/")
console.log("80服务器2启动http://127.0.0.1/")
})
//app.请求类型(请求地址,代表服务器的处理函数)
app.get('/',function(req,res){
res.send("hello 路由")
})
路由的匹配过程
- 每当一个请求到达服务器之后,需要先经过路由的匹配,只有匹配成功,才会调用处理函数
- 在匹配时, 会按照路由的顺序进行匹配,如果请求类型和请求的URL同时匹配成功,express才会将请求交给对应的function函数进行处理
路由的使用
- 最简单的是把路由挂载到app上,代码量多,不常用
//app.请求类型(请求地址,代表服务器的处理函数)
app.get('/',function(req,res){
res.send("hello 路由")
})
app.post('/',function(req,res){
res.send("hello 路由post")
})
创建路由模块文件
- 模块化路由,将路由抽离为单独的模块
– 步骤1:创建路由模块对应的js文件
–步骤2:调用express.Router()函数创建路由对象
–步骤3:向路由对象上挂载具体的路由
–步骤4:使用module.exports向外共享路由对象
// 导入express
const express = require('express')
// 创建web服务器
const app = express()
// 创建路由对象
const router = express.Router()
router.get('/', function (req, res) {
res.send("hello 路由")
})
router.post('/', function (req, res) {
res.send("hello 路由post")
})
//向外共享路由对象
module.exports = router
注册路由模块文件
// 导入express
const express = require('express')
// 创建web服务器
const app = express()
// 导入路由
const userRouter = require('./router')
// 启动文本服务器
app.listen(80, () => {
console.log("80服务器启动http://127.0.0.1/")
})
//使用app.use()注册路由模块,app.user()函数的作用,就是来注册全局中间件的
app.use(userRouter)
为路由模块添加前缀
//添加统一的访问前缀/api,此时想访问userRouter里的路由就得添加/api
app.use('/api',userRouter)
Express中间件
- 类似污水处理的中间处理过程
- express的中间件的调用流程
- 当一个请求到达Express的服务器之后,可以连续调用多个中间件,从而对这次请求进行预处理
Express中间件格式
- 本质就是一个function的处理函数
- 中间件函数的形参列表,必须包含next参数且放在最后,而路由处理函数只包含req和res
- next函数的作用?
- next函数是实现多个中间件连续调用的关键,它表示把流转关系转交给下一个中间件或路由
- 也就是上图中的中间件1执行完调用next函数,到中间2等等
定义中间件函数
- 在中间件的业务处理完毕后,必须调用next()函数,表示把流转关系交给下一个中间件或路由
//定义中间件函数
const nw = function (req, res, next) {
console.log("中间件函数")
next()
}
定义全局生效的中间件函数
- 客户端发起的所有请求,到达服务器之后,都会触发的中间件,叫做全局生效的中间件
- 通过调用app.use(中间件函数),即可定义一个全局生效的中间件
//定义中间件函数
const nw = function (req, res, next) {
console.log("中间件函数")
next()
}
//将nw注册为全局生效的中间件
app.use(nw)
//或者,组合使用注册
app.use(function (req, res, next) {
console.log("中间件函数")
next()
})
//或者,es语法组合使用
app.use((req, res, next)=> {
console.log("中间件函数")
next()
})
app.get('/1',function(req,res){
res.send("hello 路由")
})
-执行,会在控制台打印中间件函数,next下一个组件,没有下一个组件就到了hello 路由
中间件的作用
- 多个中间件,共享同一份req和res,基于这样的特性,我们可以在上游的中间件中,统一为req或者res对象添加自定义的属性或方法,供下游的中间件或者路由使用
//定义中间件函数
app.use((req, res, next) => {
const time = Date.now()
req.time = time
console.log("中间件函数")
next()
})
//下游的中间件或者路由也能用
app.get('/', function (req, res, next) {
res.send("hello 路由get"+ req.time)
})
定义多个全局中间件
- 可以使用app.use()连续定义多个全局中间件
- 客户端请求到达服务器后,会按照中间件定义的先后顺序依次进行调用
//定义中间件函数
app.use((req, res, next) => {
const time = Date.now()
req.time = time
console.log("中间件函数")
next()
})
//定义中间件函数
app.use((req, res, next) => {
const time = Date.now()
req.time = time
console.log("中间件函数2")
next()
})
//定义中间件函数
app.use((req, res, next) => {
const time = Date.now()
req.time = time
console.log("中间件函数3")
next()
})
局部生效的中间件
- 不使用app.use定义的中间件,只要在走指定路由才会执行中间件,先走中间件,再走路由
//定义中间件函数
const nw = function (req, res, next) {
console.log("中间件函数")
next()
}
//第二个参数就是中间件名称
app.get('/666',nw,function (req, res) {
res.send("hello 路由get")
})
定义多个局部生效的中间件
const nw = function (req, res, next) {
console.log("中间件函数")
next()
}
const nw1 = function (req, res, next) {
console.log("中间件函数2")
next()
}
//下面两种方式完全等价,用哪种方式都行
//第二个参数就是中间件名称
app.get('/666',nw,nw1,function (req, res) {
res.send("hello 路由get")
})
//第二个参数就是中间件名称
app.get('/666',[nw,nw1],function (req, res) {
res.send("hello 路由get")
})
了解中间件的使用注意事项
- 一定要在路由之前注册中间件,否则不生效
- 客户端发送过来的请求,可以连续调用多个中间件进行处理
- 执行完中间件的业务代码之后,不要忘记调用next()函数
- 为了防止代码逻辑混乱,调用next()函数后不要再写额外的代码
- 连续调用多个中间件时候,多个中间件之间,共享req和res对象
中间件的分类
- 应用级别的中间件
- 路由级别的中间件
- 错误级别的中间件
- Express内置的中间件
- 第三方的中间件
应用级别的中间件
- 通过app.use或者app.get()或者app.post(),绑定到app实例上的中间件
//全局中间件
app.use((req, res, next) => {
const time = Date.now()
req.time = time
console.log("中间件函数")
next()
})
//局部中间件
app.get('/666',nw,function (req, res) {
res.send("hello 路由get")
})
路由级别的中间件
- 绑定在express.Router()实例上的中间件
const express = require('express')
// 创建web服务器
const app = express()
// 导入路由
const router= require('./router')
router.use((req, res, next) => {
const time = Date.now()
req.time = time
console.log("中间件函数")
next()
})
// 启动文本服务器
app.listen(80, () => {
console.log("80服务器启动http://127.0.0.1/")
})
//使用app.use()注册路由模块,app.user()函数的作用,就是来注册全局中间件的
app.use(router)
错误级别的中间件
- 错误级别的中间件的作用:专门用来捕获整个项目中发生的异常错误,从而防止项目异常崩溃
- 格式:
- 错误级别的中间件的处理函数中,必须有四个参数,分别是(err,req,res,next)
- 错误级别的中间件必须注册在所有路由之后
// 导入express
const express = require('express')
// 创建web服务器
const app = express()
// 启动文本服务器
app.listen(80, () => {
console.log("80服务器启动http://127.0.0.1/")
})
//路由
app.get('/666',function (req, res) {
throw new Error("抛出定义错误")
res.send("hello get")
})
//会崩溃,所以接下来演示全局的中间件
//定义错误中间件函数
const nw = function (err,req, res, next) {
res.send("检测定义错误1"+err.message)
next()
}
app.use(nw)
expressd的内置中间件
- 自4.16.0版本,Express内置了3个常用的中间件,提高了开发效率和体验
- 1.express.static快速托管静态资源的内置中间件
- 2.express.json解析json格式的请求体数据
- 3.express.urlencoded解析URL-encoded格式的请求体数据(同理)
//路由
app.get('/',function (req, res) {
// 在服务器可以使用req.body这个属性,来接受客户端发送过来的数据
console.log(req.body)
res.send("hello get")
})
- 在服务器可以使用req.body这个属性,来接受客户端发送过来的数据
- 默认情况下,如果不配置解析表单数据的中间件,则req.body默认等于undefined
- 解决方法:配置express.json中间件
- 若还是空对象,检查postman自动生成的请求头勾上了吗(未复现)
app.use(express.json)
app.use(express.urlencoded({extended:false}))
app.get('/',function (req, res) {
// 在服务器可以使用req.body这个属性,来接受客户端发送过来的数据
console.log(req.body)
res.send("hello get")
})
第三方的中间件
- 由第三方开发出来的中间件,大家可以按需下载并配置第三方中间件
- 运行npm install body-parser安装中间件
- 运行require导入中间件
- 调用app.use()注册使用中间件
- 注意:Express内置的express.urlencoded()中间件,就是基于body-parser这个第三方中间件进一步封装的
//导入组件
const parser =require('body-parser')
//使用app.use()注册中间件
app.use(parser.urlencoded({extended:false}))
//路由
app.get('/',function (req, res) {
// 在服务器可以使用req.body这个属性,来接受客户端发送过来的数据
console.log(req.)
res.send(req.query)
})
自定义中间件案例
手动模拟一个类似于express.urlerlencoded这样的中间件,来解析POST提交到服务器的表单数据
- 定义中间件
- 监听req的data事件
- 监听req的end事件
如果数据量比较大,无法一次性发送完毕,客户端会把数据切割,分批发送到服务器,所以data事件可能会触发多次。每一次的触发,获取到的数据只是完整数据的一部分,需要手动进行拼接
- 使用querystring模拟解析请求体数据
node.js内置了querystring模块,专门用来处理查询字符串,通过这个模块提供的parse()函数,将查询到的字符串解析为对象的格式
- 将解析出来的数据挂载为req.body
因为上游的中间件和下游的中间件及路由之间,共享同一份req和res,因此,我们可以将解析出来的数据,挂载为req的自定义属性,命名为req.body,供下游使用
// 导入express
const express = require('express')
// 创建web服务器
const app = express()
// 启动文本服务器
app.listen(80, () => {
console.log("80服务器启动http://127.0.0.1/")
})
//导入内置querystring模块
const qs = require('querystring')
//中间件的业务逻辑
app.use((req, res, next) => {
let str = ""
//监听req对象的data事件。客户端发送过来的新的请求体数据
req.on('data', (chunk) => {
//拼接请求体数据,格式转为字符串
str += chunk
})
//当请求体数据接收完毕之后,会自动触发req的end事件
req.on('end', () => {
//可以拿到完整的请求体数据
console.log(str)
//字符串格式解析为对象格式
const body = qs.parse(str)
//挂载为自定义属性req.body
req.body=body
next()
})
})
//路由
app.post('/', function (req, res) {
//下游使用挂载的req.body
res.send(req.body)
})
- 将自定义组件封装为组件
- 封装文件parse.js
//导入内置querystring模块
const qs = require('querystring')
//中间件的业务逻辑
const bodyParse=(req, res, next) => {
let str = ""
//监听req对象的data事件。客户端发送过来的新的请求体数据
req.on('data', (chunk) => {
//拼接请求体数据,格式转为字符串
str += chunk
})
//当请求体数据接收完毕之后,会自动触发req的end事件
req.on('end', () => {
//可以拿到完整的请求体数据
console.log(str)
//字符串格式解析为对象格式
const body = qs.parse(str)
//挂载为自定义属性req.body
req.body=body
next()
})
}
module.exports=bodyParse
- 调用模块
// 导入express
const express = require('express')
// 创建web服务器
const app = express()
// 启动文本服务器
app.listen(80, () => {
console.log("80服务器启动http://127.0.0.1/")
})
//导入自己封装的组件
const bodyParse=require('./parse')
app.use(bodyParse)
//路由
app.post('/', function (req, res) {
//下游使用挂载的req.body
res.send(req.body)
})