【nodejs-03】黑马nodejs学习笔记03-express中间件与跨域

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

文章目录


接上篇 【nodejs-02】黑马nodejs学习笔记02-express

3.Express中间件

3.1 中间件的概念

1. 什么是中间件

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

2. 现实生活中的例子

在处理污水的时候一般都要经过三个处理环节从而保证处理过后的废水达到排放标准。
处理污水的这三个中间处理环节就可以叫做中间件

3. Express 中间件的调用流程

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

在这里插入图片描述

4. Express 中间件的格式

Express 的中间件本质上就是一个 function 处理函数Express 中间件的格式如下
注意中间件函数的形参列表中必须包含 next 参数。而路由处理函数中只包含 req 和 res。

在这里插入图片描述
5. next 函数的作用
next 函数是实现多个中间件连续调用的关键它表示把流转关系转交给下一个中间件或路由。
在这里插入图片描述

3.2 Express 中间件的初体验

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

//定义一个最简单的中间件
const mw = function (req, res, next) {
  console.log("这是最简单的一个中间件函数");
  // 注意:在当前中间件的业务处理完毕后,必须调用next()函数
  // 表示吧流转关系转交给下一个中间件或者路由
  next()
}

2. 全局生效的中间件
客户端发起的任何请求到达服务器之后都会触发的中间件叫做全局生效的中间件。
通过调用 app.use(中间件函数)即可定义一个全局生效的中间件示例代码如下

const mw = function (req, res, next) {
  console.log("这是最简单的一个中间件函数")
  next()
}
//注册全局生效的中间件
app.use(mw)

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

//全局生效的中间件
app.use((req,res,next)=>{
	console.log("这是最简单的一个中间件函数")
 	next()
})

4. 中间件的作用

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

5. 定义多个全局中间件

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

aa.use((req, res, next)=> {
  console.log("这是第一个中间件");
  next()
})
app.use( (req, res, next) =>{
  console.log("这是第2个中间件");
  next()
})
app.get('/', [mw1, mw2], function (req, res) {
  res.send('home page')
})

6. 局部生效的中间件

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

// 不使用app.use()定义的中间件叫做局部生效的中间件
const express = require('express')
const app = express()

//定义一个最简单的中间件
const mw = function (req, res, next) {
  //获取到请求到达服务器的时间
  const time = Date.now()
  //为req对象挂载自定义属性,从而把时间共享给后续的所有路由
  req.startTime = time
  next()
}
//只在/中生效的中间件
app.get('/', mw, (req, res) => {
  res.send("home page" + req.startTime)
})

// /user中req.startTime无定义内容
app.get('/user', (req, res) => {
  res.send("User page" + req.startTime)
})
//使用postman进行路由的调用


app.listen(80, () => {
  console.log("express server running at http://127.0.0.1")
})

7. 定义多个局部中间件

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

const express = require('express')
const app = express()
//定义局部中间件
const mw1 = function (req, res, next) {
  console.log("这是第一个中间件");
  next()
}
const mw2 = function (req, res, next) {
  console.log("这是第2个中间件");
  next()
}
app.get('/', [mw1, mw2], function (req, res) {
  res.send('home page')
})
//或者
// app.get('/', mw1, mw2, function (req, res) {
//   res.send('home page')
// })

app.listen(80, () => {
  console.log("express server running at http://127.0.0.1")
})

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

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

3.3 中间件的分类

为了方便大家理解和记忆中间件的使用Express 官方把常见的中间件用法分成了 5 大类分别是
① 应用级别的中间件
② 路由级别的中间件
③ 错误级别的中间件
④ Express 内置的中间件
⑤ 第三方的中间件
1. 应用级别的中间件
通过 app.use() app.get() app.post() 绑定到 app 实例上的中间件叫做应用级别的中间件代码示例如下

