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
方法请求时对应的响应不能携带包体。1xx
、204
、304
状态码对应的响应。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-Control
在 ABNF
中的秒数如下:
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-age
、max-stale
、min-fresh
、no-cache
、no-store
、no-tansform
、only-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-age
、s-maxage
、must-revalidate
、proxy-revalidate
、no-cache
、no-store
、no-transform
、public
、private
,它们的含义如下:
- must-revalidate:告诉客户端一旦缓存过期,必须向服务器验证后才可以使用。
- proxy-revalidate:与
must-revalidate
类似,但它仅对代理服务器的共享缓存有效。 - no-cache:告诉客户端不能直接使用缓存的响应,使用前必须在源服务器验证得到
304
返回码。如果no-cache
后指定头部,则客户端的后续请求及响应中不含有这些头则可直接使用缓存。 - max-age:告诉客户端缓存
Age
超出max-age
秒后则缓存过期。 - s-maxage:与
max-age
相似,但仅针对共享缓存,且优先级高于max-age
和Expires
。 - 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 响应
扫码关注爱因诗贤
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。