从零开始的前端异世界生活--005--“HTTP详细解析中”

基础篇

HTTP的前世今生

  • HTTP 协议始于三十年前蒂姆·伯纳斯 - 李的一篇论文
  • HTTP/0.9 是个简单的文本协议,只能获取文本资源;
  • HTTP/1.0 确立了大部分现在使用的技术,但它不是正式标准;
  • HTTP/1.1 是目前互联网上使用最广泛的协议,功能也非常完善;
  • HTTP/2 基于 Google 的 SPDY 协议,注重性能改善,但还未普及;
  • HTTP/3 基于 Google 的 QUIC 协议,是将来的发展方向

HTTP是什么

  • HTTP是一个用在计算机世界里的协议,它确认了一种计算机之间交流通信的规范,以及相关的各种控制和错误处理方式
  • HTTP专门用来在两点之间传输数据,不能用于广播,寻址,路由。
  • HTTP传输的是文字,图片,音频,视频等超文本数据。
  • HTTP是构建互联网的重要基础技术,它没有实体,依赖许多其他的技术来实现,但是同时许多技术也都依赖于它。

http世界全览

  • 互联网上绝大部分资源都使用HTTP协议传输;
  • 浏览器是HTTP协议中的请求方,即User Agent
  • 服务器是HTTP协议中的应答方,常用的有ApacheNginx
  • CDN位于浏览器和服务器之间,主要起到缓存加速的作用
  • 爬虫是另一类的User Agent,是自动访问网络资源的程序;
  • TCP/IP是世界上最常用的协议,HTTP通常运行在TCP/IP提供的可靠传输基础上
  • DNS域名是IP地址的等价替代,需要用域名解析实现到IP地址的映射;
  • URI是用来标记互联网上资源的一个名字,由协议名+主机名+路径构成,俗称URL
  • HTTPS相当于HTTP+SSL/TLS+TCP/IP,为HTTP套一个安全的外壳;
  • 代理是HTTP传输过程中的中转站,可以实现缓存加速,负载均衡等功能

HTTP分层

  • 第一层:物理层,TCP/IP里无对应;
  • 第二层:数据链路层,对应TCP/IP的连接层;
  • 第三层:网络层,对应TCP/IP的网际层;
  • 第四层:传输层,对应TCP/IP的传输层;
  • 第五,六,七层:统一对应到TCP/IP的应用层;

OSI七层模型(理论参考模型)与TCP/IP四层模型(实际互联网应用模型),再结合 "分层协作" 的逻辑,看 HTTP 如何依赖底层协议实现 "客户端 - 服务器" 的通信,以及它自身在分层中的角色。

一、先理清基础:OSI 七层模型与 TCP/IP 四层模型的对应关系

有一个辨别四层和七层比较好的(但不是绝对的)小窍门,"两个凡是":凡是由操作系统负责处理的就是四层或四层以下,否则,凡是需要由应用程序(也就是你自己写代码)负责处理的就是七层

两种模型的核心是 "上层依赖下层提供的服务,下层为上层屏蔽实现细节"(比如 HTTP 不用关心数据如何通过网线传输,只需专注 "请求 / 响应" 的逻辑)。具体对应如下:

OSI 七层模型(从下到上) 核心功能 TCP/IP 四层模型(从下到上) 核心功能 与 HTTP 的关联
1. 物理层(Physical) 传输 "比特流"(0/1 电信号),如网线、无线信号 无直接对应(融入链路层) 物理介质的信号传输 HTTP 不直接交互,依赖其传输底层数据
2. 数据链路层(Data Link) 将比特流封装成 "帧",处理 MAC 地址、差错校验(如以太网、Wi-Fi) 1. 链路层(Link) 同 OSI 数据链路层,负责局域网内传输 HTTP 不直接交互,依赖其封装帧数据
3. 网络层(Network) 封装 "数据包",处理 IP 地址、路由(找目标服务器) 2. 网际层(Internet) 同 OSI 网络层,负责跨网络路由 HTTP 依赖其实现 "找服务器"(IP 寻址)
4. 传输层(Transport) 封装 "段 / 报",处理端口、可靠传输(如 TCP/UDP) 3. 传输层(Transport) 同 OSI 传输层,负责端到端通信 HTTP 依赖其实现 "可靠连接"(TCP)
5. 会话层(Session) 建立 / 维护 / 断开 "会话"(如 TCP 连接管理) 4. 应用层(Application) 融合 OSI 5-7 层功能,负责具体应用逻辑 HTTP 的会话管理(如连接复用)依赖此层逻辑
6. 表示层(Presentation) 处理数据格式(编码、压缩、加密),如 JSON、gzip、TLS 4. 应用层(Application) 同表示层功能,由应用协议自行实现 HTTP 的编码、压缩、HTTPS 加密在此层处理
7. 应用层(Application) 定义 "用户级" 通信协议(如 HTTP、FTP、DNS)

二、HTTP 的分层定位:属于 "应用层",依赖底层协议实现通信

HTTP 的本质是应用层协议------ 它不关心 "数据如何从 A 地传到 B 地"(底层的物理层、链路层、网络层、传输层负责),只关心 "数据传什么、怎么传(请求 / 响应格式)、传完后怎么处理"。

可以用 "快递运输类比" 理解 HTTP 与各层的关系:

  • 你(客户端)要寄一个包裹(HTTP 请求)给朋友(服务器);
  • HTTP(应用层):你写的 "快递单"(请求行、请求头、请求体),明确包裹内容(如 "商品数据")、收件人(服务器域名)、寄件要求(如 "优先送达");
  • TCP(传输层):快递员,负责保证包裹不丢、不损坏(可靠传输),并记录 "收件人电话(端口号,如 HTTP 默认 80)";
  • IP(网际层):快递路线规划师,根据 "收件人地址(服务器 IP)" 规划从你家到朋友家的路线(跨网络路由);
  • 链路层 / 物理层:快递车、公路 / 铁路,负责将包裹从一个站点运到下一个站点(物理传输)。

