HTTP协议结构
网络上已经存在了大量关于HTTP协议的优质文章, 我就不重复. 关于HTTP协议结构的介绍, 可以阅读以下的文章.
关于HTTP的发展历史, 可以参考以下的文章.
HTTP Header
HTTP是基于文本的, 所以很多属性看名字就可以知道含义. 但由于HTTP Header中属性太多, 因此本文显然无法记录全部的属性, 对于大部分属性, 在需要的时候再去查询其含义也不会消耗太多时间. 下面介绍一些最常用的参数.
请求头参数
| 参数 | 含义 |
|---|---|
| Accept | 可接受的响应格式 |
| Accept-Encoding | 可接受的编码格式 |
| Accept-Language | 可接受的语言 |
| Cache-Control | 指示服务器端的Cache保存策略 |
| Connection | 表示连接类型,例如持续连接 |
| Cookie | 客户端传输给服务端的Cookie |
| Host | 请求服务器的域名 |
| Referer | 发送此请求时来自的页面 |
| User-Agent | 使用的客户端名称 |
Accept开头的一组参数都是表示可接受的响应类型, 这些参数可以指定多种类型, 类型之间使用;分割. 此外, 其中还会包含一些类似q=0.9样式的字段, 此字段用于表示优先级, 例如zh-CN,zh;q=0.9,en;q=0.8就表示接受中文的优先级为0.9,而接受英文的优先级为0.8
Referer用于指定请求的来源页面, 例如在知乎页面上点击一个外部链接, 向目标网站发送请求时, Referer字段就是知乎的网址. 使用Referer可以用来防止盗链(即如果Referer不是自己的网站, 就拒绝请求).
Content-Type用于指定传输的内容的格式. HTTP协议既可以用来传输HTML代码, 也可以传输二进制的图片和视频. 文本和二进制文件的解析方式完全不同, 浏览器和服务器通过此属性来标记传输的内容. 常见的取值有
| 取值 | 含义 |
|---|---|
| text/html | HTML文本 |
| application/x-www-form-urlencoded | 键值对格式的表单参数 |
| image/jpeg | JPEG格式的图片 |
| multipart/form-data | 包含多个部分的数据 |
| application/json | JSON格式的文本 |
响应头参数
| 参数 | 含义 |
|---|---|
| Cache-Control | 指示客户端的Cache保存策略 |
| ETag | 指示服务器端资源是否发生变化 |
| Location | 重定向时的重定向目标地址 |
如果某个Cache-Control取值情况是Cache-Control: max-age=600, public, must-revalidate, 则表明Cache的最大保存时间为600秒, 且过期后必须从服务器端重新获取
HTTP Body
HTTP的数据部分必须存放在Body之中. HTTP Header和HTTP Body之间空一行, 因此读取到连续的\r\n\r\n即表示后续内容属于HTTP Body. HTTP协议并没有规定数据必须以某种方式进行编码, 因此通常使用前面介绍的Content-Type属性来决定编码的内容.
传输单个数据
HTTP可以传输文本数据或者二进制数据. 对于文本数据, 文本虽然都是可读的, 但也存在多种组织数据的方式. 例如XML文件和JSON文件都可以表达同样的信息, 但文件的组织方式就完全不同. 在浏览器提交表单数据时, 默认采取x-www-form-urlencode格式, 此时参数以key1=value1&key2=value2的方式编码数据.
如果只传输一个二进制文件(例如单文件上传), 则传输过程与传输文本并没有区别, 直接将二进制数据放入Body之中即可.
传输多个数据
如果既要传输二进制信息, 又要传输一部分文本进行, 那么也可以使用混合传输的方式. 此时需要将Content-Type属性指定为multipart/form-data, 即向接收端表明HTTP Body中存在多个部分的数据, 需要分开处理.
指定编码方式的同时, Content-Type还需要附带一个boundary属性, 此属性为一个任意的随机字符串, 用来分割HTTP Body的不同部分. 只要boundary属性的值不出现在各部分数据之中, boundary可以取任意值.
在这种情况下的HTTP协议具有如下的样式
1 | POST http://www.example.com HTTP/1.1 |
可以看到, HTTP Body被boundary指定的字符串分割为多个部分, 而每个部分中, 又可以分割为Header和Body.
HTTP状态码
HTTP状态码由三个十进制数字组成, 可以分为如下的5种类别
| Code | Message |
|---|---|
| 1xx | 信息,服务器收到请求,需要请求者继续执行操作 |
| 2xx | 成功,操作被成功接收并处理 |
| 3xx | 重定向,需要进一步的操作以完成请求 |
| 4xx | 客户端错误,请求包含语法错误或无法完成请求 |
| 5xx | 服务器错误,服务器在处理请求的过程中发生了错误 |
参考文献和扩展阅读
以下的几篇文章中, 第一篇文件介绍了application/x-www-form-urlencoded, multipart/form-data, application/json 三种取值的具体含义. 第二篇文章对这三种属性与POST, GET方法的关系进行了讨论, 并且补充了更多关于multipart/form-data的特性.
HTTP方法
方法的区别
| 方法 | 主要特点 | 方法 | 主要特点 |
|---|---|---|---|
| GET | 只获得服务器端的数据, 不对服务器进行修改 | DELETE | 删除资源 |
| POST | 创建资源, 对应服务器上的一个动作, 多次操作会可以创建多个结果 | PUT | 替换资源, 对应服务器上的一个资源, 多次操作结果不变(幂等) |
| PATCH | 局部资源更新, 对PUT方法的补充 | HEAD | 获得资源的头部信息 |
结合前面的HTTP Header和HTTP Body可知, 确定请求方法的是HTTP头部的动词, 与是否传递数据无关, 因此使用GET方法也能够在HTTP Body中传递数据. 因此, 本质来说, 这些方法都是一样的, 更多的是语义上的区别.
使用curl指令发送请求
curl指令是Linux中经常使用的一个文件传输指令, 可以用来简单的模拟GET, POST等请求. 对于大部分Linux系统, 都内置了此指令. 在Windows系统中, 如果安装了git bash, 则git bash也内置了此指令.
curl指令具有如下的一些用法
| 用法示例 | 作用 |
|---|---|
| curl www.baidu.com | 直接发送请求并输出结果 |
| curl -i www.baidu.com | 直接发送请求并且只返回头部的内容 |
| curl URL -H “Content-Type:application/json” | 设置请求头后发送请求 |
| curl URL -d “param1=value1¶m2=value2” | 发送POST请求 |
| curl URL -F “file=XXX” -F “name=YYY” | 上传文件 |
注意: 如果URL或者参数中包含特殊字符, 则需要使用引号将内容包裹起来,否则shell会错误的解析指令的内容.
Chrome的postman插件也可以完成curl的功能, 如果能够安装此插件, 则可以完全图形化地完成上述的操作.
其他内容
查看原始的HTTP数据
虽然使用浏览器的检查功能, 也可以查看网络请求的内容, 但其中的分隔符等细节还是被隐藏了, 所以如果想看到二进制级别的HTTP报文, 最直接的方法还是直接抓包.
抓包的工具很多, 例如Ethereal或者Wireshark. 如何使用这些工具偏离本文的主题太远了, 就不详细介绍了. 但需要说明, 新版本的Wireshark支持抓取本地回环数据, 所以如果想抓取本地运行的多个服务之间的HTTP数据包, 那么还是建议安装Wireshark. 最终, 我们可以得到抓包的结果, 以下是一个HTTP响应的抓包结果:
1 | 0000 9a aa b0 ee 57 25 00 1a 1e 02 0a 00 08 00 45 00 ....W%.. ......E. |
除去MAC协议,IP协议和TCP协议的头部信息, 可以看到第0030行的第7个字节开始, 对应的文字为HTTP/1.1 200 OK. 这正是HTTP响应的第一行信息. 依次往后看, 还可以发现很多前面介绍过的头部属性, 并且可以清晰的看到, 每个属性结束后都有两个字节0d 0a, 这正是\r\n的二进制编码.
最后, 在第00e0行的第4到第7个字节是0d 0a 0d 0a, 也就是\r\n\r\n, 即Header与Body的分隔符. 因此最后的unknown user()是位于HTTP Body之中的内容.
HTTP缓存控制
浏览器缓存涉及到HTTP协议中的三个属性
- Last-modified: Web服务器自动添加, 浏览器在此请求时携带此数据, 如果无变化, 服务器返回403
- Etag: 类似文件的Hash值, 文件多次修改后内容不变时, Etag不变
- Expires: 服务器或程序直接控制过期时间, 控制能力最强
更多内容可以参考下面的文章
-最常被遗忘的Web性能优化:浏览器缓存
HTTP/2协议
HTTP/1.1的问题
队头阻塞(Head-of-Line Blocking):在管道化中,如果第一个请求响应慢,会阻塞后面所有请求的响应。即使不用管道化,浏览器也限制同一域名下并行连接数(通常 6~8 个)。冗余的头部信息:每个请求都携带大量重复头部,如 Cookie、User-Agent,浪费带宽。只能由客户端发起请求:服务器无法主动推送资源,需客户端解析 HTML 后再请求 CSS/JS,产生额外 RTT。基于文本协议:解析效率低。
HTTP/2协议的核心改进
二进制分帧层: 将传输数据分割为更小的帧(Frame),如 HEADERS 帧、DATA 帧。所有通信在一个 TCP 连接上完成,可同时发送多个请求和响应,互不干扰。多路复用: 在同一个 TCP 连接中,多个流(Stream)交错传输,每个流由唯一的标识符区分。彻底解决了 HTTP/1.1 的队头阻塞问题:一个请求阻塞不会影响其他请求的接收和响应。头部压缩: 客户端和服务器共同维护一份静态和动态字典,对常见头部字段进行索引,只传输索引号。
关于头部压缩, 规范预先定义了一些常见的键值对, 可以直接应用这些项目. 此外在传输过程中, 还可以按照顺序创建新的条目, 并且在项目数量达到最大值时, 按照FIFO规则淘汰旧项目.
由于HTTP/2协议使用单个TCP链接传输数据, 因此数据严格有序, 仅需要按照固定的算法添加和删除条目, 即可保证服务端和客户端的条目一致
服务器推送: 服务器可以预测客户端将需要的资源(如 HTML 引用的 CSS/JS),主动推送给客户端,减少请求往返。流优先级与依赖: 客户端可以设置流的优先级,告知服务器哪些资源更重要,优化资源加载顺序。
服务器推送由于难以确认客户端需要哪些资源, 实际上并未广泛启用.
HTTP/3协议
虽然 HTTP/2 解决了 HTTP 层面的队头阻塞,但底层 TCP 仍有队头阻塞问题。因为 TCP 是可靠传输,数据包必须按序到达,丢失一个包会阻塞后续所有已到达包的处理(等待重传)。这在丢包率高的网络(如移动网络)中影响尤其严重。
QUIC(Quick UDP Internet Connections)协议最初由 Google 设计,现已成为 IETF 标准。它并非 HTTP 专属,但作为 HTTP/3 的底层传输协议而广为人知。基于 UDP:避免 TCP 在操作系统内核中的僵化,可在用户空间快速迭代。多路复用:类似 HTTP/2 的流,但每个流独立可靠传输,单个流丢包不影响其他流(解决了 TCP 队头阻塞)。内置 TLS 1.3:加密成为默认选项,握手更快,且所有数据包均加密,减少明文暴露。0-RTT 连接建立:如果之前连接过,客户端可以携带加密凭据直接发送数据,无需握手延迟。连接迁移:使用连接 ID 标识连接,而非 IP+端口,切换网络(如 WiFi 切 4G)时连接不中断。更灵活的拥塞控制:在用户空间实现,可快速迭代新算法。
HTTP/3 将 HTTP/2 的二进制分帧层移植到 QUIC 之上
HTTP/3协议具有如下特点: 彻底解决队头阻塞:基于流级别的可靠传输,一个流丢包只影响该流,其他流继续处理。更快的连接建立:结合 QUIC 的 0-RTT,尤其适合移动网络或频繁切换网络的场景。更好的拥塞控制:QUIC 在用户空间实现,可更精细控制,且能避免 TCP 的“愚蠢窗口减少”问题。增强的隐私与安全:所有 QUIC 数据包均加密,包括部分控制信息,防止中间人窥探。
虽然现在服务器和浏览器都支持QUIC协议和HTTP/3了, 但由于防火墙等各类网络原因, 很多时候只在移动网络中能正常启用QUIC协议.
协议栈对比总结
| 特性 | HTTP/1.1 | HTTP/2 | HTTP/3 |
|---|---|---|---|
| 传输层 | TCP | TCP | UDP + QUIC |
| 多路复用 | 无(依赖多个连接) | 有(二进制分帧) | 有(QUIC 流) |
| 队头阻塞 | 应用层与传输层均有 | 仅传输层(TCP)有 | 无(QUIC 流独立) |
| 头部压缩 | 无 | HPACK | QPACK(适配 QUIC 乱序) |
| 加密 | 可选(HTTPS) | 事实要求 TLS | 内置(TLS 1.3) |
| 连接建立 RTT | 3-4 RTT(含 TLS) | 2-3 RTT | 0-1 RTT |
| 连接迁移 | 不支持 | 不支持 | 支持(连接 ID) |
对于头部压缩, 由于HTTP/3可以同时发送多个流(HTTP/2全部复用1个TCP链接), 但共享全局的头部字典, 因此可能存在乱序问题(例如使用了某个索引的数据包达到客户端, 但是声明该索引的包还未到达客户端), 因此QUIC协议还需要对这些可能的乱序问题进行处理, 适当的缓存数据并进行排序.
最后更新: 2026年03月03日 21:01
版权声明:本文为原创文章,转载请注明出处
原始链接: https://lizec.top/2019/08/08/HTTP%E5%8D%8F%E8%AE%AE%E8%A7%A3%E6%9E%90/