目录
1、HTTP 相关 (准备工作)
1.1 HTTP 请求交互的基本过程
1.2 请求报文
1.3 响应报文
1.4 常见的响应状态码
1.5 请求方式与请求参数
1.6 HTTP 及其版本(HTTP1.0、HTTP1.1、HTTP2.0、HTTP3.0)详解
http1.0
http1.1
http2.0
http3.0/QUIC
2.1 API 分类
2.2 使用 json-server 搭建 REST API
2.2.1 json-server 是什么?
2.2.2 使用 json-server
2.2.3 使用浏览器访问测试
2.2.4 使用 postman 测试接口
2.2.5 一般 http 请求与 ajax 请求
3 axios 的理解和使用
3.1 axios 是什么?
3.2 axios 特点
3.2 axios 基本使用
3.3 axios 常用配置项
3.4 axios.create方法
3.5 axios中的拦截器
3.6 axios中取消请求
3.7 axios批量发请求
- 前台应用从浏览器端向服务器发送 HTTP 请求 (请求报文)
- 后台服务器接收到请求后, 调度服务器应用处理请求, 向浏览器端返回 HTTP 响应 (响应报文)
- 浏览器端接收到响应, 解析显示响应体 / 调用监视回调
HTTP 请求报文由请求行、请求头、空行和请求包体 (body) 组成。
- 请求行
methodurl:GET/product_detail?id=2POST/login- 多个请求头
Host:www.baidu.com
Cookie:BAIDUID=AD3B0FA706E;BIDUPSID=AD3B0FA706;
Content-Type:application/x-www-form-urlencoded
或者 application/json- 请求体
username=tom&pwd=123
{“username”:“tom”,“pwd”:123}
HTTP 响应报文由状态行、响应头部、空行和响应包体 (body) 组成
- 响应状态行: statusText
- 多个响应头
Content-Type:text/html;charset=utf-8
Set-Cookie:BD_CK_SAM=1;path=/- 响应体 html 文本 / json 文本 / js/css / 图片…
100,接受的请求正在处理,信息类状态码
200
OK 请求成功。一般用于 GET 与 POST 请求201
Created 已创建。成功请求并创建了新的资源
301,永久性重定向,表示资源已被分配了新的 URL
比如,我们访问 http://www.baidu.com 会跳转到 https://www.baidu.com,发送请求之后,就会返回 301 状态码,然后返回一个 location,提示新的地址,浏览器就会拿着这个新的地址去访问。
302,临时性重定向,表示资源临时被分配了新的 URL
比如未登陆的用户访问用户中心重定向到登录页面
303,表示资源存在另一个 URL,用 GET 方法获取资源
304,(未修改) 自从上次请求后,请求网页未修改过。服务器返回此响应时,不会返回网页内容401
Unauthorized 未授权 / 请求要求用户的身份认证404
NotFound 服务器无法根据客户端的请求找到资源500
InternalServerError 服务器内部错误,无法完成请求
1. 请求方式
- GET: 从服务器端读取数据 查 (get 请求不能发请求体参数)
- POST: 向服务器端添加新数据 增
- PUT: 更新服务器端数据 改
- 4DELETE: 删除服务器端数据 删
2. 请求参数
1. query 参数(查询字符串参数)
- 参数包含在请求地址中,格式是:
/xxxx?name=tom&age=18
- 敏感数据不要使用 query 参数,因为参数是地址的一部分,比较危险
- 备注:query 参数又称查询字符串参数,编码方式为
urlencoded
2. params 参数
- 参数包含在请求地址中,格式是:
http://localhost:3000/add_person/tom/18
- 敏感数据不要使用 params 参数,因为参数是地址的一部分,比较危险
3. 请求体参数
- 参数包含在请求体中,可以通过浏览器开发工具查看
- 常用两种格式:
格式一:urlencoded
格式
例如:name=tom&age=18
对应请求头:Content-Type:application/x-www-form-urlencoded
格式二:json 格式
例如{“name”:“Tom”,“age”:18}
对应请求头:Content-Type:application/json
特别注意
- GET 请求不能携带请求体参数,因为 GET 请求没有请求体
- 理论上,一次请求可以随意使用上述三种类型参数中的任何一种,甚至一次请求 3 个参数可以用三种形式携带,但一般不这么做。
- 一般来说,有一些约定俗成的规矩:
(1)例如 form 表单发送 POST 请求时:自动使用请求体参数,用 urlencoded 编码。
(2)例如 jQuery 发送 ajax-post 请求时:自动使用请求体参数,用 urlencoded 编码。- 开发中请求到底发给谁?用什么请求?携带什么参数?。。。参考项目的 API 接口文档
三次握手
)不允许断点续传
,缓存处理,
在 HTTP1.0 中主要使用 header 里的If-Modified-Since
,Expires 来作为缓存判断的标准,HTTP1.1 则引入了更多的缓存控制策略如Entity tag
,If-None-Match
,If-Unmodified-Since
,If-Match
、Last-Modify
等更多可供选择的缓存头来控制缓存策略。
带宽优化及网络连接的使用
HTTP1.0 中,存在一些浪费带宽的现象,例如客户端只是需要某个对象的一部分,而服务器却将整个对象送过来了,并且不支持断点续传功能,HTTP1.1 则在请求头引入了 range 头域,它允许只请求资源的某个部分,即返回码是 206(Partial Content),这样就方便了开发者自由的选择以便于充分利用带宽和连接。
错误通知的管理:
在 HTTP1.1 中新增了 24 个错误状态响应码,如 409(conflict)表示请求的资源与资源的当前状态发生冲突;410(Gone)表示服务器上某个资源被永久性删除。
Host 头处理:
在 HTTP1.0 中认为每台服务器都绑定一个唯一的 IP 地址,因此,请求消息中的 URL 并没有传递主机名。但随着虚拟主机技术的发展,在一台物理服务器上可以存在多个虚拟主机,并且他们共享一个 IP 地址。HTTP1.1 的请求消息和响应消息都应支持 Host 头域,且请求消息中如果没有 Host 头域会报告一个错误(400 Bad Request)。
长连接(Connection: keep-alive)
:
HTTP1.1 支持长连接和请求的流水线处理,在一个 TCP 连接上可以传送多个 HTTP 请求和相应,减少了建立和关闭连接的消耗和延迟,在 HTTP1.1 中默认开启Connection:keep-alive
,一定程度上弥补了 HTTP1.0 每次请求都要创建连接的缺点。(串行)
多路复用
HTTP2.0 使用了多路复用的技术,做到同一个连接并发处理多个请求
,而且并发请求的数量比 HTTP1.1 打了好几个数量级。HTTP1.1 也可以多建立几个 TCP 连接,来处理更多并发的请求,但是创建 TCP 连接本身也是有开销的。(并行)
头部数据压缩
在 HTTP1.1 中,HTTP 请求和相应都是由状态行、请求 / 响应头部、消息主体三部分组成。一般而言,消息主体都会经过 gzip 压缩,或者本身传输的就是压缩过后的二进制文件,但状态行和头部却没有经过任何压缩,直接以纯文本传输。随着 Web 功能越来越复杂,每个页面产生的请求数也越来越多,导致消耗在头部的流量越来越多,尤其是每次都要传输 UserAgent、Cookie 这类不会频繁变动的内容,完全是一种浪费。
HTTP1.1 不支持 header 数据的压缩,HTTP2.0 使用 HPACK 算法对 header 的数据进行压缩,这样数据体积小了,在网络上传输就会更快。
(http 2.0 使用 encoder 减少需要传输的 header 大小,通讯双方各自 cache 一份 header field 表,避免了 header 的重复传输,又减少了传输的大小)
服务端推送
服务端推送是一种在客户端请求之前发送数据的机制。网页使用了许多资源:HTML、样式表、脚本、图片等等。在 HTTP1.1 中这些资源每一个都必须明确地请求。这是一个很慢的过程。浏览器从获取 HTML 开始,然后在它解析和评估页面地时候,增量地获取更多的资源。因为服务器必须等待浏览器做每一个请求,网络经常是空闲和未充分使用地。
为了改善延迟,HTTP2.0 引入 server push,它允许服务端推送资源给浏览器,在浏览器明确的请求之前,免得客户端再次创建连接发送请求到服务器端获取。这样客户端可以直接从本地加载这些资源,不用再通过网络。
数据流优先级
对数据流可以设置优先值,这个优先值决定了客户端和服务端处理不同的流采用不同的优先级策略。(例如浏览器在等待关键的 CSS 或者 JS 文件完成对页面的渲染时,服务器却在专注于加载图片)
使用二进制分帧层
在应用层与传输层之间增加一个二进制分帧层,在不改动 HTTP 的语义(HTTP 方法、状态码、URI 及首部字段)的情况下,将所有传输的信息分割为更小的消息和帧,并对它们采用二进制格式编码,改进传输性能,实现低延迟和高吞吐量。
其中 HTTP1.x 的首部信息会被封装到 Headers 帧,而我们的 request body 则封装到 Data 帧里面。
基于 UDP 减少了TCP三次握手
及 TLS 握手时间:
HTTP1.0/1.1/2.0/HTTPS 都使用了TCP
进行传输,HTTP2/HTTPS 还需要使用TLS
协议来进行安全传输。这就存在两个握手延迟,而基于无连接 UDP 协议的 QUIC,连接建立时只需要一次交互,相当于半个握手的时间。
解决多路复用丢包时的线头阻塞问题:
QUIC 中一个连接上的多个 stream 之间没有依赖。所以当发生丢包时,只会影响当前的 stream,从而避免线头阻塞问题。
优化重传策略:
TCP 中重传序号一致
问题而导致的发送端不能明确 ACK 是初始包的确认还是对重传包的确认。为了避免这一问题,QUIC 发送端设置初始与重传的每一个封包都改用一个新
的编号,unique packet number,每一个编号都唯一而且严格递增,这样每次在收到 ACK 时,就可以依据编号明确的判断这个 ACK 是来自初始封包或者是重传封包。
流量控制:
QUIC 为了避免出现流量极慢的 stream,采用了连线层 (connection flow control) 和 Stream 层的 (stream flow control) 流量控制,限制单一 Stream 可以占用的最大 buffer size。
连接迁移:
QUIC 连接不以四元组作为标识(源 IP、源端口、目的 IP、目的端口)(连接变换时就需要切换 TCP 连接),而是使用一个 64 位的随机数 Connection ID,对应每个 stream,即使 IP 或者端口发生变化,只要 Connection ID 没有变化,那么连接依然可以维持。
- REST API: restful (Representational State Transfer (资源) 表现层状态转化)
(1) 发送请求进行 CRUD 哪个操作由请求方式来决定
(2) 同一个请求路径可以进行多个操作
(3) 请求方式会用到 GET/POST/PUT/DELETE- 非 REST API: restless
(1) 请求方式不决定请求的 CRUD 操作
(2) 一个请求路径只对应一个操作
(3) 一般只有 GET/POST
用来快速搭建 REST API 的工具包
10 秒快速建一个服务器
在线文档: GitHub - typicode/json-server: Get a full fake REST API with zero coding in less than 30 seconds (seriously)
npm install -g json-server
db.json
json-server --watch db.json
// db.json 文件
{
"posts": [
{ "id": 1, "title": "json-server", "author": "typicode" },
{ "id": 2, "title": "json-server2", "author": "typicode" }
],
"comments": [
{ "id": 1, "body": "some comment", "postId": 1 }
],
"profile": { "name": "typicode" }
}
http://localhost:3000/posts
http://localhost:3000/posts/1
测试 GET/POST/PUT/DELETE
json-server 模拟的服务器,id 必须用 params 带,其他的可以用请求体:
http://localhost:3000/students/3
GET query 参数
get Params 参数
请求体 urlencoded 形式
请求体 json 形式
ajax
(xhr)请求是一种特别的 http
请求XHR
或fetch
发出的才是ajax
请求,其它所有的都是非 ajax 请求接口文档是: 由 工具 api-doc 写一些注释,就能生成
拿到文档 先用 postman 测试
引入
- axios 调用的返回值是 Promise 实例。
- 成功的值叫 response,失败的叫 error。
- axios 成功的值是一个 axios 封装的 response 对象,服务器返回的真实数据在 response.data 中
- 携带 query 参数的配置项叫做 params
- 携带 params时, 需要自己手动拼接在url中
axios(config): 通用 / 最本质的发任意类型请求的方式axios(url[, config]):
可以只指定 url 发 get 请求axios.request(config):
等同于 axios(config)axios.get(url[, config]):
发 get 请求axios.delete(url[, config]):
发 delete 请求axios.post(url[, data, config])
: 发 post 请求axios.put(url[, data, config]):
发 put 请求
axios.defaults.xxx:
请求的默认全局配置(method\baseURL\params\timeout…)axios.interceptors.request.use():
添加请求拦截器axios.interceptors.response.use():
添加响应拦截器
axios.create([config]):
创建一个新的 axios(它没有下面的功能)
axios.Cancel():
用于创建取消请求的错误对象axios.CancelToken():
用于创建取消请求的 token 对象
axios.isCancel(): 是否是一个取消请求的错误axios.all(promises)
: 用于批量执行多个异步请求
axios.spread(): 用来指定接收所有成功数据的回调函数的方法
————————————————
const btn1 = document.getELementById('btn1')
const btn2 = document.getELementById('btn2')
const btn3 = document.getELementById('btn3')
const btn4 = document.getELementById('btn4')
const btn4 = document.getELementById('btn4')
const btn5 = document.getELementById('btn5')
const personId = document.getElementByid('person_id')
const personName = document.getElementByid('person_name')
const personAge = document.getElementByid('person_age')
const personUpdateId = document.getElementByid('person_update_id')
const personUpdateName = document.getElementByid('person_update_name')
const personUpdateAge = document.getElementByid('person_update_age')
const personDeleteId = document.getElementByid('person_delete_id')
// 获取所有人信息-发送 GET 请求-不携带参数
btn1.onclick=()=>{
// 完整版
axios({
url:'http://localhost:5000/test1?delay=5000',//请求地址,延迟5秒
method:'GET',
timeout:2000, //配置超时时间
}).then(
response=>{ console.log('请求成功了!',response.data);},
error=>{console.log('请求失败了!',error);}
)
// 精简版
axios.get('http://localhost:5000/person').then(
response=>{ console.log('请求成功了!',response.data); },
error=>{console.log('请求失败了!',error);}
)
}
//获取某个人的信息-发送GET请求-携带query参数
btn2.onclick=()=>{
// 完整版
axios({
url:'http://localhost:5000/person',
method:'GET',
params:{id:personId.value}
// 此处写的是params,但是携带的是query参数
}).then(
response=>{ console.log('请求成功了!',response.data);},
error=>{console.log('请求失败了!',error);}
)
// 精简版
axios.get('http://localhost:5000/person', {params:{id:personId.value}}).then(
response=>{ console.log('请求成功了!',response.data); },
error=>{console.log('请求失败了!',error);}
)
}
// 添加一个人,年龄和名字--发送POST请求--携带json编码参数或urlencoded编码
btn3.onclick=()=>{
// 完整版
axios({
url:'http://localhost:5000/person',
method:'POST',
// 1. 携带请求体参数,json编码
data:{name:personName.value,age:personAge}
// 2. 携带请求体参数,urlencoded编码
data:`name=${personName.value}&age=${personAge.value}`
}).then(
response=>{ console.log('请求成功了!',response.data);},
error=>{console.log('请求失败了!',error);}
)
// 精简版
// 1. 携带请求体参数,json编码
axios.post('http://localhost:5000/person',
{name:personName.value,age:personAge} ).then(
response=>{ console.log('请求成功了!',response.data); },
error=>{console.log('请求失败了!',error);}
)
// 2. 携带请求体参数,urlencoded编码
axios.post('http://localhost:5000/person',
`name=${personName.value}&age=${personAge.value}`).then(
response=>{ console.log('请求成功了!',response.data); },
error=>{console.log('请求失败了!',error);}
)
}
// 更新某个人--发送PUT请求--携带json编码参数或urlencoded编码
btn4.onclick=()=>{
// 完整版
axios({
url:'http://localhost:5000/person',
method:'PUT',
data:{id:personUpdateId.value,
name:personUpdateName.value,
age:personUpdateAge.value,
}
}).then(
response=>{ console.log('请求成功了!',response.data);},
error=>{console.log('请求失败了!',error);}
)
// 精简版
axios.put('http://localhost:5000/person',{id:personUpdateId.value,
name:personUpdateName.value,
age:personUpdateAge.value}).then(
response=>{ console.log('请求成功了!',response.data); },
error=>{console.log('请求失败了!',error);}
)
}
// 删除某个人--发送DELETE请求--携带parmas
btn5.onclick=()=>{
// 完整版
axios({
url:`http://localhost:5000/person/${personDeleteId.value}`,
method:'DELETE',
}).then(
response=>{ console.log('请求成功了!',response.data);},
error=>{console.log('请求失败了!',error);}
)
}
axios.defaults.xxx:
请求的默认全局配置
// 给 axios配置默认配置
axios.defaults.timeout = 2000
axios.defaults.headers = {name:atjm}
axios.defualts.baseURL = 'http://localhost:5000'
const btn1 = document.getELementById('btn')
btn.onclick=()=>{
// 完整版
axios({
url:'http://localhost:5000/person',//请求地址,
method:'GET', //请求方式
//params:{a:1,b:2},//配置query
//data:{a:3,d:3}, //配置请求体 参数 (json)
//data:'e=5&f=6' //配置请求体参数(urlencoded)
timeout:2000, //配置超时时间
header:{demo:123} //配置请求头
responseType:'json' //配置相应数据格式(默认值)
}).then(
response=>{ console.log('请求成功了!',response.data);},
error=>{console.log('请求失败了!',error);}
)
}
axios.create
方法axios.create([config])
: 创建一个新的 axios(它没有下面的功能)
axios.create(config)
- 根据指定配置创建一个新的
axios
, 也就是每个新axios
都有自己的配置- 新
axios
只是没有取消请求和批量发请求的方法, 其它所有语法都是一致的- 为什么要设计这个语法?
(1) 需求: 项目中有部分接口需要的配置与另一部分接口需要的配置不太一样,
(2) 解决: 创建 2 个新 axios, 每个都有自己特有的配置, 分别应用到不同要求的接口请求中
const axios2 = axios.create({
timeout: 2000
//headers: {name:atjm}
baseURL:'http://localhost:5000'
})
// 需要放在defaluts前方
btn.onclick =()=>{
// 完整版
axios2({
url:'http://localhost:5000/person',//请求地址,
method:'GET', //请求方式
//params:{a:1,b:2},//配置query
//data:{a:3,d:3}, //配置请求体 参数 (json)
//data:'e=5&f=6' //配置请求体参数(urlencoded)
timeout:2000, //配置超时时间
header:{demo:123} //配置请求头
responseType:'json' //配置相应数据格式(默认值)
}).then(
response=>{ console.log('请求成功了!',response.data);},
error=>{console.log('请求失败了!',error);}
)
}
axios
中的拦截器
axios
请求拦截器
- 是什么?
在真正发送请求前执行的一个回调函数- 作用:对所有请求做统一的处理:追加请求头、追加参数、界面 loading 提示等等
axios
响应拦截器
- 是什么?
得到响应之后执行的一组回调函数- 作用:若请求成功,对成功的数据进行处理,
若请求失败,对失败进行统一的操作
拦截器函数 /
ajax
请求 / 请求的回调函数的调用顺序。
说明: 调用axios()
并不是立即发送ajax
请求, 而是需要经历一个较长的流程
流程: 请求拦截器 2 => 请求拦截器 1 => 发ajax
请求 => 响应拦截器 1 => 响应拦截器 2 => 请求的回调
注意: 此流程是通过promise
串连起来的, 请求拦截器传递的是config
, 响应拦截器传递的是response
// 请求拦截器
axios.interceptors.request.use((config)=>{
if(Date.now()%2 === 0){
config.headers.token ='atuge'
}
return config;
})
// 响应拦截器
axios.interceptors.response.use(
response=>{
console.log('响应拦截器成功的回调执行了',response)
if(Date.now()%2 === 0) return response.data
else return '时间戳不是偶数,不能给你数据'
},
error=>{
console.log('响应拦截器失败的回调执行了')
alert(error)
return new Promise(()=>{})
}
)
btn.onclick=() async() =>{
const result = await axios.get('http://localhost:5000/person')
console.log(result);
}
axios
中取消请求
- 基本流程
配置cancelToken
对象
缓存用于取消请求的 cancel 函数
在后面特定时机调用 cancel 函数取消请求
在错误回调中判断如果 error 是 cancel, 做相应处理- 实现功能
点击按钮, 取消某个正在请求中的请求
在请求一个接口前, 取消前面一个未完成的请求
取消请求 1
let cancel;
btn1.onclick = async() =>{
// 完整版
axios({
url:'http://localhost:5000/person',
cancelToken:new CancelToken((c)=>{
console.log(c) //c是一个函数,调用c可以关闭本次请求
cancel = c
})
}).then(
response=>{ console.log('请求成功了!',response.data);},
error=>{console.log('请求失败了!',error);}
)
}
btn2.onclick=()=>{
cancel('任性,就是不要了')
}
取消请求 2
// CancelToken 能为一次请求“打标注"
const {CancelToken, isCancel} = axios
let cancel;
btn1.onclick=() async() =>{
if(cancel) cancel()
axios({
url:'http://localhost:5000/test?delay=3000',
cancelToken:new CancelToken((c)=>{
console.log(c) //c是一个函数,调用c可以关闭本次请求
cancel = c
})
}).then(
response=>{ console.log('请求成功了!',response.data);},
error=>{
if(isCancel(error))
console.log('用户取消了请求,原因是',error,message)
else
console.log('请求失败了!',error);}
)
}
btn2.onclick=()=>{
cancel('任性,就是不要了')
}
取消请求 3 取消请求+拦截器
// CancelToken 能为一次请求“打标注"
// isCancel 判断错误是真的错误还是用户取消请求导致的
const {CancelToken,isCancel}= axios
let cancel;
// 请求拦截器
axios.interceptors.request.use((config)=>{
if(cancel) cancel('取消了')
config.CancelToken = new CancelToke((c)=> cancel=c)
return config;
})
// 响应拦截器
axios.interceptors.response.use(
response=>{ return response.data },
error=>{
// 如果进入判断,证明是用户取消了请求
if(isCancel(error))
console.log('用户取消了请求,原因是',error.message)
else
console.log('失败了',error)
return new Promise(()=>{})
}
)
btn1.onclick = async() =>{
const result = await axios.get('http://localhost:5000/test?delay=3000')
}
btn2.onclick=()=>{
cancel('任性,就是不要了')
}
axios
批量发请求btn1.onclick = async() =>{
axios.all([
axios.get('http://localhost:5000/test'),
axios.get('http://localhost:5000/test2?delay=3000'),
axios.get('http://localhost:5000/test3')
]).then(
response=>{ console.log('请求成功了!',response.data);},
error=>{console.log('请求失败了!',error);}
)
}