HTTP相关

180 阅读10分钟

缓存

浏览器缓存保存着用户通过HTTP请求获取的所有资源,下一次请求时可以直接获取资源,避免向服务器发出多余的请求。

通俗的说,就是在你访问过一次某个网站之后,这个站点的文字、图片等所有资源都被下载到本地了,下次再访问该网站时判断是否满足缓存条件,如果满足就不用再花费时间去等待资源的获取了。

分类

浏览器缓存分为:

  • 强制缓存
  • 协商缓存

当命中强制缓存时,就直接返回存放在本地的资源;如果没有命中,就会发请求到服务器看是否命中协商缓存。

流程

强制缓存

客户端第一次向服务器发送请求,不存在缓存的结果和标示,直接向服务器发送请求。

服务器返回数据,并将缓存规则放入到响应头中,一起返回给客户端。缓存规则存放在Expire(HTTP1.0)和Cache-Control(HTTP1.1)中。

Expire

expires: Tue, 13 Apr 2021 08:27:47 GMT

expires表示的是缓存过期的时间,这是个绝对时间。GMT表示的是格林威治时间,和北京时间相差8小时。

客户端首次发送请求到服务端,服务端返回的响应数据里会包含expires字段,当再次发送请求时,会先查看expires这个字段的时间是否过期,如果没有过期就直接在本地缓存里取数据,如果过期了,就发送请求到服务器,获取最新数据再更新缓存里的数据。

缺陷:由于expires是绝对时间,如果客户端自己改了本地时间,这个缓存就失效了。。。

Cache-Control

为了解决上面的问题,HTTP1.1中的响应头里新增了字段Cache-ControlCache-Control字段可以设置值为max-age,该字段表示资源缓存的最大有效时间,这是一个相对时间。

Cache-Control:max-age=200

这里代表最大缓存时间是200s。这里的计时开始是在第一次被请求时服务器记录的Request_time(请求时间)。 当客户端再发送请求时,浏览器会判断是否在这个范围内,如果在,就直接取缓存里的数据,如果不再才会再发送请求到服务器。

Cache-Control字段还包含以下几个值可以选择:

  • no-cache:需要进行协商缓存,发送请求到服务器确认是否使用缓存。
  • no-store:禁止使用缓存,每一次都要重新请求数据。
  • public:默认设置。可以进行代理器缓存。
  • private:不能被多用户共享。不可以进行代理器缓存。

一般会同时设置expiresCache-Control,如果同时设置了,Cache-Control的优先级会更高。

协商缓存

当强制缓存没有命中,浏览器会发请求到服务器,服务器根据请求头中的部分信息来判断是否命中缓存。如果命中,则返回304,告诉浏览器资源未更新,可使用本地的缓存。

流程

如果未命中强制缓存,浏览器发请求到服务器,服务器在返回数据同时,会在响应数据头部返回Last-Modified字段和Etag字段。当再次发送请求到服务器时,请求头会自动带上If-Modified-Since字段,这个字段值就是Last-Modified返回值。服务器拿到请求后会比对这个值,如果没有变化就返回304状态码,取本地缓存中数据,如果有变化就返回200,并把最新数据和最新的Last-Modified的值返回,并把数据放入到缓存中。

但是Last-Modified有缺陷:

  1. 因为时间是s级的,如果在短时间修改,Last-Modified并不会变化。
  2. 如果在一个周期内多次改变,最终又变回原来的值,我们应该视为内容未变,但是Last-Modified就显示变化了。

为了解决这个问题,Etag就出现了。Etag是内容的hash值,第一次请求,服务器会返回Etag在头部,再次请求,请求头带上If-no-match,它的值就是Etag。服务器会对值进行对比,如果没有变化,就直接返回304

Last-ModifiedEtag是可以同时设置的,服务器会优先校验Etag,如果Etag相等就会继续比对Last-Modified,最后才会决定是否返回304

缓存位置

缓存的内容放到内存或磁盘中,对应的标示是from disk cache(磁盘缓存)from memory cache(内存缓存)以及资源大小数值。

| 状态 | 类型 | 说明

| --- | --- |

| 200 | form memory cache | 不请求网络资源,资源在内存当中,一般脚本、字体、图片会存在内存当中

| 200 | form disk ceche | 不请求网络资源,在磁盘当中,一般非脚本会存在内存当中,如css等

| 200 | 资源大小数值 | 从服务器下载最新资源

| 304 | 报文大小 | 请求服务端发现资源没有更新,使用本地资源

关闭标签页,再打开时,缓存是在disk ceche中获取的。

F5刷新时,缓存是再memory cache获取的。

内容来源:

github.com/xiangxingch…

mp.weixin.qq.com/s/Wvc0lkLpg…

HTTP版本区别

HTTP1.0

1. 连接

HTTP1.0是串行链接,每一次通信后都要断开TCP连接,当下一次请求时要重新创建一个TCP连接。

2. 缓存

只有Expires字段,只有强制缓存。

HTTP1.1

1. 连接

HTTP1.1默认为持久连接。既TCP只需要连接一次,后面请求都会复用这个连接。这样就减少了每次连接带来的不必要的通信时间的浪费。

只有在服务端和客户端头部的Connection设置为Keep-Alive时才开启了持久连接,如果是close就是关闭状态。

