Node.js 第四天 express路由,中间件

Express路由

路由的概念

什么是路由

广义上来讲,路由就是映射关系

image-20220429123844599

Express中的路由

在Express中,路由指的是客户端的请求和服务处理函数之间的映射关系

Express中的路由分3部分组成,分别是请求的类型 , 请求的URL地址 , 处理函数

Express路由实例

这个就是我们前面经常用的例子

//匹配GET请求,且请求URL为/
app.get('/',function(req,res){
res.send('Hello world')
})
//匹配POST请求,且请求URL为/
app.post('/',function(req,res){
res.send('GOT a POST request')
})

路由的匹配过程

每当一个请求到达服务器之后, 需要先经过路由的匹配 , 只有匹配成功后 , 才会调用对应的处理函数

在匹配时,按照路由的顺序进行匹配,如果请求的类型和URL同时匹配,则会执行对应的函数来处理这次的请求

就是说,我们可以根据不同的请求类型和不同的URL,定义多个app.get()app.post() , 当收到一个请求时, 按照刚才定义的顺序去匹配

image-20220429124533066

注意

  • 按照定义的先后顺序进行匹配
  • 请求类型和请求URL同时匹配成功,才会调用相应的处理函数

路由的使用

最简单的用法

也就是我们刚才说的那个示例代码

const express = require('express')
//创建web服务器
const app = express()
//挂载路由
app.get('/',(req,res)=>{res.send('hello world')})
app.post('/',(req,res)=>{res.send('别发了,我收到了')})
//启动web服务器
app.listen(80,()=>{
console.log('app is running at http://127.0.0.1')
})

但是这种方法我们一般很少使用, 你想想 , 加入我的项目结构很复杂 , 那岂不是需要些很多的app.get()app.post() , 整个代码的体量就会很大

模块化路由

为了方便对路由进行模块化的管理,Express不建议将路由直接挂载到app上 , 而是推荐将路由抽离为单位的模块 , 简单点说 , 就是我们把挂载路由的部分代码单独放到一个.js文件中

  1. 创建路由模块对应的.js文件
  2. 调用express.Router()函数创建路由对象
  3. 向路由对象上挂载具体的路由
  4. 调用module.exports向外共享这个路由对象
  5. 使用app.use()函数注册路由模块

创建路由模块

var express = require('express')
var router = express.Router()
router.get('/user',(req,res) => {
res.send('Get User list')
})
router.post('/user',(req,res) => {
res.send('Post a request')
})
module.exports = router

注册路由

在项目文件.js 引入路由模块

// 导入路由模块
const userRouter = require('./Router.js')
// 使用app.use() 注册路由模块
app.use(userRouter)

这样访问http://127.0.0.1/user

为路由模块添加前缀

类似于前面所说的静态资源托管 , 路由模块也支持添加前缀的方式

//导入路由模块
const userRouter = require('./Router.js')
//调用app.use()注册路由模块 , 并添加统一的访问前缀 /api
app.use('/api',userRouter)

这样 , 当我们请求时,需要加上api前缀,http://127.0.0.1/api/user

Express中间件

中间件的概念

什么是中间件

特指业务流程的中间处理环节

中间价一般都输输入和输出, 上一级的输出作为下一级的输入

举个现实生活中的例子

image-20220429194046688

Express中间件的调用流程

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

image-20220429194223562

Express中间件的格式

Express中间件的本质就是一个function处理函数

image-20220429203824176

前面的路由函数的形参列表只包含req,res两个参数,

在中间件函数的形参列表中, 必须包含next参数

next函数的作用

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

next就好像是C语言中的一个指针 , 他指向下一个中间件或路由

image-20220429204051311

Express中间件初体验

定义中间件函数

可以通过如下的方式, 定义一个最简单的中间件函数

//常量mw 所指向的,就是一个中间件函数
const mw = function(req, res ,next){
console.log('这是一个最简单的中间件函数')
//注意, 在当前中间件的业务处理完毕后, 必须调用next()方法
//表示把流转关系转交给下一个中间件或路由
next();
}

全局生效的中间件

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

通过调用app.use(中间件函数) , 即可定义一个全局生效的中间件函数

//常量mw 所指向的,就是一个中间件函数
const mw = function(req, res ,next){
console.log('这是一个最简单的中间件函数')
//注意, 在当前中间件的业务处理完毕后, 必须调用next()方法
//表示把流转关系转交给下一个中间件或路由
next();
}
app.use(mw)

定义全局中间件的简化形式

//全局生效的中间件的简化形式
app.use(function(req,res,next){
console.log('这是一个中间件函数')
next()
})

