HTTP 协议(包体的传输方式&缓存的工作原理)

这篇文章主要了解一下 HTTP 协议中定长包体传输的格式和不定长包体传输的格式,然后简单介绍一下 HTTP 协议中缓存的工作原理和应用场景。

1.HTTP 包体

1.1 HTTP 包体格式

请求或者响应都可以携带包体,基于 ABNF 描述的格式如下:

#message-body = *OCTET:二进制字节流
HTTP-message = start-line *(header-field CRLF) CRLF [message-body]
1.2 不能携带包体的请求或响应
  • HEAD 方法请求时对应的响应不能携带包体。
  • 1xx204304 状态码对应的响应。
  • CONNECT 方法对应的 2xx 响应。
1.3 发送定长包体格式

在发送 HTTP 消息时已经能够确定包体的全部长度,格式如下:

#使用 Content-Length 头部明确指明包体长度
Content-Length = 1*DIGIT
Tips:1*DIGIT 表示用 1 个 十进制(不是十六进制)数表示包体中的字节个数,必须与实际传输的包体长度一致,它的优点是接收端处理简单。
1.4 发送不定长包体格式

发送 HTTP 消息时不能确定包体的全部长度,需要使用 Transfer-Encoding 头部指明使用 Chunk 传输方式,含有 Transfer-Encoding 头部后 Content-Length 头部会被忽略。不定长包体优点如下:

  • 基于长连接持续推送动态内容。
  • 压缩体积较大的包体时,不必完全压缩完(计算出头部)再发送,可以边发送边压缩。
  • 传递必须在包体传输完才能计算出的 Trailer 头部。
transfer-coding = "chunked" / "compress" / "deflate" / "gzip" / transfer-extension

Chunked transfer encoding 分块传输编码:Transfer-Encoding:chunked

chunked-body = *chunk
                last-chunk
                trailer-part
                CRLF
                
chunk = chunk-size[chunk-ext] CRLF chunk-data CRLF

#chunk-size-1*HEXDIG:注意这里是十六进制

chunk-data-1*OCTET

last-chunk = 1*("0")[chunk-ext] CRLF

trailer-part = *(header-field CRLF)

2.常用 HTTP 缓存应用场景

如下图所示,以百度首页为例,展示了 HTTP 缓存场景:

Tips:disk cache 表示磁盘缓存,下次访问的时候不需要下载,可以直接去磁盘获取,memory cache 表示缓存存在内存中,当浏览器退出进程时,内存中的数据会被清空。

3.HTTP 缓存实现示意图

Tips:以 HTTP 请求中部分信息(如 schema、path、host)构成的字典,HTTP 响应消息构成的 LRU 链表。

4.判断缓存是否过期

4.1 freshness_lifetime

判断缓存是够过期可以使用如下格式的公式计算:

#freshness_lifetime:按优先级,取以下响应头部的值

#s-maxage > max-age > Expires > 预估过期时间

# Cache-Control:s-maxage=3600
# Cache-control:max-age=86400
# Expires:Fri,03 May 2019 03:15:20 GMT
# Expires-HTTP-date,指明缓存的绝对过期时间
response_is_fresh = (freshness_lifetime > current_age)

freshness_lifetime 如下图所示:

下面给出各种缓存时间请求统计占比情况:

Tips:缓存的预估时间计算参照 RFC7234文档中 (DownloadTime-LastModified)*10%
4.2 current_age
  • Age 头部

Age 头部表示来自原服务器发出响应(或者验证过期缓存),到使用缓存的响应发出时经过的秒数,对于代理服务器管理的共享缓存,客户端可以根据 Age 头部判断缓存时间,格式如下:

Age = delta-seconds
  • current_age 计算公式如下:
:current_age = corrected_initial_age + resident_time
4.3 模拟浏览器缓存工作原理

步骤1:从浏览器复制一个缓存相关的请求命令:
如下图所示,可以在百度首页显示有缓存的资源右键复制出相关的 HTTP 请求的 curl 命令:

命令如下:

curl 'https://dss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/js/min_notice-816c20c940.js' -H 'Referer: https://www.baidu.com/'  -H 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36' --compressed -I 
Tips:命令最后增加一个 -I 表示返回输出头部内容。

请求结果如下图:

步骤2:使用 If-None-Match 条件判断请求内容是否过期:

curl 'https://dss0.bdstatic.com/5aV1bjqh_Q23odCf/static/superman/js/min_notice-816c20c940.js' -H 'Referer: https://www.baidu.com/'  -H 'User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/86.0.4240.198 Safari/537.36' --compressed  -H 'If-None-Match' -I

如下图所示:

