HTTP
超文本传输协议(HyperText Transfer Protocol,简称 HTTP)是一种用于分布式、协作式和超媒体信息系统的应用层协议。
超文本
超文本(HyperText,简称 HT)是使用超链接的方法,将不同空间的文档信息组织在一起的网状文本。它的文字中包含可以链接到其它位置或者其它文档的链接,允许从当前阅读位置直接切换到超文本链接所指向的位置。超文本的格式很多,目前最常用的就是超文本标记语言(HyperText Markup Language,简称 HTML)和富文本格式(Rich Text Format,简称 RTF)。
超媒体信息系统
超媒体信息系统(HyperMedia System,简称 HMS)不仅可以包含文字,还包含图片、声音、动画和影片等,这些媒体之间是通过超链接组织的。
网络模型
OSI 模型 | TCP/IP 四层模型 | TCP/IP 五层模型 |
应用层 | 应用层 | 应用层 |
表示层 | ||
会话层 | ||
传输层 | 传输层 | 传输层 |
网络层 | 网络层 | 网络层 |
数据链路层 | 网络接口层 | 数据链路层 |
物理层 | 物理层 |
开放式系统互联通信参考模型
开放式系统互联通信参考模型(Open System Interconnection Reference Model,简称 OSI 模型)由国际标准化组织(International Organization for Standardization,简称 ISO)提出,是一个试图使各种计算机在世界范围内互连为网络的标准框架,也可以理解为定义了通用的网络通信规范。
第一层 物理层
主要定义了物理设备的标准,它的主要作用是通过物理设备将数字信号转换为模拟信号,到达目标节点时,再将模拟信号转换为数字信号来传输比特流,实现两个节点之间的通信。这一层的数据单位为比特。
第二层 数据链路层
单纯的比特流没有任何意义,数据链路层定义了数字信号的分组方式和含义。早期,各个公司都有自己的分组方式,后面形成了统一的标准,即以太网协议。以太网协议规定数字信号多少位为一组,一组数字信号构成一个数据包,这个数据包就叫做帧。
每帧都由报头和数据两部分组成。报头固定为 18 个字节,其中发送者即源地址为 6 个字节,接收者即目标地址也为 6 个字节,数据类型也为 6 个字节。数据最短为 46 个字节,最长为 1500 个字节。所以一帧至少 64 个字节,至多 1518 个字节,超过最大限制就需要分片传输。
报头中包含源地址和目标地址的媒体访问控制地址(Media Access Control Address,简称 MAC 地址,也称作局域网地址、以太网地址或者物理地址),在以太网上通信的每张网卡都拥有一个全球唯一的 MAC 地址。也就是说,我们通过地址解析协议(Address Resolution Protocol,简称 ARP)查询到目标节点的 MAC 地址,同一局域网内的两个节点就可以相互通信了。
以太网采用最原始的方式,即广播方式进行通信。以太网中的所有节点共享一个通信信道,在传输数据帧时,源节点将需要传输的数据帧在这个通信信道中进行广播,以太网上的所有其它节点都能接收到这个数据帧,拆开数据帧后,它们通过比较自己的 MAC 地址和数据帧报头中的目标 MAC 地址来判断这个数据帧是否是传输给自己的,在确认是传输给自己的之后再做进一步处理。
数据链路层的主要作用就是将网络层转发的数据包封装为帧,传输给物理层,以及分析物理层传输过来的数字信号,即将网络层转发的数据可靠地传输到相邻节点的网络层。这一层的数据单位为帧。
第三层 网络层
世界范围内的互联网是由一个个彼此隔离的局域网组成的,一个局域网可以看成一个广播域。广播域中的任意一个节点都可以在数据链路层通过广播的方式到达任意另一个节点,广播域可以被部署在同一局域网,也可以被桥接到其它局域网。
以太网的数据包只能在同一个广播域内传输,跨广播域通信可以通过路由的方式向不同的广播域转发数据包,所以我们需要区分哪些节点属于同一个广播域,哪些节点不属于。MAC 地址虽然能定位到目标节点,但是它无法区分节点是否属于同一广播域。所以网络层引入一套新的地址用来区分不同的广播域,也就是不同的子网,这套新的地址就是网络地址。
规定网络地址的协议称作网际协议(Internet Protocol,简称 IP,也称作互联网协议),被广泛部署的第四版本即 IPv4,规定网络地址由 32 位二进制表示,一个 IP 地址通常写成四段十进制数,范围为 0.0.0.0 到 255.255.255.255。
IP 地址分为网络部分和主机部分,从网络部分和主机部分其实都无法辨别 IP 地址所处的子网。子网掩码是表示子网特征的一个参数,它也是由 32 位二进制表示,它的网络部分全部为 1,主机部分全部为 0,比如本机的 IP 地址为 172.18.10.145,如果已知网络部分为前 24 位,主机部分为后面 8 位,那么本机的子网掩码就是 11111111.11111111.11111111.00000000,转换为十进制就是 255.255.255.0。
那么我们怎么判断 172.18.10.145 和 172.18.10.128 这两个 IP 地址是否处在同一个子网中呢?首先需要将 172.18.10.145 转换为二进制为 10101100.00010010.00001010.10010001 与子网掩码 11111111.11111111.11111111.00000000 对位进行 AND 运算,如果对位都为 1,那么该位就为 1,否则如果对位中有为 0,那么该位结果为 0,所以运算后的结果为 10101100.00010010.00001010.00000000 再转换为十进制为 172.18.10.0,同理再将 172.18.10.128 与其子网掩码 11111111.11111111.11111111.00000000 进行 AND 运算,最后转换为十进制得到 172.18.10.0,两者结果都为 172.18.10.0,因此它们在同一个子网中。
IP 数据包也分为报头和数据两部分。其中报头长度为 20 到 60 个字节,数据最长为 65515 个字节,因为以太网数据包的数据部分最长为 1500 字节,所以 IP 数据包的数据部分如果超过了 1500 个字节就需要分割成几个以太网数据包分片传输了。
那么 ARP 又是怎么查询到 MAC 地址的呢?所有上层的数据包最后都要封装以太网报头,然后通过以太网协议传输,由于通信是基于以太网广播的方式,需要获取到目标节点的 MAC 地址,这时就依靠 ARP 来实现了。由于每个节点的 IP 地址是已知的,首先可以通过 IP 地址和子网掩码区分出目标节点和源节点是否处于同一个子网。数据包会以广播的方式在源节点所处的子网内传输,所有节点接收到数据包后拆开数据包,如果数据包报头中的目标 IP 地址和自己的 IP 地址相同,就响应返回自己的 MAC 地址。如果不处于同一子网,那么通过 ARP 获取的是网关的 MAC 地址。
网络层的主要作用就是提供寻址功能,决定两个节点相互连接的最佳路径。这一层的数据单位为数据包。
第四层 传输层
网络层的 IP 地址帮助我们区分子网,数据链路层的 MAC 地址帮助我们找到节点。那我们如何标识计算机上提供的特定应用进程呢?答案就是通信端口,简称端口。端口的范围为 0 到 65535,在传输控制协议(Transmission Control Protocol,简称 TCP)中端口号 0 是被保留的,不可使用,其中 1 到 1023 为系统占用端口。
传输层的两大协议,一个是传输控制协议(Transmission Control Protocol,简称 TCP),另一个是用户数据报协议(User Datagram Protocol,简称 UDP)。简单的来说,TCP 是一种面向连接的、可靠的传输层协议,而 UDP 是一种不可靠的传输层协议。TCP 的运行可以分为三个阶段,分别是创建连接、传输数据、终止连接。TCP 用三次握手的过程来创建一个连接,在传输数据后通过四次挥手来终止连接。
传输层的主要作用就是提供端到端的数据通信。这一层的数据单位为数据报。
第五层 会话层
会话层的主要作用是为会话实体间创建连接。
第六层 表示层
表示层的主要作用是为不同终端的上层用户提供数据和信息的正确语法表示和转换方法。
第七层 应用层
应用层的主要作用是提供常见的网络应用服务。这一层的协议主要有域名系统(Domain Name System,简称 DNS)、文件传输协议(File Transfer Protocol,简称 FTP)、简单邮件传送协议(Simple Mail Transfer Protocol,简称 SMTP)、超文本传输协议(HyperText Transfer Protocol,简称 HTTP)、安全外壳(Secure Shell,简称 SSH)等。
互联网协议套件
互联网协议套件(Internet Protocol Suite,简称 IPS),因为该协议族的两个核心协议:传输控制协议(Transmission Control Protocol,简称 TCP)和网际协议(Internet Protocol,简称 IP),为该协议族中最早通过的标准,因为此它常被通称为 TCP/IP 协议族,简称 TCP/IP。TCP/IP 常被视为简化的 OSI 模型。
我们输入一个网址 www.baidu.com 到页面返回,到底经历了一个怎样的流程呢?
域名解析(Domain Name System,简称 DNS)过程
查询缓存(查存货)的过程
-
浏览器查询浏览器缓存,如果有域名对应的 IP 地址则返回。
-
系统查询系统缓存,如果有域名对应的 IP 地址则返回。
-
路由器查询路由器缓存,如果有域名对应的 IP 地址则返回。
-
互联网服务提供商(Internet Service Provider,简称 ISP)查询 ISP 缓存,如果有域名对应的 IP 地址则返回。
本地域名服务器开始迭代查询(放大招)的过程
-
本地域名服务器先向一个根域名服务器进行查询,全球有 13 台根域名服务器,分布在美国、英国、瑞典和日本,其中主根服务器在美国,根域名服务器告诉本地域名服务器,下一次应该查询的顶级域名服务器 dns.com 的 IP 地址。
-
本地域名服务器再向这个顶级域名服务器进行查询,顶级域名服务器告诉本地域名服务器,下一次应该查询的权限域名服务器 dns.baidu.com 的 IP 地址。
-
本地域名服务器再向这个权限域名服务器进行查询,权限域名服务器告诉本地域名服务器,查询的 www.baidu.com 对应的 IP 地址。
-
本地域名服务器再把这个 IP 地址告诉客户端,并写入缓存以便备查。
HTTP 请求与响应的过程
在 HTTP 开始工作前,web 浏览器首先要向 web 服务器建立 TCP 连接,只有低层协议建立连接之后才能进行更高层次协议的连接。
-
初始化序列编号(Initial Sequence Number,简称 ISN)。
-
确认编号(Acknowledgement Number,简称 ACK)。
-
同步序列编号(Synchronize Sequence Number,简称 SYN)。
我们假设初始端为客户端 web 浏览器 A,另一端为服务端 web 服务器 B。
三次握手的过程
-
首先,A 的初始化序列号生成器会生成一个 32 位长的初始化序列编号,假设该初始化序列编号为 1,以该初始化序列编号为原点,对自己将要发送的每个数据报进行编号,比如 1、2、3...,并把自己的初始化序列编号告诉 B。
-
B 对 A 发送的每个数据报进行确认,然后发送一个确认编号,如果 A 收到 B 发送的确认编号为 1000,那么意味着数据报编号为 1 到 1000 的共 1000 个数据报已经安全到达 B。
-
同时 B 也有类似的操作,B 的初始化序列号生成器也生成一个 32 位长的初始化序列编号,假设该初始化序列编号为 1001,以该初始化序列编号为原点,对自己将要发送的每个数据报进行编号,比如 1001、1002、1003...,并把自己的初始化序列编号告诉 B。
-
同理,A 对 B 发送的每个数据报进行确认,然后发送一个确认编号 2001 给 B,告诉 B 1001 到 2000 的共 1000 个数据报已经安全到达 A。
数据传输的过程
-
一旦 TCP 建立连接,web 浏览器就会向 web 服务器发送 http 请求命令 GET/a.js HTTP/1.1。
-
浏览器在发送请求命令后,还要以请求头信息的形式向 web 服务器发送一些如 User-Agent、Host 等自己的信息,最后浏览器发送一个空白行来代表请求头信息已经发送完毕。如果是 POST 请求,浏览器会继续提交请求体。
-
服务器接收到请求命令后,会向浏览器回送响应,响应的第一部分是协议的版本号和应答状态码 HTTP/1.1 200 OK。
-
正如浏览器会随同请求发送自己的信息一样,服务器也会随同响应向浏览器发送自己的信息和被请求的文档,最后发送一个空白行代表响应头信息已经发送完毕。
-
服务器以 Content-Type 响应头信息所表述的格式发送给浏览器用户实际所请求的数据。
-
一旦服务器向浏览器发送了请求数据,它就要关闭 TCP 连接。如果在服务器添加 Connection: keep-alive 设置,TCP 连接在响应后仍然保持打开状态。
四次挥手的过程
-
A 发送最后一段初始化序列编号和确认编号给 B 并带上一个 FIN 报文段,此时 A 进入 FIN_WAIT_1 状态,表示 A 已经没有要发送给 B 的数据了。
-
B 收到 A 发送的 FIN 报文段,向 A 发送确认编号,此时 A 进入 FIN_WAIT_2 状态。
-
B 发送最后一段初始化序列号给 A 并带上一个 FIN 报文段,请求关闭连接,此时 B 进入 LAST_ACK 状态。
-
A 收到 B 发送的 FIN 报文段,向 B 发送确认编号,此时 A 进入 TIME_WAIT 状态。B 收到 A 的确认编号后,就此关闭连接。A 等待两个最大报文段生存时间(Maximum Segment Lifetime,简称 MSL)后如果没有收到回复,证明 B 已正常关闭,那么 A 也可以关闭连接了。
HTTP 缓存
通过请求来获取资源往往伴随着巨大的开销,也会降低交互效率,所以将之前获取过的资源缓存起来重复利用是提升性能的一个好方法。如果我们本身是使用浏览器来访问资源,那么我们只需要保证每个响应都提供了正确的 HTTP 标头指令来指示浏览器的缓存策略,因为每个浏览器都自带了 HTTP 缓存功能。如果我们使用了 webview 来获取资源和显示网页内容的话,可能需要客户端的同学提供额外的配置来确保 HTTP 缓存功能得到启用。
私有缓存和共享缓存
私有缓存只能应用于单独用户,可以理解为用户代理也就是客户端的缓存。共享缓存可以应用于多个用户,比如互联网服务提供商可能会架设 web 代理来作为本地网络基础的一部分提供给用户,共享缓存也可以理解为代理服务器缓存。
我们发起请求到载入资源,到底经历了一个怎样的流程呢?
浏览器对于资源的请求有一套成熟的缓存策略,按照时间的发生顺序依次为存储策略、过期策略、协商策略,其中存储策略是在接收响应后应用,过期策略和协商策略则是在发送请求前应用。
我们假设浏览器有一个缓存数据库用于存放缓存信息。在浏览器首次请求数据时,缓存数据库中并没有对应的缓存数据,所以需要对服务器发起请求。服务器返回缓存规则和被请求的数据,浏览器再将这些缓存规则和数据存储到这个缓存数据库中。
HTTP 的缓存规则有很多种,我们可以将它们归为两大类,一个是强制缓存,另一个是对比缓存,也叫做协商缓存。
对于强制缓存来讲,浏览器再次请求数据时,如果缓存未失效,那么直接使用缓存数据。如果缓存失效,浏览器向服务器发送请求,服务器返回缓存规则和数据。
对于对比缓存来讲,浏览器再次请求数据时,浏览器将缓存标识发送给服务器,服务器根据缓存标识进行对比,对比成功后,响应 304 状态码通知浏览器对比成功,可以直接使用缓存数据。如果对比失败,响应 200 状态码通知浏览器对比失败,同时返回新的缓存规则和数据。
从上面的四张流程图可以看出两类缓存的区别。强制缓存下如果缓存有效,不需要再和服务器发生交互。而在对比缓存下是都需要与服务器发生交互。
两类缓存规则是可以并存的,强制缓存的优先级高于对比缓存,也就是说,当执行强制缓存规则时,如果缓存有效就直接使用缓存,不再执行对比缓存规则。
HTTP header 中与缓存相关的 key
key | 描述 | 存储策略 | 过期策略 | 协商策略 |
---|---|---|---|---|
Cache-Control | 指定缓存的机制,覆盖其它设置 | ✔️ | ✔️ | |
Pragma | http 1.0 字段,指定缓存的机制 | ✔️ | ||
Expires | http 1.0 字段,指定缓存过期时间 | ✔️ | ||
Last-Modified | 资源最后一次修改的时间 | ✔️ | ||
ETag | 唯一标识请求资源的字符串 | ✔️ |
缓存协商策略用于重新验证缓存是否有效,相关的 key
key | 描述 |
---|---|
If-Modified-Since | 缓存校验字段,值为资源最后一次修改的时间,即上次收到的 Last-Modified 的值 |
If-Unmodified-Since | 同上,处理方式与之相反 |
If-None-Match | 缓存校验字段,值为唯一标识请求资源的字符串,即上次收到的 ETag 的值 |
If-Match | 同上,处理方式与之相反 |
Cache-Control
Cache-Control 是金字塔顶尖的机制,它不仅能覆盖与之冲突的其它设置,还是一个复合机制,包含存储策略和过期策略。在请求头和响应头中都可以设置。
Cache-Control | 描述 | 存储策略 | 过期策略 | 请求字段 | 响应字段 |
---|---|---|---|---|---|
public | 允许私有缓存和共享缓存,资源将被客户端和代理服务器缓存 | ✔️ | ✔️ | ||
private | 仅允许私有缓存,资源仅被客户端缓存 | ✔️ | ✔️ | ||
no-store | 不缓存,强制缓存和对比缓存都不会触发 | ✔️ | ✔️ | ✔️ | |
no-cache | 相当于 max-age 为 0,must-revalidate,即缓存立即过期,同时强制向服务器发起验证 | ✔️ | ✔️ | ✔️ | ✔️ |
max-age | 缓存资源,在指定时间(单位为秒)后缓存过期 | ✔️ | ✔️ | ✔️ | ✔️ |
s-maxage | 同上,依赖 public 的设置,仅在代理服务器上有效 | ✔️ | ✔️ | ✔️ | |
max-stable | 指定时间(单位为秒)内,即使缓存过期,资源仍然有效 | ✔️ | ✔️ | ||
min-fresh | 缓存的资源至少要保证指定时间的新鲜期 | ✔️ | ✔️ | ||
must-revalidate / proxy-revalidate | 如果缓存过期,强制向服务器发起验证,因为 max-stable 等指令有可能会改变资源的有效时间 | ✔️ | ✔️ | ||
only-if-cached | 仅返回已缓存过的资源,不访问网络,如果没有命中缓存,则返回 504 | ✔️ | |||
no-transform | 强制要求代理服务器不要对资源进行转换,禁止代理服务器对 Content-Encoding、Content-Range、Content-Type 字段进行修改,因此代理服务器的 gzip 压缩将不被允许 | ✔️ | ✔️ |
思考题:设请求资源的时间是 3 月 6 日,响应后随即缓存,且在 3 月 13 日过期,当我们将 max-age、max-stable 和 min-fresh 同时使用时,因为它们的设置彼此之间相互独立应用,且客户端总是采用最保守的缓存策略,如果 max-age 为 20 天,max-stable 为 2 天,min-fresh 为 4 天,那么请问这个缓存的资源将会在什么时候失效呢?
根据 max-age 的设置,覆盖原有的缓存周期,那么缓存的资源将会在 3 月 26 日失效。
根据 max-stable 的设置,缓存过期后 3 天内缓存的资源仍然有效,此时的响应将会返回 110 状态码,那么缓存的资源将会在 3 月 17 日失效。
根据 min-fresh 的设置,缓存的资源至少要保证 2 天的新鲜期,那么缓存的资源将会在 3 月 11 日失效。
根据客户端总是采用最保守的缓存策略,那么这个缓存的资源将会在 3 月 11 日失效。对于该资源的请求将会重新像服务端发起验证。
Pragma
HTTP 1.0 字段,通常设置 Pragma: no-cache,作用和 Cache-Control: no-cache 相同。当一个 no-cache 请求发送给一个不遵循 HTTP/1.1 的服务器时,客户端应该包含 Pragma 字段。
Expires
HTTP 1.0 字段,过期时间,优先级比 Cache-Control 的 max-age 低。如果两者都没有出现在响应头时,并且也没有其它的缓存设置时,浏览器会默认采用一个启发式算法,通常是会取响应头的 Date 的值和 Last-Modified 的值相减后的 10% 作为缓存时间。
const DATA_TIMESTAMP = +new Date('Thu, 06 Apr 2017 01:30:56 GMT');
const LAST_MODIFIED_TIMESTAMP = +new Date('Thu, 01 Dec 2016 06:23:23 GMT');
const CACHE_TIMESTAMP = (DATA_TIMESTAMP - LAST_MODIFIED_TIMESTAMP) / 10;
const EXPIRES_TIMESTAMP = DATA_TIMESTAMP + CACHE_TIMESTAMP;
const EXPIRES_VALUE = new Date(EXPIRES_TIMESTAMP);
console.log('Expires:', EXPIRES_VALUE); // Expires: Tue Apr 18 2017 23:25:41 GMT+0800 (中国标准时间)
复制代码
Last-Modified
这个字段用于标记资源最后一次修改的时间。Last-Modify 是 ETag 的降级方案,优先级比 ETag 低,且只能精确到秒,因此不太适合短时间内频繁改动的资源。不仅如此,由于服务端的静态资源通常经过编译打包,可能出现资源内容没有变化而 Last-Modified 却改变的情况。
If-Modified-Since
缓存校验字段,值为资源最后一次修改的时间,即上次收到的 Last-Modified 的值。优先级比 If-None-Match 低。浏览器在首次请求时,服务器返回被请求资源最后一次修改的时间 Last-Modified。在对比缓存机制下,浏览器再次请求服务器时在请求头中携带 If-Modified-Since 字段,值为上次收到的 Last-Modified 的值。服务器收到请求后发现请求头里面含有 If-Modified-Since,将这个字段的值与资源最后一次修改的时间进行对比,如果资源最后一次修改的时间大于 If-Modified-Since 的值,说明资源又被修改过,则响应 200 状态码和被请求的资源。如果资源最后一次修改的时间小于或等于 If-Modified-Since 的值,说明资源没有新的修改,则响应 304 状态码通知浏览器可以使用缓存的数据。
ETag
唯一标识请求资源的字符串,如果资源已经更新,ETag 可以帮助防止同步更新资源的相互覆盖。
If-None-Match
缓存校验字段,值为唯一标识请求资源的字符串,即上次收到的 ETag 的值。浏览器在首次请求时,服务器返回唯一标识请求资源的字符串 ETag。在对比缓存机制下,浏览器再次请求服务器时在请求头中携带 If-None-Match 字段,值为上次收到的 E-Tag 的值。服务器收到请求后发现请求头里面含有 If-None-Match,将这个字段的值与唯一标识请求资源的字符串进行对比,如果不同,说明资源又被修改过,则响应 200 状态码和被请求的资源。如果相同,说明资源没有新的修改,则响应 304 状态码通知浏览器可以使用缓存的数据。
...未完待续
参考文档 彻底弄懂HTTP缓存机制及原理 浏览器缓存机制剖析