Rest
AJAX的简介
// 点击按钮后,就去自动去加载服务器的数据
const btn = document.getElementById("btn");
btn.addEventListener("click", () => {
// 向服务器发送请求
/*
在js中向服务器发送的请求加载数据的技术叫AJAX
AJAX
- A 异步 J JavaScript A 和 X xml
- 异步的js和xml
- 它的作用就是通过js向服务器发送请求来加载数据
- xml是早期AJAX使用的数据格式
<student>
<name>孙悟空</name>
</student>
- 目前数据格式都使用json
{"name":"孙悟空"}
- 可以选择的方案:
1.XMLHTTPRequest(xhr)
2.Fetch
3.Axios
- CORS(跨域资源共享)
- 跨域请求
- 如果两个网站的完整的域名不相同
a网站:http://haha.com
b网站:http://heihei.com
- 跨域需要检查三个东西:
协议 域名 端口号
- 三个只要有一个不同,就算跨域
- 当我们通过AJAX去发送跨域请求时,
浏览器为了服务器的安全,会阻止JS读取到服务器的数据
- 解决方案
- 在服务器中设置一个允许跨域的头
Access-Control-Allow-Origin
- 允许那些客户端访问我们的服务器
*/
/*
xhr
创建一个新的xhr对象,xhr表示请求信息
*/
const xhr = new XMLHttpRequest();
// 设置请求的信息
xhr.open("GET", "http://localhost:3000/students");
// 发送请求
xhr.send();
});
服务器:
app.use((req, res, next) => {
/*
设置响应头
res.setHeader("Access-Control-Allow-Origin", "*");
Access-Control-Allow-Origin 设置指定值时只能设置一个,多个可以在外部创建数组动态设置
Access-Control-Allow-Methods 允许的请求的方式
Access-Control-Allow-Headers 允许传递的请求头
*/
// res.setHeader("Access-Control-Allow-Origin", "http://127.0.0.1:5500");
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,PATCH");
res.setHeader("Access-Control-Allow-Headers", "Content-type,Authorization");
next();
});
AJAX的使用
const btn = document.getElementById("btn");
const root = document.getElementById("root");
btn.addEventListener("click", () => {
// 创建一个xhr对象
const xhr = new XMLHttpRequest();
// 设置响应体的类型,设置后会自动对数据进行转换
xhr.responseType = "json";
// 设置请求的信息
xhr.open("get", "http://localhost:3000/students");
// 发送请求
xhr.send();
// 可以为xhr对象绑定一个load时间
xhr.onload = () => {
// 读取响应信息
// xhr.status 表示响应状态码
if (xhr.status === 200) {
// xhr.response 表示响应信息
const result = xhr.response;
// 判断数据是否正确
if (result.status === "ok") {
// 创建一个ul
const ul = document.createElement("ul");
// 将ul插入到root中
root.appendChild(ul);
// 遍历数据
for (const stu of result.data) {
ul.insertAdjacentHTML("beforeend",
`<li>${stu.id} - ${stu.name} - ${stu.age} - ${stu.gender} - ${stu.address}</li>`
)
}
}
}
}
});
Fetch
const btn = document.getElementById("btn");
const root = document.getElementById("root");
btn.addEventListener("click", () => {
/*
fetch
- fetch是xhr的升级版,采用的是Promise API
- 作用和AJAX是一样的,但是使用起来更加友好
- fetch是原始js就支持支持的一种ajax请求的方式
*/
fetch("http://localhost:3000/students")
.then(res => {
if (res.status === 200) {
// res.json() 可以用来读取json格式的数据 Promise
return res.json();
} else {
throw new Error("请求失败");
}
})
.then(res => {
if (res.status === "ok") {
// 创建一个table
const table = document.createElement("table");
root.appendChild(table);
table.insertAdjacentHTML("beforeend", "<caption>学生列表</caption>");
table.insertAdjacentHTML("beforeend", `
<thead>
<tr>
<th>学号</th>
<th>姓名</th>
<th>年龄</th>
<th>性别</th>
<th>地址</th>
</tr>
</thead>
`);
const tbody = document.createElement("tbody");
table.appendChild(tbody);
// 遍历数组
for (const stu of res.data) {
tbody.insertAdjacentHTML("beforeend", `
<tr>
<th>${stu.id}</th>
<th>${stu.name}</th>
<th>${stu.age}</th>
<th>${stu.gender}</th>
<th>${stu.address}</th>
</tr>
`);
}
}
})
.catch((err) => console.log("出错了", err));
});
const btn = document.getElementById("btn");
const btn2 = document.getElementById("btn2");
const root = document.getElementById("root");
btn2.addEventListener("click", () => {
fetch("http://localhost:3000/students", {
// 请求方式
method: "post",
// 通过body去发送数据时,必须通过请求头来指定数据的类型
headers: {
"Content-type": "application/json"
},
// 请求数据
body: JSON.stringify({ name: "白骨精", age: 16, gender: "女", address: "白骨洞" })
});
});
btn.addEventListener("click", () => {
// param参数
fetch("http://localhost:3000/students/1") // 直接后面写id
.then(res => {
if (res.status === 200) {
return res.json();
} else {
throw new Error("请求失败");
}
})
.then(res => {
console.log(res.data);
})
.catch((err) => console.log("出错了", err));
});
// 点击login-btn后实现登录功能
const loginBtn = document.getElementById("login-btn");
const root = document.getElementById("root");
loginBtn.addEventListener("click", () => {
// 获取用户输入的用户名和密码
const username = document.getElementById("username").value.trim();
const password = document.getElementById("password").value.trim();
// 调用fetch发送请求来完成登录
fetch("http://localhost:3000/login", {
method: "post",
headers: { "Content-type": "application/json" },
body: JSON.stringify({ username, password }),
}).then(res => res.json())
.then(res => {
if (res.status !== "ok") throw new Error("用户名或密码错误");
root.innerHTML = `
<h1>欢迎 ${res.data.nickname} 回来!</h1>
<hr>
<button id="load-btn">加载数据</button>
`;
})
.catch(err => {
document.getElementById("info").textContent = "用户名或密码错误";
});
});
localStorage
const loginBtn = document.getElementById("login-btn");
const root = document.getElementById("root");
// 判断用户是否登录
if (localStorage.getItem("nickname")) {
// 用户已经登录
root.innerHTML = `
<h1>欢迎 ${localStorage.getItem("nickname")} 回来!</h1>
<hr>
<button id="load-btn" onclick="loadData()">加载数据</button>
<hr>
<div id="data"></data>
`;
} else {
loginBtn.addEventListener("click", () => {
const username = document.getElementById("username").value.trim();
const password = document.getElementById("password").value.trim();
fetch("http://localhost:3000/login", {
method: "post",
headers: { "Content-type": "application/json" },
body: JSON.stringify({ username, password }),
}).then(res => res.json())
.then(res => {
if (res.status !== "ok") throw new Error("用户名或密码错误");
/*
登录成功后,需要保持用户的登录的状态,需要将用户的信息存储到某个地方
需要将用户信息存储到本地存储
所谓的本地存储就是浏览器自身的存储空间,可以将用户的数据存储到浏览器内部
sessionStorage 中存储的数据 页面一关闭就会丢失
licalStorage 存储的时间比较长
登录成功,向本地存储中插入用户的信息
*/
localStorage.setItem("username", res.data.username);
localStorage.setItem("userId", res.data.id);
localStorage.setItem("nickname", res.data.nickname);
root.innerHTML = `
<h1>欢迎 ${res.data.nickname} 回来!</h1>
<hr>
<button id="load-btn" onclick="loadData()">加载数据</button>
<hr>
<div id="data"></data>
`;
})
.catch(err => {
document.getElementById("info").textContent = "用户名或密码错误";
});
});
}
btn01.addEventListener("click", () => {
// console.log(sessionStorage);
/*
setItem() 用来存储数据
getItem() 用来获取数据
removeItem() 删除数据
clear() 清空数据
*/
// sessionStorage.setItem("name", "孙悟空");
// sessionStorage.setItem("age", "18");
// sessionStorage.setItem("gender", "男");
// sessionStorage.setItem("address", "花果山");
localStorage.setItem("name", "孙悟空");
localStorage.setItem("age", "18");
localStorage.setItem("gender", "男");
localStorage.setItem("address", "花果山");
});
btn02.addEventListener("click", () => {
const name = localStorage.getItem("name");
console.log(name);
});
token
const jwt = require("jsonwebtoken");
const obj = {
name: "孙悟空",
age: 18,
gender: "男"
}
const token = jwt.sign(obj, "hello", {
expiresIn: "1h"
});
try {
const decodeData = jwt.verify(token, "hello");
console.log(decodeData);
} catch (e) {
// 说明token解码失败
console.log("无效token");
}
/*
问题:
- 现在是问题以后直接将用户信息存储到了localStorage
- 主要存在两个问题:
1.数据安全问题
2.服务器不知道你有没有登录
- 解决问题:
如何告诉服务器客户端的登录状态
- rest风格的服务器是无状态的服务器,所以注意不要再服务器中存储用户的数据
- 服务器中不能存储用户信息,可以将用户信息发送给客户端保存
比如:{id:"xxx", username:"xxx", email:"xxx"}
客户端每次访问服务器时,直接将用户信息发回,服务器就可以根据用户信息来识别用户的身份
- 但是如果将数据直接发送给客户端同样会有数据安全的问题,
所以我们必须对数据进行加密,加密以后再发送给客户端保存,这样即可避免数据的泄露
- 在node中可以直接使用jsonwebtoken这个包来对数据进行加密
jsonwebtoken(jwt) -> 通过对json加密后,生成一个web中使用的令牌
- 使用步骤:
1.安装
yarn add jsonwebtoken
*/
const loginBtn = document.getElementById("login-btn");
const root = document.getElementById("root");
function loadData() {
// 当我们访问的是需要权限的api时。必须在请求中附加权限的信息
// token 一般都是通过请求头来发送
const token = localStorage.getItem("token");
fetch("http://localhost:3000/students", {
headers: {
// "Bearer xxxxx"
"authorization": "Bearer " + token
}
})
.then(res => {
if (res.status === 200) {
// res.json() 可以用来读取json格式的数据 Promise
return res.json();
} else {
throw new Error("请求失败");
}
})
.then(res => {
if (res.status === "ok") {
// 创建一个table
const dataDiv = document.getElementById("data");
const table = document.createElement("table");
dataDiv.appendChild(table);
table.insertAdjacentHTML("beforeend", "<caption>学生列表</caption>");
table.insertAdjacentHTML("beforeend", `
<thead>
<tr>
<th>学号</th>
<th>姓名</th>
<th>年龄</th>
<th>性别</th>
<th>地址</th>
</tr>
</thead>
`);
const tbody = document.createElement("tbody");
table.appendChild(tbody);
// 遍历数组
for (const stu of res.data) {
tbody.insertAdjacentHTML("beforeend", `
<tr>
<th>${stu.id}</th>
<th>${stu.name}</th>
<th>${stu.age}</th>
<th>${stu.gender}</th>
<th>${stu.address}</th>
</tr>
`);
}
}
})
.catch((err) => console.log("出错了", err));
}
// 判断用户是否登录
if (localStorage.getItem("nickname")) {
// 用户已经登录
root.innerHTML = `
<h1>欢迎 ${localStorage.getItem("nickname")} 回来!</h1>
<hr>
<button id="load-btn" onclick="loadData()">加载数据</button>
<button onclick="localStorage.clear()">注销</button>
<hr>
<div id="data"></data>
`;
} else {
loginBtn.addEventListener("click", () => {
const username = document.getElementById("username").value.trim();
const password = document.getElementById("password").value.trim();
fetch("http://localhost:3000/login", {
method: "post",
headers: { "Content-type": "application/json" },
body: JSON.stringify({ username, password }),
}).then(res => res.json())
.then(res => {
if (res.status !== "ok") throw new Error("用户名或密码错误");
localStorage.setItem("token", res.data.token);
localStorage.setItem("nickname", res.data.nickname);
root.innerHTML = `
<h1>欢迎 ${res.data.nickname} 回来!</h1>
<hr>
<button id="load-btn" onclick="loadData()">加载数据</button>
<button onclick="localStorage.clear()">注销</button>
<hr>
<div id="data"></data>
`;
})
.catch(err => {
document.getElementById("info").textContent = "用户名或密码错误";
});
});
}
服务器:
const express = require("express");
// 引入jwt
const jwt = require("jsonwebtoken");
const app = express();
app.use(express.urlencoded({ extended: true }));
// 解析json格式请求体的中间件
app.use(express.json());
app.use((req, res, next) => {
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Methods", "GET,POST,PUT,DELETE,PATCH");
res.setHeader("Access-Control-Allow-Headers", "Content-type,Authorization");
next();
});
// 定义一个登录的路由
app.post("/login", (req, res) => {
// 获取用户输入的用户名和密码
const { username, password } = req.body;
// 验证用户名和密码
if (username === "admin" && password === "123123") {
// 登录成功,生成token
const token = jwt.sign({ id: "12345", username: "admin", nickname: "超级管理员" }, "hello", {
expiresIn: "1d"
});
res.send({
status: "ok",
data: { token, nickname: "超级管理员" }
});
} else {
// 登录失败
res.status(403).send({
status: "error",
data: "用户名或密码错误"
});
}
});
// 统一的api
// 定义学生信息相关的路由
app.get("/students", (req, res) => {
try {
// 这个路由必须在用户登录后才能访问
// 需要检查用户是否登录
// 读取请求头
const token = req.get("Authorization").split(" ")[1];
// 对token进行解码
const decodeToken = jwt.verify(token, "hello");
// console.log(decodeToken);
// 解码成功,token有效
// 返回学生信息
res.send({
status: "ok",
data: STU_ARR
});
} catch (e) {
// 解码错误,用户token无效
res.status(403).send({
status: "ok",
data: "token无效"
});
}
});
// 获取按钮
const btn01 = document.getElementById("btn01");
const btn02 = document.getElementById("btn02");
const btn03 = document.getElementById("btn03");
let controller;
btn01.onclick = () => {
// 终止请求
// 创建一个AbortController
controller = new AbortController();
// setTimeout(() => {
// controller.abort();
// }, 3000);
// 点击按钮向test发送请求
fetch("http://localhost:3000/test", {
signal: controller.signal
})
.then(res => console.log(res))
.catch(err => console.log(err));
}
btn02.onclick = () => {
controller && controller.abort();
}
btn03.onclick = async () => {
// 注意:将promise改写为await时,一定要写try-catch
try {
const res = await fetch("http://localhost:3000/students");
const data = await res.json();
console.log(data);
} catch (e) {
console.log("出错了", e);
}
}
axios
document.getElementById("btn1").onclick = () => {
// 直接调用axios发送请求
axios({
method: "post",
url: "http://localhost:3000/students",
data: {
name: "唐僧",
age: 18,
gender: "男",
address: "女儿国"
}
// data:"name=swk&age=18"
}).then(res => console.log(res.data))
.catch(e => console.log("出错了", e));
}
document.getElementById("btn2").onclick = () => {
// 直接调用axios发送请求
axios({
method: "get",
url: "http://localhost:3000/students"
}).then(res => console.log(res.data)) // axios默认只会在响应状态为2xx时才会调用then
.catch(e => console.log("出错了", e));
}
axios的配置对象
document.getElementById("btn1").onclick = () => {
// 直接调用axios发送请求
axios({
// baseURL 指定服务器的根目录(路径的前缀)
baseURL: "http://localhost:3000",
// 请求地址
url: "students",
// 请求方法,默认是get
method: "post",
// 指定请求头
// headers:{"Content-Type":"application/json"} // 默认
// 请求体
// data:"name=swk&age=16
data: {
name: "唐僧",
age: 18,
gender: "男",
address: "女儿国"
},
// params 用来指定路径中的查询字符串
params: {
id: 1,
name: "swk"
},
// timeout 过期时间
timeout: 1000,
// 用来终止请求
// signal
// transformRequest 可以用来处理请求数据(data)
// 它需要一个数组作为参数,数组可以接受多个函数,请求发送时多个函数会按照顺序执行
// 函数在执行时,会接收到两个参数data和headers
transformRequest: [function (data, headers) {
// 可以在函数中对data和headers进行修改
data.name = "猪八戒";
headers["Content-Type"] = "application/json";
return data;
}, function (data, headers) {
// console.log(1111, data, headers);
// 最后一个函数必须返回一个字符串,才能使得数据有效
return JSON.stringify(data);
}]
}).then(res => console.log(res.data))
.catch(e => console.log("出错了", e));
}
axios.defaults.baseURL = "http://localhost:3000";
axios.defaults.headers.common['Authorization'] = "Bearer " + localStorage.getItem("token");
document.getElementById("btn1").onclick = () => {
// 直接调用axios发送请求
axios({
url: "students",
method: "post",
data: {
name: "唐僧",
age: 18,
gender: "男",
address: "女儿国"
},
// timeout 过期时间
timeout: 1000
}).then(res => console.log(res.data))
.catch(e => console.log("出错了", e));
}
axios实例
axios.defaults.baseURL = "http://localhost:3000";
axios.defaults.headers.common['Authorization'] = "Bearer " + localStorage.getItem("token");
/*
axios实例相当于是axios的一个副本
axios的默认配置在实例也同样会生效
但是我们可以单独修改axios实例的默认配置
*/
const instance = axios.create({
baseURL: "http://localhost:4000"
});
document.getElementById("btn1").onclick = () => {
instance.get("stduents")
.then(res => console.log(res.data))
.catch((e) => console.log("出错了", e));
}
axios拦截器
axios.defaults.baseURL = "http://localhost:3000";
/*
axios的拦截器可以对请求或响应进行拦截,在请求发送前或响应读取前处理数据
拦截器只对当前实例有效
*/
// 添加请求拦截器
axios.interceptors.request.use(function (config) {
// 在发送请求之前做些什么
// 拦截器执行了
// config.data.name = "猪";
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);
});
document.getElementById("btn1").onclick = () => {
axios({
url: "students",
method: "post",
data: { name: "猪八戒" }
})
.then(res => console.log(res.data))
.catch((e) => console.log("出错了", e));
}