三、关键:HTTP 如何与各层协作?(以 "发送 HTTP 请求" 为例拆解)

HTTP 的通信过程,本质是 "数据在各层层层封装、传输、再层层解封装" 的过程,每一层都为数据添加 "自己的标识"(头部信息),帮助数据准确到达目标并被处理。

步骤 1:应用层(HTTP)------ 封装 "请求报文"

客户端(如浏览器)根据用户操作(如访问www.baidu.com),按 HTTP 协议格式生成 "HTTP 请求报文":

yaml 复制代码
GET / HTTP/1.1  # 请求行(方法、路径、版本)
Host: www.baidu.com  # 请求头(服务器域名、缓存策略等)
User-Agent: Chrome/118.0.0.0  # 客户端标识
Accept: text/html,application/xhtml+xml  # 可接收的响应格式
(空行,分隔请求头和请求体)
(请求体,GET请求无内容)

这一步是 HTTP 的核心工作:定义 "要什么资源、用什么规则要"。

步骤 2:传输层(TCP)------ 封装 "TCP 段",建立可靠连接
  • 首先,TCP 会先与服务器的 80 端口(HTTP 默认端口)建立 "三次握手",确保连接可靠(见之前 HTTP 工作原理中的 TCP 握手);
  • 然后,TCP 将 HTTP 请求报文(应用层数据)封装成 "TCP 段",添加TCP 头部(包含:源端口(客户端随机端口,如 54321)、目标端口(80)、序列号(确保数据按顺序到达)、确认号(确保数据不丢失));
  • 作用:TCP 为 HTTP 提供 "可靠传输保障"------ 如果数据丢包,TCP 会重传;如果数据乱序,TCP 会排序。
步骤 3:网际层(IP)------ 封装 "IP 数据包",实现路由

IP 层将 TCP 段(传输层数据)封装成 "IP 数据包",添加IP 头部(包含:源 IP(客户端 IP,如 192.168.1.100)、目标 IP(服务器 IP,如 14.215.177.38)、TTL(数据存活时间,避免循环传输));

  • 作用:IP 负责 "找路"------ 通过路由器将 IP 数据包从客户端所在的局域网,路由到服务器所在的网络(比如从你家的路由器,经过运营商机房、骨干网,最终到达百度的服务器集群)。
步骤 4:链路层(Data Link)------ 封装 "数据帧",实现局域网传输

链路层将 IP 数据包封装成 "数据帧",添加帧头部(包含:源 MAC 地址(客户端网卡 MAC)、目标 MAC 地址(下一跳设备的 MAC,如你家路由器的 MAC)、帧校验序列(检查数据是否损坏));

  • 作用:负责 "局域网内传输"------ 比如将数据帧从你的电脑(客户端)通过网线 / Wi-Fi 传到路由器,再由路由器转发到下一个设备(如运营商的交换机)。
步骤 5:物理层(Physical)------ 传输 "比特流"

链路层的 "数据帧" 最终会被转换为 "比特流"(0 和 1 的电信号 / 光信号),通过物理介质(网线、光纤、无线信号)传输到下一个设备;

  • 作用:最底层的 "信号传输",HTTP 完全不感知这一层的存在。
步骤 6:服务器端 ------ 层层解封装,最终交给 HTTP 处理

服务器接收数据后,按 "从下到上" 的顺序层层解封装:

  1. 物理层:将比特流还原为数据帧;
  2. 链路层:验证帧校验,去除帧头部,得到 IP 数据包;
  3. 网际层:验证 IP 头部,去除 IP 头部,得到 TCP 段;
  4. 传输层:验证 TCP 头部,去除 TCP 段头部,得到 HTTP 请求报文;
  5. 应用层:将 HTTP 请求报文交给 Web 服务器(如 Nginx),Web 服务器解析请求(如 "要访问根路径 /"),处理后生成 HTTP 响应报文;
  6. 响应报文再按 "应用层→传输层→网际层→链路层→物理层" 的顺序封装,传回客户端,客户端解封装后,浏览器渲染响应内容(如百度首页)。

四、补充:OSI 5-7 层功能在 HTTP 中的体现(为什么 TCP/IP 应用层要融合这三层?)

你提到 OSI 的会话层(5)、表示层(6)、应用层(7)统一对应 TCP/IP 的应用层,这是因为 TCP/IP 模型更侧重 "实际应用",将 "会话管理、数据表示" 的功能交给应用协议(如 HTTP)和传输层(如 TCP)共同实现,而非单独分层。具体到 HTTP:

1. 会话层(Session)功能:由 HTTP+TCP 共同实现

会话层的核心是 "建立、维护、断开通信会话",HTTP 通过以下方式实现:

  • 会话建立:TCP 三次握手建立连接,HTTP 在连接上发送请求;
  • 会话维护 :HTTP/1.1 的Keep-Alive(持久连接),让一个 TCP 连接可处理多个 HTTP 请求,避免频繁建立会话;
  • 会话断开 :TCP 四次挥手断开连接,或 HTTP 主动发送Connection: close请求关闭连接。
2. 表示层(Presentation)功能:由 HTTP 直接实现

表示层的核心是 "处理数据格式、编码、压缩、加密",HTTP 通过请求头 / 响应头明确这些规则:

  • 数据格式Content-Type头(如text/html表示 HTML 文本、application/json表示 JSON 数据、image/jpeg表示 JPEG 图片);
  • 数据压缩Content-Encoding头(如gzip表示数据用 gzip 压缩,客户端需解压后处理);
  • 数据加密 :HTTPS(HTTP+TLS),TLS 属于表示层的加密功能,通过https://协议标识,确保数据传输安全;
  • 字符编码Content-Charset头(如UTF-8,确保客户端正确解析中文等特殊字符)。