中间件的作用

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

image-20220429210318783

定义多个全局中间件

当我们定义了多个全局中间件时 , 程序会按照全局中间件的顺序来依次处理

const express = require('express')
const app = express()
//这是第一个全局中间件
app.use((req,res,next)=>{
console.log('经过第一次处理')
next()
})
//这是第二个全局中间件
app.use((req,res,next)=>{
console.log('经过第二次处理')
next()
})
app.get('/user',(req,res)=>{
res.send('User page')
})
app.listen(80,()=>{
console.log('app is running')
})

局部生效的中间件

不使用app.use()定义的中间件 , 叫做局部生效的中间件

想要在哪个地方使用中间件函数, 直接在哪个地方调用即可

const express = require('express')
const app = express()
//定义一个中间件函数
const m1 = (req,res , next)=>{
console.log('我是一个局部生效的中间件')
next()
}
//我们想要这个中间件函数只在第一个路由中生效
//只需要将这个中间件函数放在路由处理函数前面的参数位置即可
app.get('/',m1,(req,res)=>{
res.send('Home page')
})
app.get('/user',(req,res)=>{
res.send('user page')
})
app.listen(80,()=>{
console.log('app is running')
})

定义多个局部中间件

可以在路由中, 通过如下两种等价的方式 , 使用多个局部中间件

//以下两种写法是完全等价的
app.get('/',m1,m2,(req,res)=>{res.send('HOme page')})
app.get('/',[m1,m2],(req,res)=>{res.send('HOMe Page')})

也是按照局部中间件的顺序去调用的

了解中间件的5个使用注意事项

  • 一定要在路由之前注册中间件 , 因为整个程序是从前到后执行的 , 如果中间价在路由之后, 那么久直接响应给客户端了 , 中间件就没有用了
  • 执行完中间件的处理后, 千万别忘记调用next()函数
  • 调用完next()之后, 就不要再写多余的代码了 , 因为执行到next()的时候,就已经转入下一个中间件或者路由了, next()后面的代码就屁用没有了

中间件的分类

为了方便使用 , Express官方把常见的中间件用法 , 分成了5大类 , 分别是

  • 应用级别的中间件
  • 路由级别的中间件
  • 错误级别的中间件
  • Express 内置的中间件
  • 第三方的中间件

应用级别的中间件

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

app.use((req,res,next)=>{
console.log('经过第一次处理')
next()
})
app.get('/',[m1,m2],(req,res)=>{
res.send('Home page')
})

路由级别的中间件

绑定到express.Router()实例上的中间件 , 叫做路由级别的中间件 , 他的用法和应用级别中间件相同.

只不过,应用级别的中间件是绑定到app实例上的 , 路由级别的中间件是绑定到router实例上,

const router = express.Router()
//路由级别的中间件
router.use(function(req,res,next){
console.log('到达路由级别中间件')
next()
})
app.use(router)

错误级别的中间件

作用: 专门用来捕获项目中发生的异常 , 从而防止项目异常崩溃

当项目中发生错误时 , 就会直接进入到错误级别的中间件 进行处理

格式 : 错误级别的中间件处理函数 , 必须有4个形参 , 且这四个形参的顺序不能乱,分别是(err,req,res,next)

app.get('/',(req,res)=>{
throw new Error('服务器内部发生错误')
res.send('Home page')
})
app.use((err,req,res,next)=>{
console.log('发生了错误:'+err.message)
res.send('Error:'+err.message)
})

项目发生错误后, 直接进入错误级别中间件

注意 : 错误级别中中间件 , 必须注册在所有路由之后 , 否则无效

Express内置的中间件

自express4.16.0版本之后 , Express内置了3个常用的中间件 , 极大地提高了Express项目的开发效率和体验

  • express.static 快速静态托管静态资源的内置中间件 , 例如 , HTML 文件 , 图片 ,css样式
  • express.json 解析JSON格式的请求体数据
  • express.urlencoded 解析URL-encoded格式的请求体数据

使用时只需要配置一下中间件即可

//配置全局中间件
//配置解析application/json格式数据的内置中间件
app.use(express.json())
//配置解析application/x-www-form-urlencoded格式数据的内置中间件
app.use(express.urlencoded({extended: false}))

看一个实例 , express.json的使用 , 向服务器发送一个json格式的数据

