目录
中间件
中间件特指业务流程的中间处理环节。当一个请求到达 Express 的服务器之后,可以连续调用多个中间件,从而对这次请求进行预处理。
Express的中间件,本质上就是一个 function 处理函数,Express 中间件的格式如下:
注意:中间件函数的形参列表中,必须包含 next 参数。而路由处理函数中只包含req和res。
next函数是实现多个中间件连续调用的关键,它表示把流转关系转交给下一个中间件或路由。
中间件函数使用
const express = require('express')
const app = express()
// 定义一个最简单的中间件函数
const thing = function(req,res,next){
console.log('这是最简单的中间件函数');
// 把流转关系,转交给下一个中间件或路由
next()
}
app.listen(80,()=>{
console.log('express server running at http://127.0.0.1');
})
全局生效的中间件:客户端发起的任何请求,到达服务器之后,都会触发中间件,叫做全局生效的中间件。通过调用 app.use(中间件函数) ,即可定义一个全局生效的中间件,如下:
const express = require('express')
const app = express()
// 定义一个最简单的中间件函数
const thing = function(req,res,next){
console.log('这是最简单的中间件函数');
// 把流转关系,转交给下一个中间件或路由
next()
}
// 将 thing 注册为全局生效的中间件
app.use(thing)
app.get('/',(req,res)=>{
console.log('调用了 / 这个路由');
res.send('Home page')
})
app.get('/user',(req,res)=>{
res.send('User page')
})
app.listen(80,()=>{
console.log('express server running at http://127.0.0.1');
})
先将请求交给中间件,中间件执行过后,把流转关系转交给下一个中间件或路由。
全局中间件简化形式:我们注册全局中间件时也可以采用简化形式。
// 定义一个简化的中间件函数
app.use(function(req,res,next){
console.log('这是最简单的中间件函数');
// 把流转关系,转交给下一个中间件或路由
next()
})
定义多个全局中间件:可以使用 app.use() 连续定义多个全局中间件。客户端请求到达服务器之后,会按照中间件定义的先后顺序依次进行调用。
局部生效中间件:不使用 app.use() 定义的中间件,叫做局部生效的中间件,如下:
const express = require('express')
const app = express()
// 定义局部生效中间件
const thing = (req,res,next)=>{
console.log('调用了局部生效中间件');
next()
}
// 定义路由
app.get('/',thing,function(req,res){
res.send('Home page')
})
app.get('/user',function(req,res){
res.send('User page')
})
app.listen(80,()=>{
console.log('express server running at http:127.0.0.1');
})
定义多个局部中间件:可以在路由中,通过如下两种方式使用多个局部中间件:
// 使用局部中间件的两种方法
app.use('/',w1,w2,w3,(req,res)=>{ res.send('Home page') })
app.use('/',[w1,w2,w3],(req,res)=>{ res.send('Home page') })
中间件的作用
多个中间件之间,共享同一份req和res。基于这样的特性,我们可以在上游的中间件中,统一为req和res对象添加自定义属性或方法,供下游的中间件或路由进行使用。
中间件分类
Express官方把常见的中间件用法,分成了如下五大类:
应用级别的中间件:通过app.use() 或 app.get() 或 app.post() ,绑定到 app 实例上的中间件,叫做应用级别的中间件,案例如下:
// 应用级别的中间件(全局中间件)
app.use((req,res,next)=>{
next()
})
// 应用级别的中间件(局部中间件)
app.get('/',w1,function(req,res){
res.send('Home page')
})
路由级别的中间件:绑定到 express.Router() 实例上的中间件,叫做路由级别的中间件。它的用法和应用级别的中间件没有任何区别,只不过一个是绑定到app实例上,另一个是绑定到router实例上,案例如下:
// 导入 express 模块
const express = require('express')
var app = express()
// 创建路由对象
const router = express.Router()
// 路由级别的中间件
router.use(function(req,res,next){
next()
})
app.use('/',router)
错误级别的中间件:作用是专门用来捕获整个项目中发生的异常错误,从而防止项目异常崩溃的问题,其格式:错误级别的中间件的function处理函数中,必须有四个参数,形参顺序从前往后分别是(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内置的中间件:自 Express 4.16.0 版本开始,Express内置了3个常用的中间件。极大的提高了Express项目的开发效率和体验:
1)express.static 快速托管静态资源的内置中间件,例如:HTML文件、图片、CSS等,之前文章已经讲解过,这里不再赘述,详情请看:express.static文章讲解
2)express.json 解析 JSON 格式的请求体数据(有兼容性,仅在 4.16.0+ 版本中可以)
// 通过 express.json() 这个中间件,解析表单中的 JSON 格式的数据
app.use(express.json())
// 定义路由
app.post('/user',function(req,res){
// 在服务器中,可使用req.body属性来接收客户端发送过来的请求体数据
// 默认情况下,如果不配置解析表单数据的中间件,则req.body默认等于undefined
console.log(req.body);
res.send('ok')
})
3)express.urlencoded 解析 URL-encoded 格式的请求体数据(有兼容性,仅在4.16.0+版本可用)
const express = require('express')
const app = express()
// 通过 express.urlencoded() 这个中间件,解析表单中的 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,()=>{
console.log('express server running at http:127.0.0.1');
})
第三方的中间件:非Express官方内置的,而是由第三方开发出来的中间件,叫做第三方中间件,在项目中,可以按需下载和配置第三方中间件,从而提高项目的开发效率。案例如下:
安装第三方中间件:npm i body-parser
使用中间件的注意事项
1)一定要在路由之前注册中间件
2)客户端发送过来的请求,可以连续调用多个中间件进行处理
3)执行完中间件的业务代码之后,不要忘记调用next()函数
4)为了防止代码逻辑混乱,调用next()函数后不要再写额外的代码
5)连续调用多个中间件时,多个中间件之间,共享 req和res对象
编写接口
编写GET接口,如下:
const express = require('express')
// 导入第三方中间件
const app = express()
// 导入路由模块
const router = require('./router')
// 将路由模块注册到app上
app.use('/api',router)
app.listen(80,()=>{
console.log('express server running at http:127.0.0.1');
})
const express = require('express')
const router = express.Router()
// 挂载对应路由
router.get('/get',(req,res)=>{
// 通过 req.query 获取客户端通过查询字符串,发送到服务器的数据
const query = req.query
// 通过 res.send() 方法,向客户端响应处理的结果
res.send({
status:0, // 0 表示处理成功,1表示失败
msg:'GET请求成功', // 状态描述
data:query // 需要响应给客户端的数据
})
})
module.exports = router
编写POST接口,如下:
router.post('/post',(req,res)=>{
// 通过 req.query 获取客户端通过查询字符串,发送到服务器的数据
const body = req.body
// 通过 res.send() 方法,向客户端响应处理的结果
res.send({
status:0, // 0 表示处理成功,1表示失败
msg:'POST请求成功', // 状态描述
data:body // 需要响应给客户端的数据
})
})
跨域问题及其解决方案
我们在编写的GET和POST接口,可能存在一个问题,不支持跨域请求,也就是说不能在file协议中去编写POST和GET请求。解决跨域问题的方案有两种:CORS(主流的解决方案,推荐使用);JSONP(有缺陷的解决方案,只支持GET请求)。
CORS:Cross-Origin Resource Sharing,跨域资源共享。由一系列HTTP响应体组成,这些HTTP响应头决定浏览器是否阻止前端JS代码跨域获取资源。
CORS注意事项:
1)CORS主要在服务器端进行配置,客户端浏览器无需做任何的额外的配,即可请求开启了CORS的接口。
2)CORS在浏览器中有兼容性,只支持 XMLHttpRequest Level2 的浏览器,才能正常访问开启了CORS的服务端口
cors是Express的一个第三方中间件,通过安装和配置cors中间件,可以很方便的解决跨域问题。
安装cors中间件命令: npm install cors
// 在路由之间配置 cors 这个中间件,从而解决接口跨域问题
const cors = require('cors')
app.use(cors())
配置CORS响应头:
响应头中可以携带一个 Access-Control-Allow-Origin 字段,如语法如下:
// 第二个参数指定了允许访问该资源外域的URL,如想访问所有域的请求,可用通配符 *
res.setHeader('Access-Control-Allow-Origin','http://www.baidu.com')
响应头中可以携带一个 Access-Control-Allow-Headers 字段,如语法如下:
// 如果客户端向服务器发送了额外的请求头信息,则需要在服务器端,通过 Access-Control-Allow-Headers 对额外的请求头进行声明,否则会失败!
res.setHeader('Access-Control-Allow-Headers','Context-Type, X-Custom-Header')
响应头中可以携带一个 Access-Control-Allow-Methods 字段,如语法如下:
// 默认情况下,CORS仅支持客户端发起GET,POST,HEAO请求
// 如果客户端希望通过PUT,DELETE等方式请求服务器资源,则需要在服务器端,通过Access-Control-Allow-Methods来指明实际请求所允许的HTTP方法
res.setHeader('Access-Control-Allow-Methods','POST,GET,DELETE,HEAD')
// 允许所有 HTTP 方法
res.setHeader('Access-Control-Allow-Methods','*')
CORS请求的分类:
客户端在请求CORS接口时,根据请求方式和请求头的不同,可以将CORS的请求分为两大类:
简单请求:
预检请求:在浏览器与服务器正式通信之前,浏览器会先发送ОPTION请求进行预检,以获知服务器是否允许该实际请求,所以这一次的OPTION请求称为“预检请求”。服务器成功响应预检请求后,才会发送真正的请求,并且携带真实数据。
简单请求和预检请求的区别:
简单请求的特点:客户端与服务器之间只会发生一次请求。
预检请求的特点:客户端与服务器之间会发生两次请求,OPTION预检请求成功之后,才会发起真正的请求。
创建JSONP接口:
浏览器端通过<script>标签的src属性,请求服务器上的数据,同时服务器返回一个函数的调用,这种请求数据的方式叫做JSONP。
如果项目中已经配置了CORS跨域资源共享,为了防止冲突,必须在配置CORS中间件之前声明JSONP的接口,否则JSONP接口会被处理成开启了CORS的接口。案例如下:
// 必须在配置 cors 中间件之前,配置 JSONP 接口
app.get('/api/jsonp',(req,res)=>{
// 得到函数名称
const funcname = req.query.callback
// 定义要发送到客户端的数据对象
const data = {name:'zs',age:18}
// 拼接出一个函数声明
const scriptStr = `${funcname}(${JSON.stringify(data)})`
// 把拼接的字符串响应给客户端
res.send(scriptStr)
})