3. 应用层(Application)功能:HTTP 的核心职责

定义 "用户级" 的通信规则,即 "请求什么、怎么响应":

  • 请求方法(GET/POST/PUT/DELETE)定义 "操作意图";
  • 状态码(200/404/500)定义 "处理结果";
  • 报文格式(请求行 + 请求头 + 请求体、响应行 + 响应头 + 响应体)定义 "数据结构"。

五、总结:HTTP 分层的核心意义

  1. 解耦与模块化:各层各司其职,HTTP 只需专注 "应用逻辑",底层的 "传输、路由、物理传输" 由其他协议负责 ------ 比如想提升安全性,只需在表示层添加 TLS(即 HTTPS),无需修改 TCP/IP 底层;想提升传输效率,只需升级 HTTP 版本(如 HTTP/2→HTTP/3),底层仍可复用 TCP/IP。
  2. 可扩展性:新的应用协议(如 HTTP/3)可基于现有底层协议(如 QUIC 替代 TCP,仍用 IP 路由、链路层传输)快速落地,无需重构整个网络模型。
  3. 故障定位清晰:若 HTTP 请求失败,可按分层排查 ------ 比如 "无法连接" 可能是 TCP 层(端口错误)或网际层(IP 错误)问题;"响应 404" 是应用层(HTTP 路径错误)问题。

简言之,HTTP 是 "应用层的协议",它像一个 "指挥官",依赖传输层(TCP)的 "可靠运输"、网际层(IP)的 "路线规划"、链路层 / 物理层的 "硬件传输",最终实现 "客户端与服务器的资源交互"。理解 HTTP 的分层,就能理解 "为什么 HTTP 需要 TCP""为什么访问网站需要 IP""HTTPS 为什么更安全" 等核心问题。

键入网址到回车发生什么

  • HTTP协议基于底层的TCP/IP协议,所以必须要用IP地址建立连接
  • 如果不知道IP地址,就要用DNS协议去解析得到IP地址,否则会连接失败
  • 建立TCP连接后会顺序收发数据,请求方和应答方都必须依据HTTP规范构建和解析报文
  • 为了减少响应事件,整个过程中的每一个环节都会有缓存,能够实现短路操作
  • 虽然现实中的HTTP传输过程非常复杂,但是理论上仍然可以简化成实验里的两点模型

HTTP报文结构是什么样的?

报文结构

  • 你也许对TCP/UDP的报文格式有所了解,拿TCP报文来举例,它在实际传输的数据之前附加了一个20字节的头部数据,存储TCP协议必须的额外信息,例如发送方的端口号,接收方的端口号,包序号,标志位等等。
  • 有了这个附加的TCP头,数据包才能够正确传输,到了目的地后把头部去掉,就可以拿到真正的数据。

HTTP协议也是与TCP/UDP类似,同样也需要在实际传输的数据前附加一些头数据,不过与TCP/UDP不同的是,它是一个纯文本的协议,所以头数据都是ASCII码的文本,可以很容易地用肉眼阅读,不用借助程序解析也能够看懂

HTTP协议的请求报文和响应报文的结构基本相同,由三大部分组成

  • 起始行(start line):描述请求或者响应的基本信息;
  • 头部字段集合(header):使用key-value形式更详细地说明报文;
  • 消息正文(entity):实际传输的数据,它不一定是纯文本,可以是图片,视频等二进制数据;

这其中前两部分起始行头部字段经常又合称为请求头或者响应头消息正文又称为实体,但与header对应,很多时候就直接称为body
HTTP协议规定报文必须有header,但是可以没有body,而且在header之后必须要有一个空行,也就是CRLF,十六进制的0D0A
CRLF(Carriage Return Line Feed,回车换行符) 是由两个 ASCII 控制字符组合而成的 "文本换行标识",也是网络协议(如 HTTP、SMTP)中定义 "数据边界" 的核心分隔符。它的本质是通过两个字符的协作,完成 "光标回到行首 + 下移一行" 的完整换行动作,同时在网络通信中明确 "不同数据段的分割规则"。
CRLF 的本质:两个字符的分工与起源

CRLF 由两个独立的 ASCII 字符组成,各自对应早期计算机终端(如电传打字机、老式终端)的物理操作逻辑,这也是 "为什么需要两个字符" 的历史根源:

字符 名称 ASCII 码 原始物理含义(电传打字机时代) 现代文本中的作用
\r Carriage Return(CR,回车符) 13 让 "打印头" 从当前行的右侧回到左侧(行首) 使光标移动到当前行的开头位置
\n Line Feed(LF,换行符) 10 让 "纸卷" 向下滚动一行(但打印头位置不变) 使光标从当前行向下移动一行

早期电传打字机需要先 "回车"(CR)让打印头归位,再 "换行"(LF)让纸卷下移,才能正确开始下一行打印 ------ 这一逻辑被延续到计算机文本和网络协议中,形成了 "CRLF" 组合。

所以一个完整的HTTP报文就像是下图的这个样子,注意在headerbody之间有一个空行。

说到这里,我不由得想起了一部老动画片《大头儿子和小头爸爸》,你看,HTTP 的报文结构像不像里面的"大头儿子"?

报文里的 header 就是"大头儿子"的"大头",空行就是他的"脖子",而后面的 body 部分就是他的身体了。

看一下我们之前用 Wireshark 抓的包吧。

在这个浏览器发出的请求报文里,第一行"GET / HTTP/1.1"就是请求行,而后面的"Host""Connection"等等都属于 header,报文的最后是一个空白行结束,没有 body。