但是这里的通信实质上还是串联的,再次请求必须等到上次响应返回后才能发起,如果上次的请求还没返回响应内容,下次请求就只能等着(就是常说的HTTP线头阻塞)。

如何解决呢:

A. 并发连接

对于一个域名允许分配多个长连接,那么相当于增加了任务队列,不至于一个队伍的任务阻塞其它所有任务。在RFC2616规定过客户端最多并发 2 个连接,不过事实上在现在的浏览器标准中,这个上限要多很多,Chrome 中是 6 个。

但其实,即使是提高了并发连接,还是不能满足人们对性能的需求。

B.域名分片

一个域名不是可以并发 6 个长连接吗?那我就多分几个域名。

比如content1.sanyuan.comcontent2.sanyuan.com。 这样一个sanyuan.com域名下可以分出非常多的二级域名,而它们都指向同样的一台服务器,能够并发的长连接数更多了,事实上也更好地解决了队头阻塞的问题。

2.缓存

增加了Cache-ControlLast-ModifiedIf-Modified-SinceEtagIf-no-match字段,扩展了缓存的功能。

3.Host

请求消息和响应消息都必须包含Host头部,以区分同一个物理主机中的不同虚拟主机的域名,可以达到负载均衡目的。

4.Range

允许范围请求,即在请求头中加入Range头部。

HTTP2.0

1.连接

多路复用。就是在一个TCP连接中可以存在多条流,也就是可以发送多个请求,服务端则可以通过帧中的标识知道该帧属于哪个流(即请求),通过重新排序还原请求。这样便从HTTP协议本身解决了线头阻塞问题。

首先,HTTP/2 认为明文传输对机器而言太麻烦了,不方便计算机的解析,因为对于文本而言会有多义性的字符,比如回车换行到底是内容还是分隔符,在内部需要用到状态机去识别,效率比较低。于是 HTTP/2 干脆把报文全部换成二进制格式,全部传输01串,方便了机器的解析。

原来Headers + Body的报文格式如今被拆分成了一个个二进制的帧,用Headers帧存放头部字段,Data帧存放请求体数据。分帧之后,服务器看到的不再是一个个完整的 HTTP 请求报文,而是一堆乱序的二进制帧。这些二进制帧不存在先后关系,因此也就不会排队等待,也就没有了 HTTP 的队头阻塞问题。 通信双方都可以给对方发送二进制帧,这种二进制帧的双向传输的序列,也叫做流(Stream)。HTTP/2 用流来在一个 TCP 连接上来进行多个数据帧的通信,这就是多路复用的概念。

可能你会有一个疑问,既然是乱序首发,那最后如何来处理这些乱序的数据帧呢?

首先要声明的是,所谓的乱序,指的是不同 ID 的 Stream 是乱序的,但同一个 Stream ID 的帧一定是按顺序传输的。二进制帧到达后对方会将 Stream ID 相同的二进制帧组装成完整的请求报文和响应报文。当然,在二进制帧当中还有其他的一些字段,实现了优先级和流量控制等功能,我们放到下一节再来介绍。

2.头部压缩

HTTP/2 针对头部字段,也采用了对应的压缩算法——HPACK,对请求头进行压缩。

3.服务器推送

在 HTTP/2 当中,服务器已经不再是完全被动地接收请求,响应请求,它也能新建 stream 来给客户端发送消息,当 TCP 连接建立之后,比如浏览器请求一个 HTML 文件,服务器就可以在返回 HTML 的基础上,将 HTML 中引用到的其他资源文件一起返回给客户端,减少客户端的等待。

HTTP3.0

HTTP/2.0 使用了多路复用,一般来说同一域名下只需要使用一个 TCP 连接。但当这个连接中出现了丢包的情况,那就会导致整个 TCP 都要开始等待重传,也就导致了后面的所有数据都被阻塞了。反而对于 HTTP/1.0 来说,可以开启多个 TCP 连接,出现丢包反倒只会影响其中一个连接,剩余的 TCP 连接还可以正常传输数据。

出现包阻塞的原因是因为底层TCP协议导致的问题,但是修改TCP协议是不现实的问题,就像typeof null === 'object'一样,修改这个问题会导致出现更多的问题。既然不能修改你,那就另起一个协议取代你。Google 基于 UDP 协议推出了一个的 QUIC 协议,并且使用在了 HTTP/3 上。 QUIC 基于 UDP,但是UDP本身存在不稳定性等诸多问题,所以QUIC在UDP的基础上新增了很多功能,比如多路复用、0-RTT、使用 TLS1.3 加密、流量控制、有序交付、重传等等功能。优点诸多,参考这里:

  • 避免包阻塞: 多个流的数据包在TCP连接上传输时,若一个流中的数据包传输出现问题,TCP需要等待该包重传后,才能继续传输其它流的数据包。但在基于UDP的QUIC协议中,不同的流之间的数据传输真正实现了相互独立互不干扰,某个流的数据包在出问题需要重传时,并不会对其他流的数据包传输产生影响。
  • 快速重启会话: 普通基于tcp的连接,是基于两端的ip和端口和协议来建立的。在网络切换场景,例如手机端切换了无线网,使用4G网络,会改变本身的ip,这就导致tcp连接必须重新创建。而QUIC协议使用特有的UUID来标记每一次连接,在网络环境发生变化的时候,只要UUID不变,就能不需要握手,继续传输数据。

内容来源:

juejin.cn/post/684490… juejin.cn/post/684490…