Tips:HTTP 请求时带上 If-None-Match 可以判断 Etag 指纹对应缓存内的容是否过期,若返回 304 表示缓存可以继续使用。

5.私有缓存和共享缓存

  • 私有缓存:仅供一个用户使用的缓存,通常只存在于浏览器这样的客户端。
  • 共享缓存:可以供多个用户的缓存,存在于网络中负责转发消息的代理服务器(对热点资源常使用共享缓存,以减轻原服务器的压力,并提升网络效率)
  • Authentication 响应不可被代理服务器缓存

6. Cache-Control 头部

Cache-ControlABNF 中的秒数如下:

Cache-Control = 1#cache-directive 

cache-directive = token ["=" (token / quoted-string)]

delta-seconds = 1*DIGIT
Tips:RFC规范中的要求是至少能支持到 2147483648(2^31)
6.1 请求中头部的 Cache-Control

请求中 Cache-Control 的取值有 max-agemax-stalemin-freshno-cacheno-storeno-tansformonly-if-cached,它们的含义如下:

  • max-age:告诉服务器,客户端不会接受 Age 超出 max-age 秒的缓存。
  • max-stale:告诉服务器,即使缓存不再新鲜,但陈旧描述没有超出 max-stale 时,客户端仍打算使用,若 max-stale 后没有值,则表示无论过期多久客户端都可使用。
  • min-fresh:告诉服务器,Age至少经过 min-fresh 秒后缓存才可以使用。
  • no-cache:告诉服务器,不能直接使用已有缓存作为响应返回,除非带着缓存条件到上游服务端得到 304 验证返回码才可使用现有缓存。
  • no-store:告诉个代理服务器不要对请求的响应缓存(实际有不少不遵循该规定的代理服务器)。
  • no-tansform:告诉代理服务器不要修改消息包体的内容。
  • noly-if-cached:告诉服务器仅能返回缓存的响应,否则若没有缓存则返回 504 错误码。
6.2 响应中头部的 Cache-Control

响应中 Cache-Control 的取值有 max-ages-maxagemust-revalidateproxy-revalidateno-cacheno-storeno-transformpublicprivate,它们的含义如下:

  • must-revalidate:告诉客户端一旦缓存过期,必须向服务器验证后才可以使用。
  • proxy-revalidate:与 must-revalidate 类似,但它仅对代理服务器的共享缓存有效。
  • no-cache:告诉客户端不能直接使用缓存的响应,使用前必须在源服务器验证得到 304 返回码。如果 no-cache 后指定头部,则客户端的后续请求及响应中不含有这些头则可直接使用缓存。
  • max-age:告诉客户端缓存 Age 超出 max-age 秒后则缓存过期。
  • s-maxage:与 max-age 相似,但仅针对共享缓存,且优先级高于 max-ageExpires
  • public:表示无论私有缓存或者共享缓存,皆可将该响应缓存。
  • private:表示该响应不能被代理服务器作为共享缓存使用,若 private 后指定头部,则在告诉代理服务器不能缓存指定的头部,但可缓存其他部分。
  • no-store:告诉所有下游节点不能对响应进行缓存。
  • no-transform:告诉代理服务器不能修改消息包体的内容。

7.什么样的 HTTP 响应会被缓存

  • 请求方法可以被缓存理解(不仅仅 GET 方法)
  • 响应码可以被缓存理解(404、206也可以被缓存)
  • 响应与请求的头部没有指明 no-store
  • 响应中至少应含有以下头部中的 1 个或者多个:
Expires、max-age、s-maxage、public
#当响应中没有明确指示过期时间的头部时,如果响应码非常明确,也可以缓存
  • 如果缓存在代理服务器上
不含有 private
不含有 Authorization

8.使用缓存作为当前请求响应的条件

  • URI 作为主要的缓存关键字,当一个 URI 同时对应多份缓存时,选择日期最近的缓存。例如 Nginx 中默认的缓存关键字:proxy_cache_key
$scheme$proxy_host$request_uri;
  • 缓存中的响应允许当前请求的方法使用缓存
  • 缓存中的响应 Vary 头部指定的头部必须与请求中的头部相匹配:
Vary = "*" / 1#field-name

vary:*意味着一定匹配失败
  • 当前请求以及缓存中的响应都不包含 no-cache 头部(Pragma:no-cache或者Cache-Control:no-cache)
  • 缓存中的响应必须是以下三者之一:
#新鲜的
#缓存中的响应头部明确告知可以使用过期的响应(如 Cache-Control:max-stale=60)
#使用条件请求去服务器端验证请求是否过期,得到 304 响应

扫码关注爱因诗贤
在这里插入图片描述


爱因诗贤
54 声望9 粉丝