在很多时候,特别是浏览器发送 GET 请求的时候都是这样,HTTP 报文经常是只有 header 而没 body,相当于只发了一个超级"大头"过来,你可以想象的出来:每时每刻网络上都会有数不清的"大头儿子"在跑来跑去。

不过这个"大头"也不能太大,虽然 HTTP 协议对 header 的大小没有做限制,但各个 Web 服务器都不允许过大的请求头,因为头部太大可能会占用大量的服务器资源,影响运行效率。

请求行

了解了HTTP报文的基本结构后,我们来看看请求报文里的起始行也就是请求行(request line),它简明地描述了客户端想要如何操作服务器端的资源。 请求行由三部分组成

  • 请求方法:是一个动词,如GET/POST,表示对资源的操作;
  • 请求目标:通常是一个URI,标记了请求方法要操作的资源;
  • 版本号:表示报文使用的HTTP协议版本;

这三个部分通常由空格来分隔,最后要用CRLF换行表示结束。

状态行

比起请求行来说,状态行要简单一些,同样也是由三部分构成:

  • 版本号:表示报文使用的HTTP协议版本;
  • 状态码:一个三位数,用代码的形式表示处理的结果,比如200是成功,500是服务器错误;
  • 原因:作为数字状态码补充,是更详细的解释文字,帮助人理解原因

头部字段

请求行或状态行再加上头部字段集合就构成了HTTP报文里完整的请求头或响应头

请求头和响应头的结构是基本一样的,唯一的区别是起始行,所以我们把请求头和响应头里的字段放在一起介绍。

头部字段是key-value的形式,key和value之间使用" :"分割,最后用CRLF换行表示字段结束。比如在"Hosting:127.0.0.1"这一行里key就是Host,value就是127.0.0.1

HTTP头字段非常灵活,不仅可以使用标准里的Host,Connection等已有头,也可以添加自定义头,这就给HTTP协议带来了无限的扩展可能。 不过使用头字段需要注意下面几点

  • 字段名不区分大小写
  • 字段名不能出现空格,可以使用连字符"-",但是不能使用下划线"_"
  • 字段名后面必须紧接着冒号:,不能有空格,而冒号后面的字段值前可以有多个空格
  • 字段的顺序是没有意义的,可以任意排列不影响语义
  • 字段原则上不能重复,除非这个字段本身的语义允许

标准请求方法

目前 HTTP/1.1 规定了八种方法,单词都必须是大写的形式

  • GET:获取资源,可以理解为读取或者下载数据;

  • HEAD:获取资源的元信息;

  • POST:向资源提交数据,相当于写入或上传数据;

  • PUT:类似 POST

  • DELETE:删除资源;

  • CONNECT:建立特殊的连接隧道;

  • OPTIONS:列出可对资源实行的方法;

  • TRACE:追踪请求 - 响应的传输路径

  • 这些方法是客户端发出的,要求服务器执行的,对资源的一种操作

  • 请求方法是对服务器的指示,真正应该如何处理由服务器来决定

  • 最常用的请求方法是GETPOST,分别是获取数据和发送数据

  • HEAD方法是轻量级的GET,用来获取资源的元信息

  • PUT基本上是POST的同义词,多用于更新数据

  • 安全幂等是描述请求方法的两个重要属性,具有理论指导意义,可以帮助我们设计系统

安全与幂等: 在HTTP协议中,安全与幂等是描述请求方法特性的两个核心属性,它们并非强制约束,但是作为设计规范,能指导我们构建更加可靠,可预测,易维护的系统。理解这两个属性,本质是理解请求对服务器资源状态的影响规律,从而在接口设计,重试机制,缓存策略等场景中做出合理决策。

安全的请求方法是指: 不会修改服务器上面的内容,仅用于获取资源。即这类请求执行后,服务器的资源不会发生任何变化。

  • GET:最典型的安全方法,用于获取资源,执行后服务器资源状态不变(仅返回数据,不增删改查)
  • HEAD:安全方法,与GET类似,但是仅返回响应头(不返回响应体),同样不修改资源
  • 非安全方法POSTPUTDELETE,这些会改变服务器资源的状态

安全属性的设计意义

  • 缓存可行性:安全方法的响应可以被浏览器,CDN等缓存(因为资源不会被请求本身修改)。例如,GET请求的结果会被浏览器缓存,下次相同请求可直接使用本地缓存,减少服务器压力。
  • 用户信任:用户可以放心执行安全方法,无需担心"重复操作导致意外的后果"。
  • 爬虫与索引:搜索爬虫主要依赖GET方法抓取内容,正是因为GET是安全的,不会破坏网站数据。

幂等的请求方法是指:多次执行相同的请求,最终服务器资源状态产生的影响与执行一次完全相同。即重复调用不会导致意外的状态变化,但是允许过程不同(比如第一次执行是创建,后续执行是忽略,但是最终状态一致) 幂等的核心是状态结果的一致性

  • GET幂等。多次请求同一个资源,服务器资源状态不变,结果一致。
  • PUT幂等。用于全量更新资源(如PUT /user/1 更新ID=1的用户信息),无论执行几次,最终的用户1的信息都是最后一次请求中的数据。
  • DELETE幂等。用于删除资源(如DELETE /order/100 删除ID=100的订单),第一次执行订单从存在到不存在,后续执行多少次,订单都是不存在,最终的状态一致。
  • POST非幂等方法。用于创建资源(如POST /order提交订单),多次执行可能创建多个订单(每个订单的ID不同),最终状态与执行一次不同(多了N-1个订单),因此非幂等。