//应用级别的中间件全局中间件
app.use((req,res,next)=>{
	next()
})
//应用级别的中间件局部中间件
app.get('/',mw1,(req,res)=>{
	res.send('Home page')
})

2. 路由级别的中间件
绑定到 express.Router() 实例上的中间件叫做路由级别的中间件。它的用法和应用级别中间件没有任何区别。只不过应用级别中间件是绑定到 app 实例上路由级别中间件绑定到 router 实例上代码示例如下

const express = require('require')
const app = express()
const router = express.Router()

//路由级别的中间件
router.use((req,res,next)=>{
	console.log('Time:',Date.now())
	next()
})
app.use('/',router)

3. 错误级别的中间件
错误级别中间件的作用专门用来捕获整个项目中发生的异常错误从而防止项目异常崩溃的问题。
格式错误级别中间件的 function 处理函数中必须有 4 个形参形参顺序从前到后分别是 (err, req, res, next)。
注意错误级别的中间件必须注册在所有路由之后

const express = require('express')
const app = express()

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('home page' + err.message)
})



app.listen(80, () => {
  console.log("express server running at http://127.0.0.1")
})

4. Express内置的中间件

自 Express 4.16.0 版本开始Express 内置了 3 个常用的中间件极大的提高了 Express 项目的开发效率和体验
① express.static 快速托管静态资源的内置中间件例如 HTML 文件、图片、CSS 样式等无兼容性
② express.json 解析 JSON 格式的请求体数据有兼容性仅在 4.16.0+ 版本中可用
③ express.urlencoded 解析 URL-encoded 格式的请求体数据有兼容性仅在 4.16.0+ 版本中可用

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

5. 第三方的中间件

非 Express 官方内置的而是由第三方开发出来的中间件叫做第三方中间件。在项目中大家可以按需下载并配置第三方中间件从而提高项目的开发效率。
例如在 express@4.16.0 之前的版本中经常使用 body-parser 这个第三方中间件来解析请求体数据。使用步骤如下
① 运行 npm install body-parser 安装中间件
② 使用 require 导入中间件
③ 调用 app.use() 注册并使用中间件

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

3.4 自定义中间件

1. 需求描述与实现步骤

自己手动模拟一个类似于 express.urlencoded 这样的中间件来解析 POST 提交到服务器的表单数据。实现步骤
① 定义中间件
② 监听 req 的 data 事件
③ 监听 req 的 end 事件
④ 使用 querystring 模块解析请求体数据
⑤ 将解析出来的数据对象挂载为 req.body
⑥ 将自定义中间件封装为模块

2. 定义中间件
使用 app.use() 来定义全局生效的中间件代码如下

app.use((req,res,next)=>{
//中间业务逻辑

})

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

//定义变量用来存储客户端发送过来的请求数据
let str=''
//监听req对象的data事件客户端发送过来的请求体数据
req.on('data',(chunk)=>{
	//拼接请求体数据隐式转换为字符串
	str+=chunk
})

4. 监听 req 的 end 事件
当请求体数据接收完毕之后会自动触发 req 的 end 事件。
因此我们可以在 req 的 end 事件中拿到并处理完整的请求体数据。示例代码如下

//监听req对象的end事件请求发送完毕后自动触发
 req.on('end', () => {
    next()
    // console.log(body);
  })

})

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

//4.1导入queryString模块把字符串解析成对象格式
const qs = require("querystring")
//4. Node.js 内置了一个 querystring 模块专门用来处理查询字符串。通过这个模块提供的 parse() 函数可以轻松把查询字符串解析成对象的格式。
    const body = qs.parse(str)//调用qs.parse()方法,把查询的字符串解析为对象

6. 将解析出来的数据对象挂载为 req.body
上游的中间件和下游的中间件及路由之间共享同一份 req 和 res。因此我们可以将解析出来的数据挂载为 req 的自定义属性命名为 req.body供下游使用。示例代码如下

