在 js 中向服务器发送请求来加载数据
方案:XMLHTTPRequest(xhr)、Fetch、Axios(现在最常用)
Rest 接口
传统服务器的局限
MVC:Model数据模型,View视图,呈现数据,Controller控制器,调度,加载数据并选择视图来呈现
传统服务器直接给客户端返回一个 HTML 页面,但是这无法适应现在的应用场景——一个应用通常有多个客户端存在(如 pc/移动端)。 那么除 web 外的其他类型的客户端是不是还要单独开发服务器来适配呢?
解决方法
传统服务器需要加载数据+将数据模型渲染进视图+返回视图,于是我们将渲染视图的功能从服务器剥离,服务器只负责向客户端返回数据,渲染视图的工作由客户端自行完成,于是服务器就可以同时为多种客户端提供服务,也减轻了服务器的压力。
Rest
一种服务器的设计风格(服务器只返回数据)
通常使用 json 作为数据格式
| GET | 加载数据 |
|---|---|
| POST | 添加数据 |
| PUT | 添加/修改数据 |
| PATCH | 修改数据 |
| DELETE | 删除数据 |
| OPTION | 浏览器自动发送,检查请求的权限 |
CORS 跨域资源共享
例如 http://localhost:5000 和 http://127.0.0.1:5000
协议,域名,端口号三个只要有一个不同,就算跨域。
当我们通过AJAX去发送跨域请求时,浏览器为了服务器的安全,会阻止JS读取到服务器的数据。
解决方案: 在服务器中设置一个允许跨域的头
Access-Control-Allow-Origin:允许哪些客户端访问我们的服务器
Cross-Origin Resource Sharing (CORS) - HTTP | MDN (mozilla.org)
例如在 node 中设置所有客户端均可访问:
res.setHeader("Access-Control-Allow-Origin","*")
允许某客户端可以访问:
res.setHeader("Access-Control-Allow-Origin","http://127.0.0.1:5000")
localStorage
本地存储用户信息并进行前端验证。
为什么不用 cookie 和 sessionStorsge?
cookie 是服务器创建并发给客户端保存。session 是基于 cookie,但是 rest 服务器是跨域的,限制对 Cookie 和 Session 的访问,需要额外的配置才能使用,很麻烦。
为什么跨域请求中限制对 Cookie 和 Session 的访问?
Cookie 和 Session 是用于在客户端和服务器之间维持会话状态的机制,涉及到敏感信息的传递。而跨域请求可能存在安全风险,为了防止恶意网站窃取用户的敏感信息,浏览器实现了同源策略,默认情况下,跨域请求不会携带 Cookie 和 Session 信息。
如果需要使用,则要在浏览器和服务器中做一些额外的配置。
所以要使用 localStorage 将用户数据存储到浏览器内部。
// setItem() 用来存储数据
// getItem() 用来获取数据
// removeItem() 删除数据
// clear() 清空数据
token 后端验证
客户端可以通过 localStorage 验证用户的登录状态,但是rest 服务器是无状态服务器,仅将用户信息发给客户端而不存储用户的数据。所以服务器要如何得知用户的登录状态呢?—— 使用token。
什么是 token
- 访问资源接口(API)时所需要的资源凭证。其作用是在服务端验证用户身份,防止恶意网站伪造用户的身份和用户请求执行非法操作。
- token 是一个由服务器通过加密包生成的随机值,它会嵌入到每个表单或 AJAX 请求中。前端通过将该令牌包含在请求中,以确保该请求是合法的。后端在接收到请求时会验证令牌的有效性,如果令牌不匹配或不存在,则拒绝该请求。
- 基于 token 的用户认证是一种服务端无状态的认证方式,服务端不用存放 token 数据,从而减轻服务器的压力,减少频繁的查询数据库。
前后端通信中 token 怎么用
- 客户端使用用户名跟密码请求登录
- 服务端收到请求,去验证用户名与密码
- 验证成功后,服务端会签发一个 token 并把这个 token 发送给客户端
- 客户端收到 token 以后,会把它存储起来,比如放在 cookie 里或者 localStorage 里
- 客户端每次向服务端请求资源的时候需要带着服务端签发的 token
- 服务端收到请求,然后去验证客户端请求里面带着的 token ,如果验证成功,就向客户端返回请求的数据
注意
- 每一次请求都需要携带 token,需要把 token 放到 HTTP 的 Header 里。
- token是在服务器端进行颁发和验证的,而不是在浏览器中进行处理的,所以它可以避开同源策略(跨域问题)。
token 怎么生成
在 node 中可以使用 jsonwebtoken 来对 json 进行加密,生成一个 web 中使用的令牌。
// 引入jsonwebtoken
const jwt = require("jsonwebtoken")
// 要加密的数据
const obj = {...}
//加密:需要加密的json,自定义密匙,加密配置对象)
//注:密匙不能泄露
const token = jwt.sign(obj, "XXX", {
expiresIn: "1" //生成的token的有效时间
})
//......然后服务器会把token发给客户端,
//客户端再度发来请求时就需要带上token以告诉服务器自己现在是合法请求
//---------------------------------------------------------------------
//服务器验证token
try {
//服务器收到客户端的token后
//解密:需要解密的token,和加密时相同的密匙
const decodeData = jwt.verify(token, "hellohellohowyou")
console.log(decodeData)
} catch (e) {
// 说明token解码失败,说明token无效
console.log("无效的token")
}
token 使用案例
1、客户端使用用户名和密码请求登录。
2、服务器验证用户名和密码并生成 token 返回给客户端。
3、客户端接收 token 存进 localStorge。
4、 客户端请求数据,请求头里携带之前保存的 token。
(增加了 token 的请求头,所以同样要在服务器设置该头允许跨域,不然前端请求就发不过去了。)
5、服务器验证 token,验证成功则返回资源。
Axios
Axios中文文档 | Axios中文网 (axios-http.cn)
和 fetch 的区别
- 在 data 里写一个 对象,它就会自动把请求头格式设置为 json;写一个 urlencoded 格式的数据,它就自动把请求头设置为urlencoded;
- axios 将服务器返回的数据封装成 "result",里面存了很多信息,
result.data也已经自动帮我们转成 js 对象了; - axios 只有在返回状态码为 2XX 的时候才会执行
.then,无需再自己判断状态码。
常用配置
axios({
baseURL:"http://localhost:3000",// 服务器的根目录(路径前缀)
url:"test", // 请求地址
method:"get", //请求方法,默认是get
// 请求头(可不写,它会根据data自动识别)
// headers:{"Content-type":"application/json"}
// 请求体
// data:"name=zxx&age=18" //urlencoded格式
data: { //json格式
name: "zxx",
address: "ZheJiang"
},
// 指定路径中的查询字符串
params:{
id:1,
name:"swk"
},
//timeout 过期时间,1s后没有响应结果自动取消请求
timeout:1000,
// 终止请求
// signal
// transformRequest 用来对请求data做一系列处理
// 一个数组作为参数,数组里有多个函数
// 请求发送时多个函数会按序执行,下一函数的参数即上一个函数修改后的结果
// 注意,上一函数需要return data才能使下一函数的参数data有值
// 注意,用了这玩意影响axios对请求头的自动识别,若headers["Content-Type"]设置得不对(如写成content-type),会使得请求一直是默认的urlencoded
transformRequest:[function(data, headers){
// 可以在函数中对data和headers进行修改
data.name = "猪八戒"
headers["Content-Type"] = "application/json"
return data
}, function(data, headers){
return JSON.stringify(data)
// 最后一个函数必须返回一个字符串,才能使得数据有效
}]
})
.then((result) => { //状态码2XX才执行
console.log(result.data) // result是axios封装过
})
.catch((err) => {
console.log("出错了!", err)
})
}
响应结构
响应结构 | Axios中文文档 | Axios中文网 (axios-http.cn)
默认配置
指定默认配置,这些配置对所有 axios 请求生效,就不必在每一个 axios 请求里重复写了。
axios.defaults.baseURL = "http://localhost:3000"
axios.defaults.headers.common[
"Authorization"
] = `Bearer ${localStorage.getItem("token")}`
axios 实例
axios 实例相当于是 axios 的一个副本,功能一模一样。
axios 的默认配置在实例里也同样会生效。
但是我可以单独修改 axios 实例的默认配置,而该实例默认配置也只对该实例有效。
//创建实例并设置配置
const instance1 = axios.create({
baseURL:"http://localhost:1000"
})
//创建实例并设置默认配置
const instance2 = axios.create()
instance.defaults.baseURL = "http://localhost:2000"
document.getElementById("btn1").onclick = () => {
instance1 //baseURL是instance1的默认配置 http://localhost:1000
.get("students")
.then((res) => console.log(res.data))
.catch((err) => {
console.log("出错了", err)
})
}
axios 拦截器
在请求发送前和响应读取前处理数据——拦截请求或者响应。
可以请求发送前,在拦截器里对请求的配置对象进行修改,常见做法是加上 token 权限信息。
拦截器只对当前实例有效。instance1.interceptors.request.use(...) 只对 instance1 生效。
axios.interceptors.request.use(
// 在发送请求之前做些什么
function (config) // config 表示axios中的配置对象
// console.log("拦截器执行了")
config.headers["Authorization"] = `Bearer ${localStorage.getItem("token")}`
return config
},
// 对请求错误做些什么
function (error) {
return Promise.reject(error)
}
)
// 添加响应拦截器
axios.interceptors.response.use(function (response) {
// 2xx 范围内的状态码都会触发该函数
// 对响应数据做点什么
return response;
}, function (error) {
// 超出 2xx 范围的状态码都会触发该函数
// 对响应错误做点什么
return Promise.reject(error);
});
清除拦截器
const instance = axios.create();
//清除单个拦截器
const myInterceptor = instance.interceptors.request.use(function () {/*...*/});
axios.interceptors.request.eject(myInterceptor);
//清除所有请求拦截器
instance.interceptors.request.clear(); // Removes interceptors from requests
//清除所有响应拦截器
instance.interceptors.response.use(function () {/*...*/});
instance.interceptors.response.clear(); // Removes interceptors from responses