幂等性的关键细节

  • 幂等关注状态变化而非响应内容:两次相同的GET请求之间有新的内容被创建了,两次响应的内容不同,但是GET没有改变订单的状态,响应不同是其他请求造成的。
  • 幂等不要求每次响应完全相同:例如,DELETE/order/100 第一次返回204 No Content(删除成功),第二次返回404 Not Found(订单已不存在),响应不同,但最终状态都是订单100不存在,因此是幂等的。
  • 幂等需要请求参数完全相同:若两次PUT请求的参数不相同,则不满足相同请求,结果自然不同,这与幂等性无关。

幂等属性的设计意义

  • 重试机制的可靠性:网络不稳定的时候(超时),客户端需要重试请求。幂等方法可安全重试------------例如,支付系统中,用户点击支付后请求超时,客户端重试PUT /pay/100(幂等),不会导致重复扣款;而若使用POST(非幂等),重试可能导致重复支付。
  • 分布式系统的一致性:在分布式场景中(如微服务调用),请求可能被重复投递(如消息队列重试),幂等设计可以确保重复投递的请求不会破坏数据的一致性(如多次修改库存的请求,最终库存只减少一次)。
  • 简化并发处理:幂等方法无需担心并发重复执行的副作用,例如,多个线程同时执行DELETE /order/100,最终结果仍是订单被删除,无需复杂的锁机制避免重复操作。

安全与幂等的关系

安全和幂等是两个独立的属性,两者既可能重叠,也可能独立存在,核心区别在于:

  • 安全:关注 "是否修改资源"(副作用有无);
  • 幂等:关注 "多次修改的结果是否一致"(副作用的重复性)。

两者的关系可用表格清晰展示(以 HTTP 方法为例):

HTTP 方法 是否安全(Safe) 是否幂等(Idempotent) 核心原因
GET 仅获取资源(不修改),多次执行资源状态不变
HEAD 同 GET(仅返回头),不修改资源,多次执行结果一致
PUT 会修改资源(不安全),但多次执行最终状态相同(幂等)
DELETE 会删除资源(不安全),但多次执行最终状态相同(幂等)
POST 会创建资源(不安全),多次执行可能创建多个资源(非幂等)

如何使用这个两个属性设计系统

  • 根据业务场景选择合适的 HTTP 方法

    • 若需 "查询数据":用 GET(安全 + 幂等),可缓存,用户可放心刷新;
    • 若需 "创建数据":用 POST(非安全 + 非幂等),需做好防重复提交(如添加唯一订单号);
    • 若需 "更新数据":用 PUT(非安全 + 幂等),支持重试,适合全量更新;
    • 若需 "删除数据":用 DELETE(非安全 + 幂等),重试不会导致错误。
  • 非幂等场景必须做 "防重复" 处理 :对于 POST 等非幂等方法(如提交订单、支付),需通过 "唯一标识"(如订单号、请求 ID)实现幂等效果 ------ 例如,客户端生成 UUID 作为request_id,服务器首次处理时记录 ID,后续相同 ID 的请求直接返回 "已处理",避免重复创建资源。

  • 安全方法可大胆缓存:对 GET/HEAD 请求,可在服务器、CDN、浏览器中多层缓存,减少重复计算和网络传输(如商品详情页、静态资源),但需注意设置合理的缓存过期时间(避免返回旧数据)。

  • 幂等方法简化重试逻辑:对 PUT/DELETE 请求,客户端可实现自动重试(如超时后重试 3 次),无需担心 "重试导致数据错误",尤其适合分布式系统(如微服务调用超时、消息队列重试)。

URI

  • URI 是用来唯一标记服务器上资源的一个字符串,通常也称为 URL;
  • URI 通常由 schemehost:portpathquery 四个部分组成,有的可以省略;
  • scheme 叫"方案名"或者"协议名",表示资源应该使用哪种协议来访问;
  • "host:port"表示资源所在的主机名和端口号;
  • path 标记资源所在的位置;
  • query 表示对资源附加的额外要求;
  • URI 里对"@&/"等特殊字符和汉字必须要做编码,否则服务器收到 HTTP报文后会无法正确处理
  • URI 是 "资源的身份证" :用于唯一标识互联网上的任何资源(如网页、图片、文件、API 接口),标识方式可以是 "告诉资源在哪里"(URL),也可以是 "给资源起个唯一名字"(另一种 URI:URN);
  • URL 是 "资源的地址" :不仅标识资源,还明确了 "如何访问资源"(如通过 HTTP 协议、在哪个服务器上、哪个路径下)。