req.on('end', () => {
    const body = qs.parse(str)//调用qs.parse()方法,把查询的字符串解析为对象
    req.body = body //将解析出来的请求体对象,挂载为req.body属性,供上游和写有的中间件及路由间使用,共享同一份req和res
    next()
  })

})

7. 将自定义中间件封装为模块
为了优化代码的结构我们可以把自定义的中间件函数封装为独立的模块示例代码如下

// custom-body-parser.js 模块中的代码
 const qs = require( 'querystring')
3function bodyParser(reqresnext)  /* 省略其它代码 */ }4 module.exports = bodyParser // 向外导出解析请求体数据的中间件函数
 //分割线--
 // 1导入自定义的中间件模块
const myBodyParser = require( 'custom-body-parser')
 // 2.注册自定义的中间件模块
 app.use(myBodyParser)

完整示例代码

// 自己手动模拟一个类似于 express.urlencoded 这样的中间件来解析 POST 提交到服务器的表单数据。
// 实现步骤
// ① 定义中间件
// ② 监听 req 的 data 事件
// ③ 监听 req 的 end 事件
// ④ 使用 querystring 模块解析请求体数据
// ⑤ 将解析出来的数据对象挂载为 req.body
// ⑥ 将自定义中间件封装为模块
const express = require('express')
const app = express()
//4.1导入queryString模块把字符串解析成对象格式
const qs = require("querystring")

//解析表单数据的中间件
app.use((req, res, next) => {
  // 定义中间件具体的业务逻辑
  // 需要监听 req 对象的 data 事件来获取客户端发送到服务器的数据。
  // 如果数据量比较大无法一次性发送完毕则客户端会把数据切割后分批发送到服务器。所以 data 事件可能会触发多次每一次触发 data 事件时获取到数据只是完整数据的一部分需要手动对接收到的数据进行拼接。
  //1.定义一个str字符串,专门存放客户端发送过来的请求体数据
  let str = ""
  //2.监听req的data事件
  req.on('data', (chunk) => {
    str += chunk
  })


  //3.当请求体数据接收完毕之后,就会自动触发req的end事件
  req.on('end', () => {
    //str中存放的是完整的请求体数据
    // console.log(str);
    // TODO:把字符串格式的请求体数据解析成对象格式
    //4. Node.js 内置了一个 querystring 模块专门用来处理查询字符串。通过这个模块提供的 parse() 函数可以轻松把查询字符串解析成对象的格式。
    const body = qs.parse(str)//调用qs.parse()方法,把查询的字符串解析为对象
    req.body = body //将解析出来的请求体对象,挂载为req.body属性,供上游和写有的中间件及路由间使用,共享同一份req和res
    next()
    // console.log(body);
  })

})
app.post('/user', (req, res) => {
  // res.send("ok")
  res.send(req.body)
})


app.listen(80, () => {
  console.log("express server running at http://127.0.0.1")
})

模块化拆分

const express = require('express')
const app = express()


//1.导入自己封装的中间件模块
const customBodyParser = require('./14.custom-body-parser')

//2.将自定义的中间件函数组测为全局可用的中间件
app.use(customBodyParser)

app.post('/user', (req, res) => {
  // res.send("ok")
  res.send(req.body)
})


app.listen(80, () => {
  console.log("express server running at http://127.0.0.1")
})

4.使用Express写接口

4.1 创建基本的服务器

// 导入 express 模块
 const express = require( 'express')
// 创建 express 的服务器实例
 const app = express( )
// write your code here...
 // 调用 app.listen 方法指定端口号并启动web服务器
 app.listen(80, function () {
10console.log('Express server running at http://127.0.0.1')
})

4.2 创建 API 路由模块

 // apiRouter.js[路由模块)
 const express = require( 'express')
 const apiRouter = express.Router()
 // bind your router here...
 module.exports = apiRouter

// app.js [导入并注册路由模块]
 const apiRouter = require('./apiRouter.js' )
 app.use('/api',apiRouter)

