数据类型与编码
MIME type
全称“多用途互联网邮件扩展”(Multipurpose Internet Mail Extensions), 用于标记 body 的数据类型, 形式是 type/subtype 的字符串, 常用的有:
- text:
text/htmltext/csstext/plain - image:
image/gifimage/jpegimage/png - audio/video:
audio/mpegvideo/mp4 - application:
application/jsonapplication/javascriptapplication/pdfapplication/octet-stream
数据编码/压缩
- gzip: GNU zip, 对于文本数据通常超过 60%压缩率
- deflate: zlib
- br: 专为 HTTP 优化的新压缩算法 Brotli, 通常比 gzip 还好
数据类型相关 Headers
-
请求头: 客户端声明希望/能够接收的数据类型
- Accept: text/html, application/json, image/png, image/webp
- Accept-Encoding: gzip, deflate, br
-
响应头: 服务端告知实际发送的数据类型
- Content-Type: text/html
- Content-Encoding: gzip
国际化 i18n
主要分为语言和字符集:
-
请求头
- Accept-Language: zh-CN, zh, en
- Accept-Charset: utf-8, gbk
-
响应头
- Content-Language: zh-CN
- Content-Type: text/html; charset=utf-8
- 字符集在响应头中没有单独的字段, 而是作为 Content-Type 的后半部分
Accept-Charset和Content-Language通常不会发送.
优先级
可以在 Accept Accept-Encoding Accept-Language 等请求头字段中的内容中添加 q 参数来设定优先级, 如 Accept: text/html, application/xml;q=0.9 .
优先级默认以及最大为 1, 最小为 0.01.
注: 在此处 ; 的断句小于 , , 表示仅作用于分号之前的一个内容.
响应头中的 Vary
表示服务器在内容协商时参考的请求头字段, 如:
Vary: Accept-Encoding,User-Agent,Accept
请求头变化时, Vary 也可能变化, 主要用于在传输链路中间的代理服务器实现缓存服务, 后文 缓存代理 部分详述.
大文件传输
分块传输/Transfer-Encoding
-
响应头:
Transfer-Encoding: chunked- 与
Content-Length字段互斥, 要么长度已知, 要么是 chunked(长度未知). Transfer-Encoding字段值可以是gzipdeflate等, 表示压缩编码; 与Content-Encoding相比,Transfer-Encoding在传输后将被自动解码.
- 与
-
每个分块内容:
- length + CRLF
- chunked data + CRLF
-
分块末尾
- 0 + CRLF (length 为 0)
- CRLF (空行结尾)
- 末尾允许有拖尾数据
Trailer
范围请求/Range
获取文件的一部分, 如视频进度拖拽、多线程并发下载、断点续传.
服务器在响应头中添加 Accept-Ranges: bytes 来告知客户端支持范围请求.
客户端在请求头中添加 Range: bytes=x-y 来进行范围请求.
- x、y 表示从 0 开始的偏移量
- x、y 可省略其中之一, 但
-不能省略, 如:bytes=10-bytes=-20
服务器接收到 Range 字段后,
- 检查范围是否合法, 不合法返回 416;
- 若范围合法, 根据 Range 头偏移量读取文件片段, 确定状态码 206(Partial Content);
- 服务器添加响应头字段
Content-Range: bytes x-y/length, length 表示总长度; - 通过 TCP 发送数据
Range 是针对原文件的范围, 而不是压缩后的文件范围(除非原文件就是 gzip 的).
条件范围请求 If-Range 在后文详述.
多段数据
在 Range 请求头中添加逗号分隔的多段范围, 如: Range: bytes=0-9, 20-29.
响应头中多段数据得到的数据 MIME 类型为 multipart/byteranges , 同时需要给出段之间的 boundary=xxx , 如: Content-Type: multipart/byterange; boundary=00000000001.
具体的数据如下
- 以双“-”开头的分隔符:
--{boundary}+ CRLF - 数据类型:
Content-Type: type/subtype+ CRLF - Range:
Content-Range: types x-y/length+ CRLF - 空行 CRLF
- 分段数据 multipart data + CRLF
- (下一段数据)分隔符:
--{boundary}+ CRLF - ...其他数据
- 分段结束:
--{boundary}--+ CRLF
连接管理/Connection
短连接和长连接
HTTP 0.9/1.0 使用短连接, 收到响应后立刻关闭连接, 效率很低:
- TCP 三次握手建立连接 → 请求 → 响应 → TCP 四次握手断开连接
HTTP/1.1 默认使用长连接, 在一次连接中收发多个请求-响应.
- 请求头和响应头加
Connection: keep-alive表示支持长连接 - 请求头和响应头加
Connection: close告知对方本次通信后关闭连接 - 请求头和响应头加
Keep-Alive: timeout=value限定超时时间(约束力不强,可能不遵守) - Nginx 可以设置
keepalive_timeout和keepalive_request规定一次连接的超时时间和最大请求数 - Connection 的另一个值
Upgrade配合状态码 101, 表示协议升级, 如 HTTP 升级为 WebSocket - HTTP 还有第三种连接方式 “pipeline”, 但主流浏览器都未实现, 事实上“废弃”了.
队首阻塞问题
HTTP 规定的“请求-响应”模型是先进先出的串行队列, 即 同域名 下收到前一次的响应后才能发起新请求, 一次收发过慢时会导致后续所有请求被阻塞.
- 利用 HTTP 长连接特性对服务器发起大量请求, 导致服务器耗尽资源, 就是 DDoS .
由于 HTTP/1.1 的“请求-应答”模型不能变, 只能通过数量来暂时“解决”质量问题:
- 并发连接数: 浏览器支持 6~8 个同域并发长连接
- 域名分片: 使用多个域名指向同一台服务器
重定向和跳转/Location
当状态码 301/302 时为重定向, 响应头 Location 标记重定向 URL.
Location 支持绝对和相对 URI, 如 Location: /index.html .
循环跳转: 几个 URI 连续重定向导致无限循环, 浏览器可以检测循环跳转并提示失败.
Refresh 延时重定向, 如 Refresh: 5; url=xxx 表示 5 秒后跳转.
Referer 和 Referrer-Policy 表示浏览器跳转来源, 用于统计分析或防盗链.
3xx 状态码重定向由浏览器执行, 对于服务器来说是“外部重定向”, 服务器的“内部重定向”是在服务器中直接跳转 URI.
重定向相关状态码
- 301: 永久重定向, 会更新浏览器历史记录和书签;
- 302: 临时重定向, 仅本次访问有效;
-
其他状态码, 浏览器和服务器不一定支持:
- 303 See Other: 类似 302, 但要改为
GET方法避免重复操作; - 307 Temporary Redirect: 类似 302, 要求重定向后的方法和实体不变;
- 308 Permanent Redirect: 301 + 307, 永久重定向且方法和实体不变.
- 300 Multiple Choices: 返回一个有多个链接选项的页面, 用户自行选择跳转
- 303 See Other: 类似 302, 但要改为
Cookie
服务器在响应头添加 Set-Cookie: key=value , 给予客户端身份标识, 响应头可具有多个 Set-Cookie .
客户端保存 Cookie 并在之后的请求头中添加 Cookie: key1=value1; key2=value2 , 请求头的 Cookie 将多个 Cookie 合并为 ; 分隔的形式.
Cookie 最大 4K.
Cookie 不属于 HTTP 标准, 所以语法上与别的属性不一致(分隔符是分号而非逗号).
Cookie 属性
一个真实 Cookie: Set-Cookie: favorite=humburger; Max-Age=10; Expires=Fri, 07-Jun-19 08:19:00 GMT; Domain=www.xxx.com; Path=/; HttpOnly; SameSite=Strict
有效期:
Expires过期的时间点Max-Age有效时间(秒数)- 浏览器优先采用
Max-Age, 如果不指定有效期, 则为会话 Cookie 关闭失效.
作用域:
Domain域名Path路径, 使用/表示该域名下任意路径- 浏览器会进行对比, 如果请求 URI 不相符则不会发送 Cookie.
安全性:
HttpOnly禁止非 HTTP 协议方式访问,禁用document.cookie相关 API.-
SameSite防范“跨站请求伪造”(XSRF)攻击SameSite=Strict限定 Cookie 不能随着跳转链接跨站发送SameSite=Lax允许 GET/HEAD 等安全方法, 禁止 POST 跨站发送
Secure该 Cookie 只能用 HTTPS 协议加密传输, 但 Cookie 本身在浏览器中还是明文存在
防范 XSS(跨站脚本攻击):
HttpOnly禁止 JS 脚本访问 Cookie;Secure只在 HTTPS 时加密传输 Cookie.
Cookie 应用
- 身份识别: 登录信息、会话事务等
- 广告追踪: 用户被广告商加上 Cookie, 实现精准推送
Cache-Control
服务器端的缓存控制
服务器在响应头中添加 Cache-Control 控制浏览器缓存资源, 如 Cache-Control: max-age=30 .
max-age有效期(秒), 从响应报文的创建时刻开始计算.no_store不允许缓存-
no_cache(语义相反)可以缓存, 需在服务器验证缓存是否有新版本- 相当于
max-age=0, must_revalidate
- 相当于
must-revalidate缓存未过期可使用, 过期需验证
浏览器端的缓存控制
不使用缓存数据:
- 刷新按钮: 浏览器在请求头中添加
Cache-Control: max-age=0, 表示越过缓存获取新数据; - 强制刷新(Ctrl+F5 或 Command+R): 请求头添加
Cache-Control: no-cache, 通常与刷新效果相同, 取决于服务器处理.
使用缓存数据的情况: 请求头中无 Cache-Control 属性
- 前进/后退 按钮
- 重定向跳转
条件请求
先验证缓存是否有效, 若无效再请求新数据 需要两次请求, 成本过高. 所以 HTTP 定义了一系列 If 开头的条件请求字段来合并数据验证和请求.
在首次请求中, 服务器在响应头中添加 Last-Modified 和 ETag 属性
通过缓存的更新时间:
- 浏览器在请求头添加
If-Modified-Since属性 - 若缓存无更新, 服务器在响应头添加
Last-Modified属性并返回"304 Not Modified", 浏览器更新缓存有效期
通过 ETag(资源标识):
- 浏览器在请求头添加
If-None-Match: "xxxx" - 若本资源无更新则返回"304 Not Modified"并更新有效期
- ETag 有"强" "弱"之分, 以 "W/"开头的为弱 ETag, 只要求资源在语义上无变化, 但内部可能发生变化; 强 ETag 要求资源在字节级别完全相符.
Proxy
Via 在请求和响应头可以出现, 没经过一个代理就追加一个代理主机名/域名, 如 Via: proxy1, proxy2 .
- 有的响应报文会使用
X-Via, 与Via含义相同
两个常用的事实上的标准(非 HTTP 标准):
-
X-Forwarded-For与Via类似, 但追加的是请求方的 IP 地址.X-Forwarded-Host记录主机名X-Forwarded-Proto记录协议名
X-Real-IP记录客户端 IP 地址, 无中间代理信息.
抓包含代理的通信过程
- 客户端与代理的 80 端口通过三次握手建立 TCP 连接;
- 客户端向代理发送 HTTP 请求, 代理接收并返回 ACK 确认;
- 代理与源服务器通过三次握手建立 TCP 连接;
- 代理向源服务器发送 HTTP 请求, 源服务器接收并返回 ACK 确认;
- 源服务器向代理发送 HTTP 响应, 代理接收并返回 ACK 确认;
- 代理与源服务器通过四次挥手断开 TCP 连接;
- 代理将响应数据发给客户端, 客户端接收并返回 ACK 确认.
代理协议
由于 X-Forwarded-For 要解析和修改 HTTP Headers, 会影响性能, 在加密通信中甚至无法实现, 所以出现了一个事实标准 代理协议/The PROXY protocol .
代理协议有 v1 和 v2 两个版本:
-
v1: 明文, 在 HTTP 报文前增加一行 ASCII 码文本:
PROXY TCP4 1.1.1.1 2.2.2.2 55555 80\r\n GET / HTTP/1.1\r\n ...- 必须
PROXY开头, 放在 HTTP 报文首行之前 TCP4表示客户端的 IP 地址类型- 之后是请求方地址, 应答方地址, 请求方端口, 应答方端口号
- 最后与 HTTP 报文一样以回车换行结束
- 必须
- v2: 非明文二进制格式, 相对复杂可自行查阅.
Cache Proxy
处于客户端和源服务器之间, 作为数据的中转站.
面向源服务器时是客户端, 面相客户端时是服务器.
服务端的缓存控制
在Cache-Control 响应头中可以对客户端和代理的缓存策略进行配置.
首先是四个基本属性 max-age no_store no_cache must-revalidate 可以通过添加 private 或 public 约束客户端和代理的缓存策略:
private表示客户端私有缓存, 如用户信息 Cookie.public表示客户端与代理都可以进行缓存
其次, Cache-Control 还可以添加 must-revalidate 或 proxy-revalidate 属性, 用于说明在缓存失效时必须回源服务器验证还是只验证代理即可.
s-maxage 用于单独标识代理端的缓存生效时间, 与客户端的 max-age 类似.
no-transform 代理专用属性, 表示禁止对数据进行优化处理(一些代理可能会对某些数据进行一些优化处理).
注意: 服务器设置完 Cache-Control 后要添加 Last-modified 或 Etag 响应头才能使用条件请求.
客户端的缓存控制
在 Cache-Control 部分讲述的的浏览器缓存控制对代理同样有效, 即通过 max-age no_store no_cache 属性作用于源服务器和代理来进行缓存控制.
还有两个属性用于声明客户端对缓存有效期的偏移量限定:
max-stale可接受缓存过期 x 秒min-fresh缓存必须在当前以及 x 秒后未过期
only-if-cached 属性表示客户端只接受代理缓存的数据, 如果代理没有缓存或缓存过期, 就返回 504.
缓存数据匹配
在 i18n 部分提到过 Vary 字段, 表示服务器在内容协商时参考的请求头字段, 可以作为报文的一个版本标记. 如: Vary: Accept-Encoding,User-Agent,Accept , 缓存代理会存储这些版本. 当再次收到相同请求时, 代理读取缓存的 Vary , 匹配时缓存数据.
缓存清理
常用的做法是使用自定义请求方发 PURGE 来告诉代理服务器删除本 URI 对应的缓存数据.
需要进行缓存清理的场景: 缓存数据过期; 源服务器存在新数据; 一些无用或有害数据.