背景
中间件的应用十分广泛。
场景1:
我们需要记录每个接口调用时间时,就可以使用中间件函数。使用中间件函数后,在调用每个接口时,在不修改之前接口代码的基础上,可以自动打印对应的时间。
场景2
路由也是中间件
:只不过路由会限制对中间件的规则有限制。
常用中间件
- 打印接口调用时间
- 打印接口调用ip
- 打印接口耗时
app.use(async (req, res, next)=>{
let startTime = Date.now();
console.log("ip:" + req.ip + "------- interface:" + req.originalUrl + "----- time:" + currentTime)
// 把流转关系转交给下一个中间件或者路由
next()
let endTime = Date.now()
console.log(req.originalUrl + "-------- 执行时间:" + (endTime - startTime)/1000 + "s")
})
中间件
Express中间件和AOP面向切面编程是一个意思,不需要修改自己的代码,以此来扩展或者处理一些功能。
将日志记录,性能统计,安全控制,事务处理, 异常处理等代码从业务逻辑代码 中划分出来,通过对这些行为的分离,我们希望望可以将它们独立到非指导业务逻辑的方法中,进而改变这些行为的时候不影响业上务逻辑的代码。
- 当一个请求到达Express的服务器后,可以连续调用多个中间件,从而对这次请求进行预处理。
- 中间件函数的形参列表中,必须包含next参数。而路由处理函数中只能包括req以及res。
- next()函数是实现多个中间件连续调用的关键,他表示把流转关系交给下一个中间件或路由。
- 多个中间件,共用一个req、res。
- 请求到来之后按照代码从上到下的顺序进行匹配,因此中间件需要在路由之前声明。
中间件可实现功能
1、执行任何代码
2、修改request或者response响应对象
3、结束请求相应周期
4、调用下一个中间件
如果从路由器中间件堆栈跳过其余中间件功能,请调用next('route')
将控制权传递给下一个路由。
注意:next('route')
仅在app.METHOD()
或router.METHOD()
函数加载的中间件函数中有效。
中间件的使用
const express = require("express");
const app = express()
// 定义一个简单的中间件 常量mw所指
const mw = function (req, res, next) {
console.log("这种最简单的中间件函数")
// 把流转关系转交给下一个中间件或者路由
next()
}
app.use(mw)
app.get('/',(req, res)=>{
console.log('Home page')
res.send("Home page")
})
app.get('/user',(req, res)=>{
console.log('user page')
res.send("user page")
})
app.listen(8080,()=>{
console.log("express start on 8080")
})
请求结果:
这种最简单的中间件函数
Home page
中间件的简化生成
中间件的调用可以这样简化:
const express = require("express");
const app = express()
// 定义全局中间件的简化形式
app.use((req, res, next)=>{
console.log("简化的全局中间件...")
next()
})
app.get('/',(req, res)=>{
console.log('Home page')
res.send("Home page")
})
app.get('/user',(req, res)=>{
console.log('user page')
res.send("user page")
})
app.listen(8080,()=>{
console.log("express start on 8080")
})
中间件挂载方法或者属性
const express = require("express");
const app = express()
/**
* 中间件的上游挂载属性,下游可以访问到挂载的属性。
*/
// 定义全局中间件的简化形式
app.use((req, res, next)=>{
console.log("简化的全局中间件...")
/**
* 为req对象 挂载自定义属性(接受请求的时间),从而把时间共享给后面的所有路由
*/
req.startTime = new Date()
next()
})
app.get('/',(req, res)=>{
console.log('Home page')
res.send("Home page " + req.startTime)
})
app.get('/user',(req, res)=>{
console.log('user page')
res.send("user page " + req.startTime)
})
app.listen(8080,()=>{
console.log("express start on 8080")
})
多个中间件
express定义多个中间件时,会按照定义的先后顺序在多个中间件间进行信息的流转。
const express = require("express");
const app = express()
/**
* 中间件的上游挂载属性,下游可以访问到挂载的属性。
* 定义全局中间件的简化形式
*/
app.use((req, res, next)=>{
console.log("简化的全局中间件...")
/**
* 为req对象 挂载自定义属性(接受请求的时间),从而把时间共享给后面的所有路由
*/
req.startTime = new Date()
next()
})
/**
* 第二个全局中间件
* 如果挂载多个全局中间件,express会按照定义的顺序前后调用多个中间件
*/
app.use((req, res, next)=>{
console.log("第二个中间件")
next()
})
app.get('/',(req, res)=>{
console.log('Home page')
res.send("Home page " + req.startTime)
})
app.get('/user',(req, res)=>{
console.log('user page')
res.send("user page " + req.startTime)
})
app.listen(8080,()=>{
console.log("express start on 8080")
})
打印的日志信息如下:
express start on 8080
简化的全局中间件...
第二个中间件
Home page
局部生效的中间件
const express = require("express");
const app = express()
// 定义一个简单的中间件 常量mw所指
const mw = function (req, res, next) {
console.log("调用了局部生效的中间件")
// 把流转关系转交给下一个中间件或者路由
next()
}
app.get('/',mw,(req, res)=>{
console.log('Home page')
res.send("Home page " + req.startTime)
})
app.get('/user',(req, res)=>{
console.log('user page')
res.send("user page " + req.startTime)
})
app.listen(8080,()=>{
console.log("express start on 8080")
})
多个局部生效的中间件
使用多个局部中间件的两种方式
app.get(‘/’, mw, mw2, (req, res)
app.get(‘/user’, [mw, mw2], (req, res)
const express = require("express");
const app = express()
// 定义一个简单的中间件 常量mw所指
const mw = function (req, res, next) {
console.log("调用了局部生效的中间件")
// 把流转关系转交给下一个中间件或者路由
next()
}
// 定义一个简单的中间件 常量mw所指
const mw2 = function (req, res, next) {
console.log("调用了第二个局部生效的中间件")
// 把流转关系转交给下一个中间件或者路由
next()
}
/**
* 使用多个局部中间件的两种方式
* app.get('/', mw, mw2, (req, res)
* app.get('/user', [mw, mw2], (req, res)
*/
app.get('/', mw, mw2, (req, res) => {
console.log('Home page')
res.send("Home page " + req.startTime)
})
app.get('/user', [mw, mw2], (req, res) => {
console.log('user page')
res.send("user page " + req.startTime)
})
app.listen(8080, () => {
console.log("express start on 8080")
})
中间件分类
应用级别的中间件
通过app.use()(全局)或者app.get()(局部),绑定到app实例上的中间件,叫做应用级别的中间件。
路由级别的中间件
绑定到express.Router()实例上的中间件,叫做路由级别的中间件。他的用法和应用级别中间件没有任何区别。只不过,应用级别中间件时绑定到app实例上,路由级别中间件绑定到router实例上。
错误级别的中间件
错误级别的中间件,必须有四个形参,err req res next
错误级别中间件,一定要注册在所有的路由之后。
const express = require('express');
const app = express();
// 不添加错误路由的情况下 人为添加错误
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)
})
app.listen(8080, () => {
console.log(' 启动在8080 ')
})
404中间件
通常在所有的路由之后配置处理404的内容。
请求来到后匹配所有的中间件以及所有的路由,然后发现客户端网址对应的地址无法匹配,进而进入404处理环节,返回404。因此404中间件需要放在所有的中间件(非错误中间件)以及路由之后。
Express内置的中间件
> express.static
> express.json(4.16.0+)
> express.urlencoded(4.16.0+)
express.json()中间件的使用
const express = require('express');
const app = express();
// 除了错误级别的中间件,其他的中间件,必须在路由之前进行配置
// 通过express.json()这个中间件 解析表单中json格式的请求体数据,
app.use(express.json())
app.post('/user',(req, res)=>{
// 在服务器,可以通过req.body这个属性,来接收客户端发送过来的请求体数据
// 默认情况下,如果不配置解析表单数据的中间件,则req.body 默认等于 undefined
console.log(req.body)
res.send(' ok')
})
app.listen(8080, () => {
console.log(' 启动在8080 ')
})
express.urlencoded()中间件的使用
const express = require('express');
const app = express();
// 通过express.urlencoded()这个中间件 解析表单中的url-encoded格式的数据
app.use(express.urlencoded())
app.post('/user',(req, res)=>{
// 在服务器,可以通过req.body这个属性,来接收客户端发送过来的请求体数据
// 默认情况下,如果不配置解析表单数据的中间件,则req.body 默认等于 undefined
console.log(req.body)
res.send(' ok')
})
app.listen(8080, () => {
console.log(' 启动在8080 ')
})
第三方的中间件
body-parser
const express = require('express');
const parser = require('body-parser');
const app = express();
// 除了错误级别的中间件,其他的中间件,必须在路由之前进行配置
// 注意express 内置的express.urlencoded中间件 就是基于body-parser这个中间件进一步封装出来的
app.use(parser.urlencoded({extended : false}))
app.post('/user',(req, res)=>{
// 在服务器,可以通过req.body这个属性,来接收客户端发送过来的请求体数据
// 默认情况下,如果不配置解析表单数据的中间件,则req.body 默认等于 undefined
console.log(req.body)
res.send(' ok')
})
app.listen(8080, () => {
console.log(' 启动在8080 ')
})
自定义中间件
import qs from "querystring";
const bodyParser = (req,res,next)=>{
// 监听req的data事件,来获取客户端发送到服务端的数据
// 如果数据量比较大,无法一次发送完毕,客户端会将数据分割后,分批发送到服务器,所以data事件可能会触发多次。每触发
//一次,获取到数据只是完整数据的一部分,需要手动对接收到的数据进行拼接。
let str = ''
req.on('data',(chunk)=>{
str += chunk
})
// 当请求体数据接收完毕之后,会自动触发req的end事件
// 因此,我们可以在req的end事件中,
req.on('end',()=>{
console.log(str)
const body = qs.parse(str);
console.log(body)
// 将解析出来的数据对象挂载为req.body
req.body = body
next()
})
}
module.exports = bodyParser;
Express实现get post请求
创建express()类,使用router路由
const express = require("express");
const app = express()
app.use(express.urlencoded({extended : false}))
const router = require("./9")
app.use('/user',router)
app.listen(8080,()=>{
console.log("server start at 8080")
})
路由中,声明使用get请求
const express = require('express')
const router = express.Router()
router.get('/get',(req, res)=>{
// 通过req.query获取客户端通过查询字符串,发送到服务器的数据
let query = req.query;
// 调用res.send() 向服务端响应处理的结果
res.send({
status : 0,
msg : ' GET请求成功',
data: query
})
})
router.post('/post',(req, res)=>{
// 获取客户端发送到服务端的url-encoded数据
let body = req.body;
// 调用res.send() 把数据响应给客户端
res.send({
status : 0,
msg : ' Post 请求成功',
data: body
})
})
module.exports = router
跨域
需要在路由之前配置cors中间件,从而解决接口跨域的问题
const express = require("express");
const app = express()
var cors = require('cors');
/**
* 跨域问题的方案
* 1. cors 主流的解决方案 推荐使用
* 需要在路由之前配置cors中间件,从而解决接口跨域的问题
*/
app.use(cors())
// 配置中间件,才可以拿到urlencoded类型的数据
app.use(express.urlencoded({extended : false}))
const router = require("./9")
app.use('/user',router)
app.listen(8080,()=>{
console.log("server start at 8080")
})
cors响应头分三类
1.assess-control-allow-origin 可以控制允许哪些网站的跨域请求。
2. 默认情况下,cors仅支持客户端向服务端发送的9个请求头,如果客户端向服务端发送了额外的请求头信息,则需要在服务端,通过Assess-Control-Allow-Headers对额外的请求头进行声明,否则这次请求会失败。 多个请求头之前用英文逗号分割。
3. Assess-Control-Allow-Methods 默认情况下,cors仅支持客户端发起GET、Post、Head请求。如果希望支持Put、Delete等方式请求服务器的资源。则需要在服务器端指明。
cors响应头分三类
简单请求。
1.没有用到自定义的cors响应头。
客户端与服务器只会发生一次请求。
预检请求。
1.使用了自定义的cors响应头。
客户端与服务器之间会发生两次请求。OPTIONS预检成功后,才会发送真正的请求。