对比维度 URI(统一资源标识符) URL(统一资源定位符) 关键结论
核心功能 唯一 "标识" 资源(回答 "资源是谁") 唯一 "定位 + 访问" 资源(回答 "资源在哪、怎么访问") URI 是 "标识",URL 是 "标识 + 定位 + 访问"------URL 比 URI 多了 "访问方式" 和 "位置信息"
必须包含的信息 至少包含 "方案" 和 "资源标识"(如urn:isbn:9787115428028 必须包含 "方案""主机名"(位置),通常包含 "路径"(如https://www.baidu.com/s URI 可无位置信息(如 URN),URL 必须有位置信息(否则无法定位)
实际应用场景 1. 用名称标识资源(如书籍 ISBN、专利号);2. 抽象描述资源(不关心访问方式) 1. 浏览器地址栏访问网页;2. API 接口调用(如https://api.example.com/user/1);3. 下载文件(如ftp://example.com/file.zip 日常互联网使用中,99% 的场景是 URL;URN 仅在 "需要永久标识资源且不依赖位置" 的场景(如学术、专利)使用

URL的通用结构

组件(Component) 含义与作用 示例中的对应内容 是否必选 关键说明
方案(Scheme) 定义访问资源的 "协议或方式",告诉客户端 "如何获取资源" https(HTTP 协议的加密版) URL 必须包含 Scheme(如 http、https、ftp);URN 的 Scheme 通常是urn(如urn:isbn:9787115428028
授权(Authority) 资源所在的 "服务器信息",通常包含 "用户认证(可选)""主机名(域名 / IP)""端口(可选)" www.baidu.com(主机名,默认端口 443) 否(但 URL 通常包含) 格式可扩展为 [用户:密码@]主机名[:端口](如ftp://user:pass@ftp.example.com:21
路径(Path) 资源在服务器上的 "具体位置",类似文件系统的目录路径 /s(百度搜索的路径标识) /分隔层级(如/blog/2024/01表示 2024 年 1 月的博客目录)
查询(Query) 向服务器传递的 "参数",用于筛选或定制资源(如搜索关键词、分页信息) wd=URI(搜索关键词为 "URI") ?开头,多个参数用&分隔(如?page=1&size=10表示第 1 页,每页 10 条)
片段(Fragment) 资源内部的 "锚点",用于定位资源的特定部分(如网页内的某个章节) top(定位到网页顶部) #开头,片段不会发送到服务器(仅由客户端解析,如浏览器滚动到锚点位置)

响应状态

  • 状态码在响应报文里表示了服务器对请求的处理结果;
  • 状态码后的原因短语是简单的文字描述,可以自定义;
  • 状态码是十进制的三位数,分为五类,从 100599
  • 2××类状态码表示成功,常用的有 200204206
  • 3××类状态码表示重定向,常用的有 301302304
  • 4××类状态码表示客户端错误,常用的有 400403404
  • 5××类状态码表示服务器错误,常用的有 500501502503

HTTP特点

  • HTTP 是灵活可扩展的,可以任意添加头字段实现任意功能;
  • HTTP 是可靠传输协议,基于 TCP/IP 协议"尽量"保证数据的送达;
  • HTTP 是应用层协议,比 FTPSSH 等更通用功能更多,能够传输任意数据;
  • HTTP 使用了请求 - 应答模式,客户端主动发起请求,服务器被动回复请求;
  • HTTP 本质上是无状态的,每个请求都是互相独立、毫无关联的,协议不要求客户端或服务器记录请求相关的信息

HTTP优缺点

  • HTTP 最大的优点是简单、灵活和易于扩展;
  • HTTP 拥有成熟的软硬件环境,应用的非常广泛,是互联网的基础设施;
  • HTTP 是无状态的,可以轻松实现集群化,扩展性能,但有时也需要用 Cookie 技术来实现"有状态";
  • HTTP 是明文传输,数据完全肉眼可见,能够方便地研究分析,但也容易被窃听;
  • HTTP 是不安全的,无法验证通信双方的身份,也不能判断报文是否被窜改;
  • HTTP 的性能不算差,但不完全适应现在的互联网,还有很大的提升空间

HTTP(Hypertext Transfer Protocol,超文本传输协议)作为互联网的核心协议,支撑了全球绝大多数的资源交互(如网页访问、API调用、文件传输等)。其设计既有适应互联网发展的"天然优势",也存在随着技术演进逐渐暴露的"局限性"。理解其优缺点,能帮助我们在实际项目中更好地利用其优势、规避风险。

一、HTTP的核心优点:奠定其成为互联网基石的原因

1. 简单性:易于理解和实现

HTTP的报文结构(请求行/响应行、请求头/响应头、请求体/响应体)设计简洁,字段语义明确(如GET表示获取资源、200表示成功),开发者无需复杂学习就能解析和构造HTTP报文。

  • 例:一个完整的HTTP请求仅需几行文本(GET /index.html HTTP/1.1\r\nHost: example.com\r\n\r\n),服务器和客户端的实现门槛极低,这也是它能快速普及的核心原因。
2. 灵活性:支持多种数据格式与场景

HTTP不限制传输的数据类型(通过Content-Type头指定),可传输文本(HTML、JSON)、二进制(图片、视频)、表单数据等,能适应从静态网页到动态API、文件上传等几乎所有互联网场景。

  • 例:同一个域名下,https://example.com/page返回HTML网页(text/html),https://example.com/api/user返回JSON数据(application/json),https://example.com/photo返回图片(image/jpeg),协议层无需额外适配。
3. 无状态:轻量且易于扩展

HTTP是"无状态协议"------服务器不会保存客户端的历史请求状态(如"用户是否登录过""上一次访问的页面")。这一特性带来两个核心优势:

  • 服务器负担轻:无需在内存中维护大量会话状态,单个服务器可支持更多并发连接;
  • 分布式友好:客户端的请求可随机分配到集群中的任何服务器(无需同步状态),便于水平扩展(如电商大促时增加服务器节点)。
4. 可扩展性:通过头部机制适配新需求

HTTP的"头部(Header)"设计具有极强的可扩展性:标准头部(如Cache-Control控制缓存、Authorization处理认证)覆盖基础需求,自定义头部(如X-Request-ID追踪请求、X-User-Region标识用户地区)可满足业务个性化需求。

  • 例:当需要对请求进行链路追踪时,无需修改HTTP协议本身,只需在请求头中添加X-Trace-ID,所有中间节点(如网关、服务)传递该字段即可实现追踪。
5. 兼容性:跨平台、跨语言的普适性

几乎所有操作系统(Windows、Linux、macOS、iOS、Android)、编程语言(Java、Python、JavaScript等)、网络设备(路由器、网关、CDN)都原生支持HTTP,形成了"全生态兼容"的局面。

  • 例:无论是浏览器(Chrome、Safari)、手机APP(微信、抖音)、后端服务(Nginx、Tomcat),还是嵌入式设备(智能手表、智能家居),都能通过HTTP进行通信,无需额外适配。

二、HTTP的主要缺点:随技术发展暴露的局限性

1. 无状态的副作用:状态维护需额外机制

"无状态"虽然轻量,但在需要"连续交互"的场景(如登录后访问个人中心、购物车操作)中,必须通过额外机制(Cookie、Session、Token)维持状态,这会增加系统复杂度:

  • 例:用户登录后,服务器需生成Token并通过响应头返回,客户端需存储Token(如LocalStorage),后续请求需在Authorization头中携带Token------这一系列操作都是对"无状态"的补充,可能引入Token泄露、过期管理等问题。
2. 安全性缺陷:明文传输易被窃听和篡改

HTTP传输的数据(包括请求头、请求体)是明文(未加密),在传输过程中(如公共Wi-Fi、运营商链路)可能被窃听(获取账号密码、支付信息)或篡改(替换商品价格、植入恶意代码)。

  • 例:用户在咖啡厅通过HTTP提交登录表单,攻击者可通过抓包工具直接获取username=xxx&password=xxx的明文数据,导致账号被盗。
  • 解决方案:需通过HTTPS(HTTP+TLS加密)弥补,但HTTPS会增加CPU加密开销和网络延迟(约10%-30%)。
3. 性能瓶颈:HTTP/1.1的队头阻塞与连接开销

HTTP/1.1(目前仍广泛使用的版本)存在两个核心性能问题:

  • 队头阻塞(Head-of-Line Blocking):同一TCP连接中,多个请求需串行处理(前一个请求未完成,后一个请求必须等待),若某个请求超时或卡顿,后续所有请求都会被阻塞;
  • 连接开销大 :虽然HTTP/1.1通过Keep-Alive支持持久连接(一个TCP连接处理多个请求),但建立TCP连接的"三次握手"仍需耗时(约100ms-1s,取决于网络),且浏览器对同一域名的并发连接数有限制(通常6-8个)。
4. 语义模糊:方法和状态码的滥用风险

HTTP定义的方法(GET/POST/PUT/DELETE)和状态码(200/404/500)有明确语义,但实际开发中常被滥用:

  • 例:用GET请求修改资源(如GET /delete?id=1删除数据),违反GET的"安全"特性(可能被缓存或误触发);用200状态码返回所有结果(包括错误,如{"code":-1,"msg":"失败"}),忽略了400/401等错误码的语义,导致客户端难以通过状态码快速判断请求结果。
5. 缓存机制复杂:易导致数据不一致

HTTP的缓存机制(Cache-ControlETagLast-Modified等)虽然能减少重复请求,但配置不当易引发"缓存脏数据"问题:

  • 例:若静态资源(如CSS、JS)的Cache-Control: max-age=3600设置过长,当资源更新后,客户端仍会使用旧缓存,导致页面样式错乱;若设置过短,又会频繁请求服务器,失去缓存意义。

三、总结:HTTP的"功与过"及演进方向

HTTP的优点(简单、灵活、可扩展、兼容性)使其成为互联网的"通用语言",支撑了从早期静态网页到现代微服务的所有场景;而其缺点(安全性、性能、状态管理)则推动了协议的持续演进:

  • 安全性:通过HTTPS(HTTP+TLS)解决明文传输问题,已成为主流(浏览器对HTTP网站标记"不安全");
  • 性能:HTTP/2(多路复用、服务器推送)解决队头阻塞,HTTP/3(基于QUIC协议,替换TCP为UDP)进一步降低延迟;
  • 状态管理:通过JWT(JSON Web Token)等标准化Token机制,简化无状态下的身份验证。

简言之,HTTP并非"完美协议",但它的设计哲学(简单优先、按需扩展)使其能通过"补丁"(HTTPS)和"升级"(HTTP/2/3)适应互联网的发展,这正是其生命力的核心所在。

HTTP的实体数据

数据类型与编码

  • text:即文本格式的可读数据,我们最熟悉的应该就是 text/html 了,表示超文本文档,此外还有纯文本 text/plain、样式表 text/css 等。
  • image:即图像文件,有 image/gifimage/jpegimage/png 等。
  • audio/video:音频和视频数据,例如 audio/mpegvideo/mp4 等。
  • application:数据格式不固定,可能是文本也可能是二进制,必须由上层应用程序来解释。常见的有 application/jsonapplication/javascriptapplication/pdf 等,另外,如果实在是不知道数据是什么类型,像刚才说的"黑盒",就会是 application/octet-stream,即不透明的二进制数据

但仅有 MIME type 还不够,因为 HTTP 在传输时为了节约带宽,有时候还会压缩数据,为了不要让浏览器继续"猜",还需要有一个"Encoding type",告诉数据是用的什么编码格式,这样对方才能正确解压缩,还原出原始的数据。

比起 MIME type 来说,Encoding type 就少了很多,常用的只有下面三种

  • gzipGNU zip 压缩格式,也是互联网上最流行的压缩格式;
  • deflatezlibdeflate)压缩格式,流行程度仅次于 gzip
  • br:一种专门为 HTTP 优化的新压缩算法(Brotli

数据类型使用的头字段

有了 MIME typeEncoding type,无论是浏览器还是服务器就都可以轻松识别出 body 的类型,也就能够正确处理数据了。

HTTP 协议为此定义了两个 Accept 请求头字段和两个 Content 实体头字段,用于客户端和服务器进行"内容协商"。也就是说,客户端用 Accept 头告诉服务器希望接收什么样的数据,而服务器用 Content 头告诉客户端实际发送了什么样的数据

Accept字段标记的是客户端可理解的 MIME type,可以用","做分隔符列出多个类型,让服务器有更多的选择余地,例如下面的这个头:

bash 复制代码
Accept: text/html,application/xml,image/webp,image/png

这就是告诉服务器:"我能够看懂 HTML、XML 的文本,还有 webppng 的图片,请给我这四类格式的数据"。

相应的,服务器会在响应报文里用头字段Content-Type告诉实体数据的真实类型:

arduino 复制代码
Content-Type: text/html
Content-Type: image/png

这样浏览器看到报文里的类型是"text/html"就知道是 HTML 文件,会调用排版引擎渲染出页面,看到"image/png"就知道是一个 PNG 文件,就会在页面上显示出图像。

Accept-Encoding字段标记的是客户端支持的压缩格式,例如上面说的 gzip、deflate 等,同样也可以用","列出多个,服务器可以选择其中一种来压缩数据,实际使用的压缩格式放在响应头字段Content-Encoding

makefile 复制代码
Accept-Encoding: gzip, deflate, br
Content-Encoding: gzip

不过这两个字段是可以省略的,如果请求报文里没有 Accept-Encoding 字段,就表示客户端不支持压缩数据;如果响应报文里没有 Content-Encoding 字段,就表示响应数据没有被压缩

语言类型使用的头字段

同样的,HTTP 协议也使用 Accept 请求头字段和 Content 实体头字段,用于客户端和服务器就语言与编码进行"内容协商"。

Accept-Language字段标记了客户端可理解的自然语言,也允许用","做分隔符列出多个类型,例如:

makefile 复制代码
Accept-Language: zh-CN, zh, en

这个请求头会告诉服务器:"最好给我 zh-CN 的汉语文字,如果没有就用其他的汉语方言,如果还没有就给英文"。

相应的,服务器应该在响应报文里用头字段Content-Language告诉客户端实体数据使用的实际语言类型

css 复制代码
Content-Language: zh-CN
  • 字符集在 HTTP 里使用的请求头字段是Accept-Charset,但响应头里却没有对应的 Content-Charset,而是在Content-Type字段的数据类型后面用"charset=xxx"来表示,这点需要特别注意。
  • 例如,浏览器请求 GBKUTF-8 的字符集,然后服务器返回的是 UTF-8 编码,就是下面这样
makefile 复制代码
Accept-Charset: gbk, utf-8
Content-Type: text/html; charset=utf-8  

不过现在的浏览器都支持多种字符集,通常不会发送 Accept-Charset,而服务器也不会发送 Content-Language,因为使用的语言完全可以由字符集推断出来,所以在请求头里一般只会有 Accept-Language 字段,响应头里只会有 Content-Type字段

内容协商的质量值

在 HTTP 协议里用 AcceptAccept-EncodingAccept-Language 等请求头字段进行内容协商的时候,还可以用一种特殊的"q"参数表示权重来设定优先级,这里的"q"是"quality factor"的意思。

权重的最大值是 1,最小值是 0.01,默认值是 1,如果值是 0 就表示拒绝。具体的形式是在数据类型或语言代码后面加一个";",然后是"q=value"。

这里要提醒的是";"的用法,在大多数编程语言里";"的断句语气要强于",",而在 HTTP 的内容协商里却恰好反了过来,";"的意义是小于","的。

例如下面的 Accept 字段:

bash 复制代码
Accept: text/html,application/xml;q=0.9,*/*;q=0.8 

它表示浏览器最希望使用的是 HTML 文件,权重是 1,其次是 XML 文件,权重是 0.9,最后是任意数据类型,权重是0.8。服务器收到请求头后,就会计算权重,再根据自己的实际情况优先输出 HTML 或者 XML

内容协商的结果

内容协商的过程是不透明的,每个 Web 服务器使用的算法都不一样。但有的时候,服务器会在响应头里多加一个Vary字段,记录服务器在内容协商时参考的请求头字段,给出一点信息,例如:

makefile 复制代码
Vary: Accept-Encoding,User-Agent,Accept

这个 Vary 字段表示服务器依据了 Accept-EncodingUser-AgentAccept 这三个头字段,然后决定了发回的响应报文。

Vary 字段可以认为是响应报文的一个特殊的"版本标记"。每当 Accept 等请求头变化时,Vary 也会随着响应报文一起变化。也就是说,同一个 URI 可能会有多个不同的"版本",主要用在传输链路中间的代理服务器实现缓存服务,这个之后讲"HTTP 缓存"时还会再提到

  • 数据类型表示实体数据的内容是什么,使用的是 MIME type,相关的头字段是 AcceptContent-Type
  • 数据编码表示实体数据的压缩方式,相关的头字段是 Accept-EncodingContent-Encoding
  • 语言类型表示实体数据的自然语言,相关的头字段是 Accept-LanguageContent-Language
  • 字符集表示实体数据的编码方式,相关的头字段是 Accept-Charset和 Content-Type;
  • 客户端需要在请求头里使用 Accept 等头字段与服务器进行"内容协商",要求服务器返回最合适的数据; Accept 等头字段可以用","顺序列出多个可能的选项,还可以用";q="参数来精确指定权重
相关推荐
Whbbit19993 小时前
在 Nestjs 中使用 Drizzle ORM
前端·javascript·nestjs
Never_Satisfied3 小时前
在JavaScript中,map方法使用指南
前端·javascript·vue.js
_码力全开_3 小时前
JavaScript从入门到实战 (1):JS 入门第一步:它是什么?能做什么?环境怎么搭?
开发语言·前端·javascript·新人首发
itslife3 小时前
vite 源码 - 执行 buildStart 钩子
前端·javascript
wsWmsw3 小时前
[译] 浏览器里的 Liquid Glass:利用 CSS 和 SVG 实现折射
前端·css·svg
用户47949283569153 小时前
还不知道'use strict'的作用?这篇文章给你讲清楚
前端·javascript·typescript
银安3 小时前
CSS排版布局篇(2):文档流(Normal Flow)
前端·css
用户47949283569153 小时前
面试官:讲讲这段react代码的输出(踩坑)
前端·javascript·react.js
jump6803 小时前
闭包详细解析
前端