const express = require('express')
const app = express()
//配置全局中间件
// 配置解析application/json格式数据的内置中间件
//通过这个中间件, 就会将解析出来的数据挂载到req.body身上
app.use(express.json())
app.post('/user', (req, res) => {
//在服务器 , 可以使用req.body这个属性
//来接收客户端发送过来的请求体数据
//默认情况下 , 如不配置解析表单数据的中间件 ,
//则req.body默认等于undefined
console.log(req.body)
res.send('ok')
})
app.listen(80, () => {
console.log('app is running')
})

express.urlencoded的使用, 向服务器发送一个x-www-form-urlencoded格式的数据

const express = require('express')
const app = express()
//配置全局中间件
//配置解析application/x-www-form-urlencoded格式数据的内置中间件
//需要传一个配置对象
//将解析出来的数据挂载到req.body身上
app.use(express.urlencoded({extended: false}))
app.post('/book',(req,res) =>{
//在服务器端 , 可以通过req.body来获取json格式的表单数据和url-encoded格式的数据
//url-encoded是一种键值对形式的数据
console.log(req.body)
res.send('OK')
})
app.listen(80, () => {
console.log('app is running')
})

第三方的中间件

非 Express 官方内置的,而是由第三方开发出来的中间件,叫做第三方中间件。在项目中,大家可以按需下载并配置第三方中间件,从而提高项目的开发效率。

image-20220430123214310

注意: express内置的express.urlencoded中间件就是基于body-parser这个第三方中间件进一步封装出来的 , 4.16之后的版本就不需要安装上面提到的那个插件了

自定义中间件

需求描述与实现步骤

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

实现步骤

  1. 定义中间件
  2. 监听 req 的 data 事件
  3. 监听 req 的 end 事件
  4. 使用 querystring 模块解析请求体数据
  5. 将解析出来的数据对象挂载为 req.body
  6. 将自定义中间件封装为模块

定义中间件

使用app.use()来配置全局中间件

app.use((req,res,next){
//.....
})

监听req的data事件

在中间件中国 , 需要监听req对象的data事件, 来获取客户端发送到服务器的数据

如果数据量比较大 , 无法一次性发送完毕 , 则客户端会把数据切割后 , 分批发送到服务器

所以data事件可能会触发多次 , 每一次触发data事件 , 获取到数据只是完整数据的一部分, 需要手动对接收到的数据进行拼接

//待拼接的字符串
let str = ''
req.on('data',(chunk) =>{
str += chunk;
})

监听req的end事件

当请求体数据接收完毕之后, 会自动触发req的end 事件

因此我们可以在req的end事件中 , 拿到并处理完整的请求体数据

使用querystring模块解析请求体数据

Node.js内置了一个querystring模块 , 专门用来处理查询字符串, 通过这个模块提供的parse()函数 , 可以轻松把查询字符串, 解析成对象的格式

req.on('end',()=>{
//在str中存放的是完整的数
// console.log(str)
//把字符串解析为对象格式
const body = qs.parse(str)
console.log(body)
})

整体代码

const express = require('express')
const res = require('express/lib/response')
const qs = require('querystring')
const app = express()
//这是解析表单数据的中间件
app.use((req,res,next)=>{
//定义中间件的逻辑处理
//待拼接的字符串
let str = ''
req.on('data',(chunk) =>{
str += chunk;
})
req.on('end',()=>{
//在str中存放的是完整的数
// console.log(str)
//把字符串解析为对象格式
const body = qs.parse(str)
console.log(body)
req.body = body
next()
})
})
app.post('/user',(req,res)=>{
res.send(req.body)
})
app.listen(80,()=>{
console.log('app is running')
})

将解析出来的数据对象挂载为req.body

因为整个过程中, 中间件和路由共享同一份reqres , 所以可以在上游中间件中挂载自定义属性

red.body = body

将自定义中间件封装为模块

为了优化代码结构, 我们把自定义的中间件函数 , 封装为独立的模块, 向外共享这个函数

const qs = require('querystring')
//定义一个中间件函数
my_urlencoded = (req,res,next)=>{
//定义中间件的逻辑处理
//待拼接的字符串
let str = ''
req.on('data',(chunk) =>{
str += chunk;
})
req.on('end',()=>{
//在str中存放的是完整的数
// console.log(str)
//把字符串解析为对象格式
const body = qs.parse(str)
console.log(body)
req.body = body
next()
})
}
module.exports = {
my_urlencoded
}

声明

此篇文章 根据黑马程序员视频教程和课件 整理而来
黑马程序员Node.js视频教程
https://www.bilibili.com/video/BV1a34y167AZ?spm_id_from=333.337.search-card.all.click

posted @   秋天Code  阅读(82)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 使用C#创建一个MCP客户端
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· ollama系列1:轻松3步本地部署deepseek,普通电脑可用
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示