Express
一、什么是Express?
Express 是基于 Node.js 平台,快速、开放、极简的 Web 开发框架
通俗的理解:Express 的作用和 Node.js 内置的 http 模块类似,是专门用来创建 Web 服务器的
Express 的本质: 就是 npm 上的第三方包,提供了快速创建 Web 服务器的便捷方法。
Express 中文官网:http://www.expressjs.com.cn/
为什么有了 http 内置模块还要有 Express ?
http 用起来过于复杂、开发效率低;
Express 是基于 http 进一步封装的,能够提高开发效率
http 内置模块与 Express 的关系?
后者是前者的进一步封装
Express 能做什么
快速创建 Web 网站的服务器或 API 接口的服务器。
二、Express 的基本使用
安装
npm i express@版本号
使用
const express = require('express')
//接收 express 实例
const app = express()
app.listen(端口号, () => {
})
监听 GET 请求
app.get('请求的 url', function(req, res) {
//可以向客户端发送 JSON 对象{}
//也可以发送文本内容
res.send()
})
监听 POST 请求
app.post('请求的 url', function(req, res) {
//在服务器可以使用 req.body 这个属性来接收客户端发送过来的请求体数据
//如果不配置解析表单数据的中间件,则 req.body 默认等于 undefine
res.send(req.body)
})
获取 url 中携带的查询参数
app.get('请求的 url',(req, res) => {
req.send(req.query)
})
获取 url 中的动态参数
app.get('请求的 url', (req, res) => {
//params 默认是空对象,里面放着通过 : 动态匹配到的参数值
req.send(req.params)
})
托管静态资源
通过它,可以非常方便的创建一个静态资源服务器
app.use(express.static('目录'))
挂载路径前缀:
app.use('/',express.static('目录'))
三、Express 的路由
客户端的请求与服务器处理函数之间的映射关系
分 3 部分,请求的类型、请求的 URL 地址、处理函数
//method 为 get 或 post
app.method(path, (req, res) => {
})
按路由顺序匹配
模块化路由
- 创建路由模块对应的 .js 文件
- 调用 express.Router() 的函数创建路由对象
const express = require('express')
- 向路由对象挂载具体的路由
const router = express.Router()
router.get('/目录', (req, res) => {
res.send('')
})
router.post('/目录', (req, res) => {
res.send('')
})
- 使用 moudle.exports 向外分享路由对象
moudle.exports = router
- 导入路由模块
const userRouter = require('./路由.js')
- 使用 app.use() 函数注册路由模块
app.use(userRouter)
示例代码:
路由模块 router.js :
const express = require('express')
const router = express.Router()
router.get('/abc', (req, res) => {
res.send('abc')
})
router.post('/def', (req, res) => {
res.send('def')
})
//导出路由模块
module.exports = router
调用路由模块:
const express = require('express')
//获取 express 实例
const app = express()
//导入路由模块
const router = require('./router')
//注册路由模块
//app.use() 函数的作用,就是用来注册全局中间件
app.use(router)
app.listen(3000,() => {
console.log('run at http://localhost:3000');
})
四、中间件
什么是中间件?
业务处理环节中的中间处理环节
Express 中间件的调用流程
请求到达 Express 服务器之后,可以连续调用多个中间件,从而对这次请求进行预处理
Express 中间件的格式
本质上是一个函数,格式如下:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CAsTP6Vr-1689042339329)(C:\Users\mzhj\AppData\Roaming\Typora\typora-user-images\image-20230704211937969.png)]
注意:
中间件函数的形参列表中,必须包含 next 参数。而路由处理函数中只包含 req 和 res
next 函数的作用
next 函数是多个中间件连续调用的关键,它表示把流转关系转交给下一个中间件或路由
定义中间件函数
const mw = function (req, res, next) {
//业务处理完毕后,必须调用 next() 函数
next()
}
全局生效的中间件
客户端发起的请求,到达服务器之后都会触发
const express = require('express')
const app = express()
const mw = function (req, res, next) {
//业务处理完毕后,必须调用 next() 函数
next()
}
//注册全局生效的中间件
app.use(mw)
简化形式
app.use((req, res, next) {
//业务处理完毕后,必须调用 next() 函数
next()
})
中间件的作用
多个中间件之间,共享 req 和 res 。基于这样的特性,我们可以在上游的中间件中,统一为 req 或 res 对象添加自定义的属性或方法,共下游的中间件或路由进行使用
局部生效的中间件
不适用 app.use() 定义的中间件,叫做局部生效的中间件,实例代码如下:
const wm1 = (req, res, next) {
next()
}
// mw1 只会在当前路由生效
app.get('/路径', mw1, (req, res) {
res.send('')
})
// wm1 不会影响下面这个路由
app.get('/路径', (req, res) {
res.send('')
})
定义多个局部中间件
//以下两种写法都可
app.get('/路径', mw1, mw2, (req, res) {
res.send('')
})
app.get('/路径', [mw1, mw2], (req, res) {
res.send('')
})
注意
- 中间件不能放在路由后面(请求是从上往下的)
- 中间件必须调用 next()
- 调用完 next() 后不能再写额外代码
- 多个中间件共享 req 和 res
中间件的分类
- 应用级别的中间件
通过 app.use() 或 app.get() 或 app.post, 绑定到 app 实例上的中间件,叫做应用级别的中间件,示例:
//全局中间件(全局)
app.use((req, res, next) {
next()
})
//应用级别的中间件(局部)
app.get('/路径', mw1, (req, res) {
res.send('')
})
- 路由级别的中间件
绑定到 express.Router() 实例上的中间件,用法与应用级的没区别,示例:
const express = require('express')
const app = express()
const router = express.Router()
//路由级别的中间件
router.use((req, res, next) {
next()
})
app.use('/', router)
- 错误级别的中间件
专门捕获项目中发生的错误,从而防止项目异常崩溃的问题
格式:必须有四个形参,从前到后分别是 (err, req, res, next),必须注册在所有路由之后
app.get('/路径', (req, res) {
throw new Error('错误信息')
res.send('')
})
app.use((err, req, res, next) => {
res.send('Error!' + err.message)
})
- Express 内置的中间件
三个常用中间件
- express.static 快速托管静态资源的内置中间件(无兼容性)
- express.json() 解析 JSOH 格式的请求体数据(有兼容性,仅在 4.16.0+ 版本可用)
- express.urlencoded() 解析 URL-encoded 格式的请求体数据(有兼容性,仅在 4.16.0+ 版本可用)
//配置解析 application/json 格式数据的内置中间件
//客户端发送的请求体数据使用 req.body 接收
app.use(express.json())
//配置解析 application/x-www-form-urlencoded 格式数据的内置中间件
app.use(express.urlencoded({ extended: false }))
- 第三方的中间件
- 运行 npm i 中间件名 安装中间件
- 使用 require 导入中间件
- 调用 app.use() 注册并使用中间件
const parser = require('body-parser')
parser.use(parser.urlencoded({ extended: false }))
内置的 url.encoded 中间件就是基于第三方 body-parser 的封装
自定义中间件
模拟一个类似 express.urlencoded 的中间件,解析 POST 提交到服务器的表单数据
//定义中间件
app.use((req, res, next) => {next()})
//监听 req 的 data 事件(数据量比较大,客户端就会切割,分批发送,所以会触发多次)
app.use((req, res, next) => {
let str = ''
req.on('data', chunk => {
str += chunk
})
req.on('end', () => {
console.log(str)
})
next()
})
//监听 req 的 end 事件(数据都接收完毕了)
app.use((req, res, next) => {
let str = ''
req.on('data', chunk => {
str += chunk
})
req.on('end', () => {
console.log(str)
})
next()
})
//使用 querystring 模块解析请求体数据
const qs = require('querystring')
app.use((req, res, next) => {
let str = ''
req.on('data', chunk => {
str += chunk
})
req.on('end', () => {
console.log(str)
})
const body = qs.parse(str)
next()
})
//将解析出来的数据对象挂载为 req.body
app.use((req, res, next) => {
let str = ''
req.on('data', chunk => {
str += chunk
})
req.on('end', () => {
const body = qs.parse(str)
console.log(str)
req.body = body
next()
})
})
//将自定义的中间件封装为模块
function bodyParser(req, res, next){}
module.exports = bodyParser
//导入自定义中间件模块
const body = require('bodyParser')
app.use(body)
五、使用 Express 写接口
5.1 创建基本服务器
const express = require('express')
const app = express()
//接口
app.listen(3000,() => {
console.log('running at http://localhost:3000');
})
5.2 创建 API 路由模块
const express = require('express')
const router = express.Router()
//接口
module.exports = router
=====================
//导入并注册路由模块
const router = require('router')
app.use('/api',router)
5.3编写 GET 请求
const express = require('express')
const router = express.Router()
router.get('/get', (req, res) => {
const query = req.query
res.send({
status: 0,//0表示成功,1表示失败
msg: 'GET 请求成功',
data: query
})
})
module.exports = router
5.4编写 POST 接口
router.post('/post', (req, res) => {
const body = req.body
res.send({
status: 0,
msg: 'POST 请求成功',
data: body
})
})
===============
//获取 body 数据必须在导入路由之前,配置解析表单数据的中间件
app.use(express.urlencoded({ extended: false }))
//引入并注册路由模块
const router = require('router')
app.use('/api',router)
六、接口的跨域问题
通过 CORS 解决跨域
什么是 CORS ?
CORS (Cross-Origin Resource Sharing, 跨语资源共享)
由一系列 HTTP 响应头组成,这些 HTTP 响应头决定浏览器是否阻止前端 JS 代码跨域获取资源。(浏览器默认阻止)
CORS 有兼容性,只有支持 XMLHttpRequest Level2 的浏览器,才能正常访问开启了 CORS 的服务端接口
cors 是 Express 的第三方中间件,通过安装和配置,可以很方便的解决跨域问题。
//安装
npm i cors
//导入
const cors = require('cors')
//调用(一定要在路由之前配置 cors)
app.use(cors())
CORS 响应头部 Access-Control-Allow-Origin
// origin 的值指定了允许访问该资源的外域 URL
Access-Control-Allow-Origin: <origin> | *
//只允许来自 http://baidu.com 的请求(*代表所有)
res.setHeader('Access-Control-Allow-Origin', 'https://baidu.com')
CORS 响应头部 Access-Control-Allow-Header
默认情况下,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-Header 对额外的请求头进行声明,否则这次的请求会失败!
res.setHeader('Access-Control-Allow-Header', 'Content-Type, X-Custom-Header')
CORS 响应头部 Access-Control-Allow-Methods
如果客户端希望通过 PUT、DELETE 等方式请求服务器的资源
//只允许 POST GET DELETE HEAD 请求方法
res.setHeader('Access-Control-Allow-Methods', 'POST, GET, DELETE, HEAD')
//允许所有的 HTTP 请求方法
res.setHeader('Access-Control-Allow-Methods', '*')
CORS 请求的分类
根据请求方式和请求头的不同,分为两大类:
- 简单请求
- 请求的方式为 GET,POST,HEAD 三者之一
- HTTP 头部信息不能超过以下几种字段:无自定义头部字段、Accept、Accept-Language、Content-Language、DPR、Downlink、Save-Data、Viewport-Width、Width、Content-Type(值只限于 text/plain、multipart/form-data、application/x-www-form-urlencoded 三者之一)
- 预检请求
- 请求方式为 GET、POST、HEAD 之外的请求 Method 类型
- 请求头中包含自定义头部字段
- 向服务器发送了 application/json 格式的数据
在浏览器与服务器正式通信之前,浏览器会发送 OPTION 请求进行预检,以获知服务器是否允许该实际请求,OPTION 被称为“预检请求”
简单请求与预检请求之间的区别
- 简单请求的特点:客户端与服务器之间只会发送一次请求。
- 预检请求的特点:客户端与服务器之间会发送两次请求。
通过 JSONP 解决跨域
浏览器端通过
特点:
- JSONP 不属于真正的 Ajax 请求,因为它没有使用 XMLHttpRequest 这个对象。
- JSONP 仅支持 GET 请求,不支持 POST、PUT、DELETE 等
注意事项:
如果已经配置了 CORS 跨域资源共享,防止冲突,必须在配置 CORS 中间件之前声明 JSONP 的接口。否则 JSONP 接口会被处理成开启了的 CORS 的接口,示例:
//优先创建 JSONP 接口
app.get('/api/jsonp', (req, res) => { })
app.use(cors())
//开启了 CORS 的接口
app.get('/api/get', (req, res) => { })
实现 JSONP 接口的步骤
- 获取客户端发送过来的回调函数的名字
- 得到要通过 JSONP 形式发送给客户端的数据
- 根据前两步得到的数据,拼接出一个函数调用的字符串
- 把上一步拼接得到的字符串,响应给客户端的
app.get('/api/jsonp', (req, res) => {
const funcName = req.query.callback
const data = { name: 'zs', age: 22}
const scriptStr = `${funcName}(${JSON.stringify(data)})`
res.send(scripStr)
})
S 跨域资源共享,防止冲突,必须在配置 CORS 中间件之前声明 JSONP 的接口。否则 JSONP 接口会被处理成开启了的 CORS 的接口,示例:
//优先创建 JSONP 接口
app.get('/api/jsonp', (req, res) => { })
app.use(cors())
//开启了 CORS 的接口
app.get('/api/get', (req, res) => { })
实现 JSONP 接口的步骤
- 获取客户端发送过来的回调函数的名字
- 得到要通过 JSONP 形式发送给客户端的数据
- 根据前两步得到的数据,拼接出一个函数调用的字符串
- 把上一步拼接得到的字符串,响应给客户端的
app.get('/api/jsonp', (req, res) => {
const funcName = req.query.callback
const data = { name: 'zs', age: 22}
const scriptStr = `${funcName}(${JSON.stringify(data)})`
res.send(scripStr)
})