Express是做web服务器的,是一个第三方的包,官网 Express - 基于 Node.js 平台的 web 应用开发框架 - Express 中文文档 | Express 中文网
Express的部分用法与http模块类似
在我看来Express是一个轻量级的框架,如果用于做一些较复杂的业务会非常麻烦
目录
12.3.1 Access-Control-Allow-Origin
12.3.2 Access-Control-Allow-Header
12.3.3 Access-Control-Allow-Methods
1 安装
我当前安装的是目前(2023.1.4)最新的 4.18.2
2 创建基本的服务器
目前这个服务里什么都没有,启动之后找不到指定路由的效果是这样的
3 监听get请求
我们简单做个例子
4 监听post请求
- app.post()与app.get()可以挂同一个路由
5 req对象
5.1 接收查询字符串 req.query
给参数的情况
不给参数的情况
post发的参数用req.query接不到
只能接收查询字符串
5.2 获取动态参数 req.params
动态参数就在路由中提前写好的参数,让查询字符串不用再写键了
post也能用
动态参数可以像上面一样直接加,也可以加在斜杠后面
动态参数可以在任意位置加任意个
6 静态文件
6.1 注册单一目录
首先创建一个放静态文件的文件夹,我将其命名为static,然后向static中放一张图像
然后再代码中注册这个静态文件文件夹
- 参数是文件夹名称,你也可以不叫static
之后在网页中就能访问到这张图片了
6.2 注册多个目录
你可以注册多个静态文件目录,我现在再加入一个目录file
之后我注册一下
访问一下发现可以访问file中的图片
现在我在file与static中有一个相同名称的图片Node.js.png,我先注册的static,所以会显示static中的Node.js.png
6.3 给目录路由
我们给各自的目录路由就可以精确的找到想要找的文件
- 路由可给可不给,路由名称随便起
7 路由模块化
我们一般不把所有的路由都挂在主路由下,比如我们给网站先分 主页,用户管理,其他 这么几个部分,然后在各自的部分加入想要的路由。
我做个例子
user.js,这个是放用户模块的路由的
server.js 这个是启动的入口
启动server.js发现可以访问模块的路由
向这样就比较麻烦,每一个路由我还得多写一遍user
我们可以在server.js文件中统一为添加模块的路由,我们下面简单改一下
user.js
server.js
重启服务后可以正常访问
8 中间件的基本使用方法
中间件的概念可以看一下这个 25.中间件_Suyuoa的博客-CSDN博客_中间件执行顺序 大概理解一下就是,请求之前我应该处理点儿什么,响应之后我应该怎么处理响应,报错了怎么办,这些
express的中间件形参包含next参数
8.1 全局生效的中间件
中间件函数一定要在函数最后加上next(),不然会阻塞。使用app.use()可以在全局应用中间件
- next()后不要再写代码,因为next()后的代码不会被执行,虽然不会报错但看起来会难看些
运行后我们访问一下,发现先执行的中间件,再执行的test_get
8.2 中间件的应用
比如我们现在想在每一次请求的时候打印出来请求的时间,那我们就这样定义一下中间件
你也可以在中间件内定义一些属性,到后面请求的时候再使用上这些属性
8.3 定义多个全局中间件
在app.use()中依次写上要执行的中间件就好。连续调用多个中间件时,多个中间件之间共享req与res对象
8.4 局部生效的中间件
我们下面只给 /test_get 绑定中间件mw1
8.5 使用多个局部中间件
给多个参数或给一个数组作为参数都可以使用多个中间件
9 中间件的分类
根据常见中间件的用法,Express官方将中间件分为5大类
- 应用级别中间件
- 路由级别中间件
- 错误级别中间件
- Express内置的中间件
- 第三方中间件
9.1 应用级别中间件
绑定在app这个对象上的中间件,我们上面用的都是应用级别中间件
9.2 路由级别中间件
绑定在router这个对象上的中间件(上面提到的路由模块化的对象),可以用router.use()方法进行注册,其余用法也都与绑在app上相同
9.3 错误级别中间件
用来捕获项目中发现的异常,从而防止项目异常崩溃。错误级别中间件有四个形参 err,req,res,next。错误中间件要写在路由的后面
9.3.1 加入中间件的情况
- 一般来讲错误级别中间件最后不写next()
如果出现了错误,路由错误下面的内容就不再被执行了
9.3.2 不加入中间件的情况
如果不加中间件,响应结果是这样的
9.4 Express内置的中间件
比如 express.static 这个我们在上面介绍静态文件时使用过,除此之外还有下面几个常用的
- express.json 解析JSON格式的请求体数据
- express.urlencoded 解析 URL-encoded 格式的请求体数据
express.static,express.json,express.urlencoded可以同时使用
9.4.1 express.json
我们先简单用一下express.json
如果你不使用express.json,请求会成功,但拿不到数据
9.4.2 express.urlencoded
如果不用 express.urlencoded 可以请求成功,但拿不到数据
9.5 第三方中间件
第三方中间件就是别人写的中间件,比如body-parser可以解析数据
- 每一个第三方中间件的使用方式不尽相同,我只是在这里做个例子
我们首先运行 npm install body-parser 安装body-parser
10 自定义中间件
我们简单了解一下第三方中间件的创建过程,我们模拟一个类似express.urlencoded这样的中间件,来解析POST提交到服务器的表单数据
首先先创建两个js文件,一个放服务,一个放中间件
10.1 中间件
先写中间件
通过req的data事件获取客户端发送到服务器的数据
如果单次发送数据量较大,无法一次性发送完毕。那么客户端会把数据进行切割,分批发送到服务器。所以data事件可能会触发多次,每一次触发data事件时,获取到数据只是完整数据的一部分,需要手动对接到的数据进行拼接
我们打印出来接收到的req_str发现是一个查询字符串
Node.js内置querystring模块,可以把查询字符串解析为对象,在请求结束的时候会触发end事件,这个时候我们将查询字符串解析,然后赋值给req.body
最后导出供我们的服务使用
10.2 服务
引入后注册,注册后使用
10.3 使用结果
发个请求测试一下
11 编写接口
接口和视图实质上没有区别,就是返回的东西不一样,我们简单看一下
11.1 编写get接口
11.2 编写post接口
12 CORS跨域
CORS (Cross-Origin Resource Sharing) 跨域资源共享,由一系列HTTP响应头组成,这些HTTP响应头决定浏览器是否阻止前端JS代码跨域获取资源
CORS在服务端进行配置,客户端无需配置
CORS有兼容性问题,只有支持XMLHttpRequest Level2的浏览器才支持CORS
12.1 跨域问题
上面我们写的接口是不支持跨域请求的,在浏览器上直接访问http地址不算跨域
但用file协议的Ajax不行,不了解跨域可以看一下这个 8.跨域请求_Suyuoa的博客-CSDN博客
我们之前在上面那个链接中使用JSONP可以解决GET的跨域请求,但POST跨域请求搞不了。
现在我们使用CORS,CORS可以解决GET与POST的跨域请求
12.2 使用cors中间件解决跨域问题
cors是Express的第三方中间件,首先先安装cors
之后注册,然后使用
这样再进行GET与POST请求就可以了
12.3 CORS的响应头
12.3.1 Access-Control-Allow-Origin
Access-Control-Allow-Origin 允许的源,这个后面可以是一个域名(IP),可以使用res.setHeader()对响应头进行设置,比如
- res.setHeader('Access-Control-Allow-Origin','http://127.0.0.1')
- res.setHeader('Access-Control-Allow-Origin','http://itcast.cn')
这样就仅允许 http://127.0.0.1 或 http://itcast.cn 发出请求,想要设置多个源可以用英文逗号隔开
也可以是一个星号,星号就是所有域名都可以访问
- res.setHeader('Access-Control-Allow-Origin','*')
12.3.2 Access-Control-Allow-Header
Access-Control-Allow-Header 允许的请求头,默认情况下CORS仅支持下面9个请求头
- Accept
- Accept-Language
- Content-Language
- DPR
- Downlink
- Save-Data
- Viewport-Width
- Width
- Content-Type
上面是9个请求头的键,它们的值仅限于下面三个
- text/plain
- multipart/form-data
- application/x-www-form-urlencoded
如果客户端向服务端发送了额外的请求头信息,则需要在服务端进行声明,比如我现在仅允许Content-Type123与X-Custom-Header这两个请求头
- res.setHeader('Access-Control-Allow-Headers','Content-Type123,X-Custom-Header')
12.3.3 Access-Control-Allow-Methods
Access-Control-Allow-Methods 允许的方法,默认情况下CORS仅支持客户端发起GET,POST,HEAD请求
比如我现在想加上DELETE请求,那就这样写
- res.setHeader('Access-Control-Allow-Methods','POST,GET,DELETE,HEAD')
想允许所有的HTTP请求方法就用星号
- res.setHeader('Access-Control-Allow-Methods','*')
12.4 CORS请求的分类
根据请求方式和请求头的不同可以分为 简单请求 与 预检请求
在浏览器与服务器正式通信之前,浏览器会先发送OPTION请求进行预检,目的是获知服务器是否允许该实际请求
此次OPTION请求称为OPTION预检请求
当服务器成功响应预检请求后,才会发送真正的请求,也就是说预检请求实质上是请求两次
简单请求条件
- 请求方式属于 GET,POST,HEAD 三者之一
- HTTP头部信息键属于
- 无自定义头部字段(自定义的不算)
- Accept
- Accept-Language
- Content-Language
- DPR
- Downlink
- Save-Data
- Viewport-Width
- Width
- Content-Type
- HTTP头部信息的值属于
- application/x-www-form-urlencoded
- multipart/form-data
- text/plain
预检请求条件,符合下面任何一个条件的请求,都是预检请求
- 请求方式为 GET,POST,HEAD 之外的请求方式
- 请求头中包含自定义头部字段
- 向服务器发送了 application/json格式的数据
我们用DELETE请求看一下预检请求
客户端
服务端
点击一次DELETE请求发现出现了两次请求
- 有时200的请求会显示在前
这个204的请求就是OPTIONS请求
下面的是真正的DELETE请求
13 JSONP接口
在这里 8.跨域请求_Suyuoa的博客-CSDN博客 我们用flask做了一个JSONP接口,现在我们用express做一个
如果要同时使用CORS与JSONP,那么我们必须在配置CORS中间件之前声明JSONP的接口
13.1 客户端
13.2 服务端
在实际情况中使用JSONP应该是不需要后端进行处理的,后端的写法是相对固定的,只是接收 回调函数以及若干参数,然后返回就行
13.3 请求结果
14 session
14.1 安装
在express中使用session可以通过 express-session 中间件使用
使用之前我们首先安装 express-session 中间件
14.2 配置
安装后对express-session进行配置
其中secret是用来加密用的,值为任意字符串,resave与saveUninitialized就这么给就行,如果想深入了解的话可以看一下文档 express-session - npm
14.3 存储数据
用session的时候一般不会用get发送数据,所以我用的post
这样写session是存储在变量中的,服务关了session就没有了
14.4 获取数据
14.5 清空session
清空session不会讲所有客户端的session都清空,而是只会清空发出该请求客户端的所有session
此时再读取session就会出问题
15 JWT
在express中使用JWT可以组合使用 jsonwebtoken与express-jwt
jsonwebtoken是生成JWT字符串的,express-jwt是将JWT字符还原为JSON对象的
下面我写个例子
const express = require('express')
const jsonwebtoken = require('jsonwebtoken')
const { expressjwt: jwt } = require("express-jwt")
const secretKey = 'ifwoenigoiehriogjserohmtr'
const app = express()
app.use(express.urlencoded({extended:false}))
app.use(
jwt({
secret: secretKey,
algorithms: ["HS256"],
}).unless({ path: ["/login"] })
)
app.post('/login',(req,res) => {
userinfo = req.body
console.log('userinfo:',userinfo)
res.send({
message:'登录成功',
token:jsonwebtoken.sign({username:userinfo.username},secretKey,{expiresIn:'300s'})
})
})
app.get('/hello',(req,res) => {
console.log('req.auth:',req.auth)
res.send('你好' + req.auth.username)
})
app.use((err,req,res,next) => {
if (err.name === 'UnauthorizedError') {
return res.send({status:401,message:'无效的token'})
}
res.send({status:500,message:err.message})
})
app.listen(80,() => {
console.log('服务在80端口启动')
})
- 新版的 express-jwt 与老版的在使用方式上有差异,我当前的版本是 8.2.1 是2023年1月4日的最新版本
下面我们说一下这个例子
express-jwt的引入方式与其他的包有些不同
secret密钥是做加密与解密用的,他实质上是自定义的字符串
使用中间件jwt,secret是刚刚定义的密钥,algorithms是算法,不了解的这样写就行,后面的unless表示哪个路由不需要token就可以访问
接收到数据使用jsonwebtoken的sign将信息加密,token的安全性不如session,所以最好不要把密码这种敏感信息放在token中
expiresIn是有效期,expiresIn是有效期
接收的时候使用req.auth进行接收
为了避免看到大量的报错信息这里给一个中间件
运行一下后先直接访问/hello ,发现处理错误的中间件是生效的
然后请求 /login,发现请求成功,此时得到了一个token
之后再请求 /hello ,把刚刚的token复制上
iat与exp是控制token有效期的