4.3 编写 GET 接口

apiRouter.get('/get' ,(req, res) => (
// 1.获取到客户端通过查询字符串发送到服务器的数据
const query = req.query
// 2.调用 res.send() 方法把数据响应给客户端
res.send((
	status: 0,
	//状态0 表示成功1 表示失败
	msg:'GET请求成功!',// 状态描述
	data: query
	//需要响应给客户端的具体数据
})

4.4 编写 POST 接口

apiRouter.post('/post',(req, res) => (
// 1.获取到客户端通过查询字符串发送到服务器的数据
const query = req.body
// 2.调用 res.send() 方法把数据响应给客户端
res.send((
	status: 0,
	//状态0 表示成功1 表示失败
	msg:'POST请求成功!',// 状态描述
	data: body
	//需要响应给客户端的具体数据
})

注意如果要获取 URL-encoded 格式的请求体数据必须配置中间件 app.use(express.urlencoded({ extended: false }))

4.5 CORS 跨域资源共享

1. 接口的跨域问题

刚才编写的 GET 和 POST接口存在一个很严重的问题不支持跨域请求
解决接口跨域问题的方案主要有两种
CORS主流的解决方案 推荐使用
JSONP有缺陷的解决方案只支持 GET 请求

2. 使用 cors 中间件解决跨域问题

cors 是 Express 的一个第三方中间件。通过安装和配置 cors 中间件可以很方便地解决跨域问题。
使用步骤分为如下 3 步
① 运行 npm install cors 安装中间件
② 使用 const cors = require(‘cors’) 导入中间件
③ 在路由之前调用 app.use(cors()) 配置中间件

3. 什么是 CORS
CORS Cross-Origin Resource Sharing跨域资源共享由一系列 HTTP 响应头组成这些 HTTP 响应头决定浏览器是否阻止前端 JS 代码跨域获取资源
浏览器的同源安全策略默认会阻止网页“跨域”获取资源。但如果接口服务器配置了 CORS 相关的 HTTP 响应头就可以解除浏览器端的跨域访问限制
在这里插入图片描述
4. CORS 的注意事项

① CORS 主要在服务器端进行配置。客户端浏览器无须做任何额外的配置即可请求开启了 CORS 的接口。
② CORS 在浏览器中有兼容性。只有支持 XMLHttpRequest Level2 的浏览器才能正常访问开启了 CORS 的服务端接口例如IE10+、Chrome4+、FireFox3.5+。

5. CORS 响应头部 - Access-Control-Allow-Origin
如果指定了 Access-Control-Allow-Origin 字段的值为通配符 *表示允许来自任何域的请求示例代码如下

res.setHeader('Access-Control-Allow-Origin','*')

6. CORS 响应头部 - Access-Control-Allow-Headers
默认情况下CORS 支持客户端向服务器发送如下的 9 个请求头
Accept、Accept-Language、Content-Language、DPR、Downlink、Save-Data、Viewport-Width、Width 、Content-Type 值仅限于 text/plain、multipart/form-data、application/x-www-form-urlencoded 三者之一
如果客户端向服务器发送了额外的请求头信息则需要在服务器端通过 Access-Control-Allow-Headers 对额外的请求头进行声明否则这次请求会失败

// 允许客户端额外向服务器发送 Content-Type 请求头和 X-Custom-Header 请求头
// 注意: 多个请求头之间使用英文的逗号进行分割
res.setHeader('Access-Control-Allow-Headers ''Content-Type X-Custom-Header')

7. CORS 响应头部 - Access-Control-Allow-Methods
默认情况下CORS 仅支持客户端发起 GET、POST、HEAD 请求。
如果客户端希望通过 PUT、DELETE 等方式请求服务器的资源则需要在服务器端通过 Access-Control-Alow-Methods来指明实际请求所允许使用的 HTTP 方法。示例代码如下:

// 只允许 POST、GET、DELETE、HEAD 请求方法
res,setHeader('Access-Control-A1low-Methods','POSTGETDELETEHEAD')
 // 允许所有的 HTTP 请求方法
 res,setHeader('Access-Control-A1low-Methods','*')

8. CORS请求的分类
客户端在请求 CORS 接口时根据 请求方式和请求头的不同可以将 CORS 的请求分为两大类分别是
① 简单请求
② 预检请求

9. 简单请求
同时满足以下两大条件的请求就属于简单请求
请求方式GET、POST、HEAD 三者之一
HTTP 头部信息不超过以下几种字段无自定义头部字段、Accept、Accept-Language、Content-Language、DPR、Downlink、Save-Data、Viewport-Width、Width 、Content-Type只有三个值application/x-www-form-urlencoded、multipart/form-data、text/plain

10. 预检请求

只要符合以下任何一个条件的请求都需要进行预检请求
① 请求方式为 GET、POST、HEAD 之外的请求 Method 类型
② 请求头中包含自定义头部字段
③ 向服务器发送了 application/json 格式的数据

在浏览器与服务器正式通信之前浏览器会先发送 OPTION 请求进行预检以获知服务器是否允许该实际请求所以这一次的 OPTION 请求称为“预检请求”。服务器成功响应预检请求后才会发送真正的请求并且携带真实数据

11. 简单请求和预检请求的区别

简单请求的特点客户端与服务器之间只会发生一次请求
预检请求的特点客户端与服务器之间会发生两次请求OPTION 预检请求成功之后才会发起真正的请求

4.6 JSONP 接口

1. 回顾 JSONP 的概念与特点

概念浏览器端通过 script 标签的 src 属性请求服务器上的数据同时服务器返回一个函数的调用。这种请求数据
的方式叫做 JSONP。
特点
① JSONP 不属于真正的 Ajax 请求因为它没有使用 XMLHttpRequest 这个对象。
② JSONP 仅支持 GET 请求不支持 POST、PUT、DELETE 等请求。

2. 创建 JSONP 接口的注意事项

如果项目中已经配置了 CORS 跨域资源共享为了防止冲突必须在配置 CORS 中间件之前声明 JSONP 的接口。否则JSONP 接口会被处理成开启了 CORS 的接口。示例代码如下:

// 优先创建 JSONP 接口[这个接口不会被处理成 CORS 接口]
 app.get('/api/jsonp',(reqres) => ( })
// 再配置 CORS 中间件[后续的所有接口都会被处理成 CORS 接口]
 app.use(cors())
// 这是一个开启了 CORS 的接口
app.get('/api/get', (req, res) => [ })

3. 实现 JSONP 接口的步骤

获取客户端发送过来的回调函数的名字
得到要通过 JSONP 形式发送给客户端的数据
③ 根据前两步得到的数据拼接出一个函数调用的字符串
④ 把上一步拼接得到的字符串响应给客户端的 script>标签进行解析执行

4. 实现 JSONP 接口的具体代码

app.get('/api/jsonp',(req,res)=>{
	//1.获取客户端发送过来的回调函数的名字
	const funName = req.query.callback
	//2.得到要通过JSON形式发送给客户端的数据
	const data = {name:'zs',age:22}
	//3.根据前两步得到的数据拼接出一个函数调用的字符串
	const scriptStr= `${funName}(${JSON.stringify(data)})`
	//4.把上一步拼接得到的字符串响应给客户端的<script>标签进行解析
	res.send(scriptStr)
})

5. 在网页中使用 jQuery 发起 JSONP 请求
调用 $.ajax() 函数提供 JSONP 的配置选项从而发起 JSONP 请求示例代码如下

$('#btnJSONP').on('click',function(){
	$.ajax({
		method:'GET',
		url:'http://127.0.0.1/api/jsonp',
		dataType:'jsonp',
		success:function(res){
			console.log(res)
		}
	})
})
阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: NodeJS