切换深浅色
第 10 章 WebSocket:HTTP 协商之后的双向帧协议
WebSocket借用HTTP建立一个被双方明确接受的会话,随后用自己的帧格式传送双向消息。它不是“不断重复HTTP请求”,也不是去掉所有边界之后的裸TCP。理解它如何承载代理,需要先看清握手、消息、帧和底层流的区别。
10.1 双向通信需要一种怎样的服务
网页聊天、事件推送和交互应用希望服务器在有新数据时直接发送,而不必等待客户端每次发起独立查询。轮询可以提供近似效果,但带来重复请求、等待和状态管理;长轮询延长等待,又需要处理响应结束后重新发起的连接周期。WebSocket为这类应用提供持续的双向消息服务。
这并不意味着HTTP/1.1在物理上只能单向传输。TCP本来就有两个方向,HTTP请求和响应内容也能在适用条件下流式处理;问题在于浏览器API、HTTP请求响应语义与中间设备,是否共同提供应用所需的持续消息抽象。WebSocket把这个抽象作为专门协议定义,而非让每个网页自行模拟一套轮询约定。RFC 6455 §1。
一条WebSocket会话建立以后,两边都能发送文本消息或二进制消息;消息又可以拆成多个帧,帧之外还有Ping、Pong和Close等控制。接收应用通常关注消息,而底层编码器还必须维护帧、掩码和分片状态。因此“有一个send调用”不意味着线上一个TCP段,也不必意味着只有一个帧。
它与原生Socket还有重要边界。浏览器脚本不能把WebSocket当成可任意构造TCP首部和连接任意服务的权限;对端需要接受WebSocket握手,并按帧协议交流。服务器明确参与,是这份协议安全模型的一部分。把一段WebSocket字节直接送进只懂普通HTTP或自定义TCP的服务,不会自动变成相容会话。
ws://表示不使用TLS的WebSocket方案,wss://则在其外建立TLS。默认端口分别为80与443,URI的路径等信息用于握手时定位服务。wss的加密能力来自TLS;WebSocket自身的帧掩码只是一种逐帧数据变换,不是保密算法,后面会逐字节解释。
对代理而言,二进制消息可以承载代理请求与目标字节,但两端需要一个适配层:发送方把要传的字节放进消息,接收方去掉WebSocket帧结构,再按代理协议解释。WebSocket不理解SOCKS目标或VLESS身份,也不代替这些协议的认证。它提供承载服务,代理协议提供转发语义。
本章先讨论RFC 6455的HTTP/1.1 Upgrade路径。后续HTTP/2等版本承载WebSocket有另外的规范映射,不能把101响应与Connection字段原样套到所有HTTP版本。通过反向代理或CDN时,也要确认平台是否支持对应升级、持续连接、帧大小及超时,而不是仅因为握手开头看起来像GET就认为必然可用。
10.2 Upgrade 握手:证明对端接受协议,不是证明用户身份
HTTP/1.1路径的客户端先发送GET升级请求。以下使用RFC中的公开示例随机值,只为复核结果,实际连接必须重新随机选择:
text
GET /chat HTTP/1.1\r\n
Host: example.com\r\n
Upgrade: websocket\r\n
Connection: Upgrade\r\n
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==\r\n
Sec-WebSocket-Version: 13\r\n
Origin: https://app.example\r\n
\r\nUpgrade声明希望使用WebSocket;Connection中的Upgrade token使这项升级信息按当前跳处理;Sec-WebSocket-Version的13是WebSocket版本,不是HTTP版本,更不是TLS 1.3。Host和路径则用来选择接收该握手的服务。
Sec-WebSocket-Key是每连接随机选择的十六字节值经Base64后的文本。这类一次使用的随机值常称为nonce;此处它不是保密密钥。服务器按公开规则产生Accept,让客户端确认返回者确实按WebSocket握手处理了请求,而非把普通HTTP响应误当升级成功。
计算步骤是:取Key字段的Base64文本本身,拼接固定字符串 258EAFA5-E914-47DA-95CA-C5AB0DC85B11,对拼接结果作SHA-1,再把二十字节结果Base64编码。SHA-1在这里是一种把输入映射成固定长度输出的公开摘要运算;完整密码学性质后文再讲。关键是不先把Key解码成十六字节再拼接。
上述例子的输出为 s3pPLMBiTxaQ9kYGzzhZRbK+xOo=,服务器成功响应如下:
text
HTTP/1.1 101 Switching Protocols\r\n
Upgrade: websocket\r\n
Connection: Upgrade\r\n
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=\r\n
\r\n客户端要核对101、Upgrade中的websocket、Connection中的Upgrade token,以及根据自己Key算出的Accept;不能只见到HTTP成功类别就开放帧解释器。普通200响应可以是网页或错误入口,不构成RFC 6455握手成功。成功头部结束后,双方才按WebSocket帧处理后续字节。RFC 6455 §4.1—4.2。
Accept不能用于验证服务器的密码学身份,因为任何知道公开Key的人都能算出它;Key也不能当作客户端密码。此流程主要避免无意跨协议接受,要求对端明确参与。若需要确认网站或代理服务身份并保护通道,wss依赖TLS;若要确认用户身份,还需适用的认证机制。
Origin说明浏览器中发起连接的页面来源。服务器可以据此决定是否允许该网页来源使用服务,防止别的站点借助浏览器已附带的身份状态发起不期望的会话。但非浏览器客户端能够自己构造Origin,所以Origin校验不是面对任意客户端的身份认证替代品。浏览器的受控行为与恶意原生客户端,属于不同攻击者模型。
握手还可以协商子协议和扩展。Sec-WebSocket-Protocol选择应用层消息约定;Sec-WebSocket-Extensions协商会改变帧处理的能力。服务器不得凭空选择客户端未提出的子协议或扩展,客户端也不能因自己支持某项功能就在未协商时使用。否则双方对后续保留位和负载的解释会不同。
中间HTTP代理需要正确转接升级语义。有的入口终止握手并另建后端WebSocket,有的把升级后的字节流转送;两者对握手字段、TLS端点和日志的处理不同。若某一跳只返回普通网页,即便GET本身得到了响应,WebSocket仍未建立。反向代理的服务路由、握手验证与长连接转发,需要作为连续路径判断。
10.3 帧头、扩展长度与掩码:从两个字节开始解析
WebSocket帧的基础头占两字节,后面是否追加长度与掩码,由基础头中的位决定。这里的位宽是真正的二进制位,不是字符 0 或 1;图中的高位先标在左侧:
text
第0字节:FIN(1) RSV1(1) RSV2(1) RSV3(1) Opcode(4)
第1字节:MASK(1) Payload-Len(7)
后续:可选扩展长度 → 可选4字节Masking-Key → Payload| 字段 | 作用 |
|---|---|
| FIN | 本帧是否结束当前消息,首帧也可以是末帧 |
| RSV1—3 | 扩展保留位,未协商相应意义时必须为零 |
| Opcode | 0续帧、1文本、2二进制、8关闭、9Ping、aPong |
| MASK | 是否带四字节掩码键 |
| Payload-Len | 小长度本身,或扩展长度的标志 |
Payload-Len低七位为0—125时,值本身就是负载字节数;为126时,后接两字节无符号大端长度;为127时,后接八字节大端长度,最高位必须为零。必须使用能表示实际长度的最短形式,不能把五字节负载也故意编码成长长度形式。RFC 6455 §5.2。
| 实际负载N | 基础头后的长度字段 | 不含掩码的总头长 |
|---|---|---|
| 0—125 | 无 | 2 |
| 126—65535 | 2字节 | 4 |
| 65536至规定上界 | 8字节 | 10 |
理论可表示范围不代表实现必须接受巨大帧。接收方要有自身帧、消息和累计分片上限,在申请内存前验证长度,避免整数溢出或资源耗尽。Payload长度计算扩展数据与应用数据,不包括掩码键本身;没有协商扩展时,负载才直接等于本帧应用数据。
客户端到服务器的所有帧必须掩码,服务器到客户端的帧则不得掩码;这些角色规则不因wss已有TLS而取消。客户端每帧选择新的不可预测四字节掩码键,接收方按帧内负载偏移循环使用它。
掩码采用逐位异或,记作⊕:相同位异或得0,不同位得1。若原始负载为P,键为K,则第i字节线上值为:
再异或同一个键字节,就恢复P,因为 x⊕k⊕k=x。键本身明文放在帧里,所以这不是保密加密;观察者可执行与接收者相同的恢复。它用于限制不可信客户端应用直接决定线上字节形态,缓解某些中间基础设施把数据误认成其它协议的风险。随机性必须按协议实现,不能因“不是加密”就省略。RFC 6455 §5.3、§10.3。
RFC的文本 Hello 示例使用键 37 fa 21 3d:
| i | 原字节 | 使用键字节 | 异或结果 |
|---|---|---|---|
| 0 | 48,H | 37 | 7f |
| 1 | 65,e | fa | 9f |
| 2 | 6c,l | 21 | 4d |
| 3 | 6c,l | 3d | 51 |
| 4 | 6f,o | 37 | 58 |
客户端完整帧为:
text
81 85 37 fa 21 3d 7f 9f 4d 51 5881是FIN=1、RSV全零、Opcode=1;85是MASK=1和长度5。随后四字节键、五字节掩码负载,总长十一。若服务器发送同样内容,完整帧为 81 05 48 65 6c 6c 6f,总长七,没有四字节键。这个公开键只是标准向量,不是实际连接应复用的固定值。
分次读取时,i以本帧负载起点为基准,而非以本次Socket读取起点为基准。若首批恢复两字节,下一批首字节应继续使用K的第2字节;每次读取都从K的第0字节重新开始,会在特定分块下损坏内容。下一帧则重新使用自己的键和i=0,不能把上帧偏移带过去。
解析顺序因此是:收齐基础头并检查角色、操作码及保留位;按标志读取扩展长度并检查上限;需要时收齐键;再在限定范围内恢复负载。不能看见两个头字节就把后面所有缓冲交给应用,因为其中可能仍有长度和键,也可能还包含下一个帧。
10.4 消息分片:续帧维持语义,控制帧可以插入
WebSocket消息可以放在一个FIN=1的数据帧,也可以拆成多个帧。无扩展的基本分片序列以FIN=0的文本或二进制帧开始,中间使用FIN=0、Opcode=0的续帧,最后用FIN=1的续帧结束。文本或二进制类型由首帧决定,续帧不重新选择消息类型。
例如服务器把 Hello 分成 Hel 与 lo,中间插入一个内容为问号的Ping。服务器方向的三个完整帧可以是:
text
01 03 48 65 6c
89 01 3f
80 02 6c 6f| 帧 | 字段解释 | 对消息的影响 |
|---|---|---|
01 03 ... | FIN=0、文本、长度3 | 开始消息并交付或累计Hel |
89 01 3f | FIN=1、Ping、长度1 | 独立控制事件,不加入文本消息 |
80 02 ... | FIN=1、续帧、长度2 | 加上lo并结束消息 |
三帧合计十二字节,文本消息仍是五字节 Hello,不是 Hel?lo。接收方必须在处理Ping后保留“已有文本分片尚未结束”的状态,继续接受续帧。把每帧都独立转换成应用消息,或者见到任意FIN=1就结束当前文本消息,都会误把控制帧的FIN作用到另一份状态上。RFC 6455 §5.4—5.5。
基本协议不允许在一个未结束的数据消息中,再用新的文本或二进制首帧开始另一条消息。控制帧可以交错,多个普通消息分片则不能任意交错;只有另有协商且明确定义这种能力的扩展,才能改变相应规则。没有消息进行时出现续帧,也是不合法状态。
这使解析器至少同时维护两类信息:当前帧的长度、键和已消费偏移,以及当前消息的类型、累计大小与是否仍在分片。帧完整不等于消息完整;控制事件可以在大消息尚未完成时处理,却不能把部分帧本身当作边界清楚的控制消息。
文本UTF-8验证也要按完整消息语义处理。字符“你”的三字节 e4 bd a0可以跨文本分片:第一帧只含 e4,续帧包含 bd a0。第一帧单独不是完整UTF-8文本,但累计消息有效。验证器可以流式保留未完成字符状态,到消息结束时确认没有非法或截断序列;不能每帧都无状态地要求独立解码成功。
二进制消息不要求UTF-8,但仍有类型和大小约束。接收端不一定要等全部分片到齐才把任何字节交给上层,能否增量交付由API和应用需求决定;即使增量处理,也不能放弃完整消息长度、分片状态和最终完成判断。无限多个小帧同样可能组成过大的消息,所以只有单帧上限并不能限制全部资源。
控制帧不得分片,负载最多125字节。这个限制便于在一条持续消息之间插入较短控制事件,但它并不让Ping越过已经开始传输的大帧。帧内部不能在任意负载位置塞进另一帧头;接收者仍要先消费当前帧的声明长度,才能识别下一个控制帧。因此大消息适度分片可能改善控制处理时机,实际延迟还受缓存和调度影响。
代理适配层若把多条WebSocket二进制消息恢复成一条内层字节流,还要决定如何处理这些消息边界。目标协议的定界来自它自己的头部和状态,而不该偶然依赖当前WebSocket帧切分。第10.6会把这个映射进一步展开。
10.5 Ping、Pong 与 Close:会话状态不同于业务完成
Ping和Pong为会话提供控制反馈。任一方在会话建立后、关闭之前都可发Ping;收到Ping且尚未收到Close的一方要按规则回应Pong,回应负载应与被回应的Ping一致。规范也允许尚未逐一回应多个Ping时只选择最近处理的一项,以及主动发送无需回应的Pong。RFC 6455 §5.5.2—5.5.3。
例如服务器发送 89 01 3f,表示一字节问号Ping。客户端回应Opcode=a的Pong,负载恢复后仍应是 3f,但客户端线上帧必须使用新的掩码键;不能直接把服务器三个字节改个操作码原样发回。角色的掩码义务也适用于控制帧。
Ping往返说明对应WebSocket端点仍能处理控制消息,不证明代理后的最终网站已经建立连接、通过TLS验证或完成业务。如果反向代理终止了WebSocket,会话的控制反馈也需要按实际终止点解释。它还会经过网络、队列和处理调度,因此测得的往返不是纯传播时间,更不是整条访问中所有目标的统一延迟。
Close则显式结束WebSocket会话。关闭帧Opcode=8、FIN=1,负载可以为空;若有内容,至少两字节为大端状态码,后面可以跟UTF-8原因。只有一字节负载是不合法的,因为无法容纳完整状态码。控制帧最多125字节,所以有状态码时原因最多123个编码后字节,而非123个字符。
服务器表达正常关闭可以发送:
text
88 02 03 e803 e8=1000,是正常关闭状态。客户端若用教学键 11 22 33 44 回应同一状态,一个相应帧为:
text
88 82 11 22 33 44 12 ca掩码后的 12 ca恢复为03 e8。这些键是公开构造值,实际实现仍要每帧新选不可预测值。接收者不能把掩码后的两字节直接解释为关闭状态。
| 状态 | 代表的协议含义示例 |
|---|---|
| 1000 | 正常结束 |
| 1002 | 协议错误 |
| 1007 | 负载数据与要求不一致,例如非法UTF-8 |
| 1008 | 策略约束 |
| 1009 | 消息过大 |
| 1011 | 服务器遇到阻止完成请求的意外条件 |
关闭状态也有只能本地表示的保留值。1005表示没有状态值、1006表示异常结束、1015与TLS握手失败相关,这些不能作为Close中的线上状态码发送。日志显示1006,不能据此推断对端真的发来了两字节1006;它常意味着没有取得正常关闭握手。RFC 6455 §7.4。
端点发出Close后不得继续发送数据帧;收到Close且尚未发送Close时,应回应关闭帧。双方都已发送并收到Close后,再结束底层TCP;规范要求服务器立即关闭,客户端通常等待服务器关闭,也可在合理条件下自行关闭。双方同时发起Close同样能够完成这一状态,不需要再无限互相回应。
底层突然断开,与完整Close交换不同。连接复位或传输丢失可能让正在接收的帧或消息截断,应用不能把“没有更多字节”当作前一消息已完整。错误场景下未必还适合发送Close;协议要求失败时应按具体错误状态终止,不能在已经判定失败后继续尝试解释不可信后续数据。
WebSocket关闭也不直接等同于TCP的独立半关闭服务。基础Close针对会话结束,不提供一个通用“我已结束发送业务、但继续等待任意长返回”的代理命令。如果内层代理需要精确保留方向结束语义,适配层必须明确怎样表达,不能把Close未经分析就当成所有内层EOF的等价映射。
10.6 从二进制消息恢复代理字节流
WebSocket向应用提供消息,许多代理协议却需要一条连续字节流。把二者连接起来的适配层,需要约定“怎样把字节放进消息,以及怎样从消息恢复字节”,而不是让WebSocket自动识别代理请求。本节讨论一种常见的流映射模型,不把它当成RFC强制的代理功能。
设发送者依次产生字节序列S,把它分成若干二进制消息M1、M2……,接收适配层按顺序把各消息的应用数据串接,恢复 S=M1∥M2∥…。WebSocket负责帧与消息完整性,适配层负责这个串接契约;内层代理解析器仍依据自己的字段定界。
沿用第3章十三字节教学流:
text
00 05 48 45 4c 4c 4f 00 04 ff 00 01 0a它含一条五字节 HELLO 和一条四字节二进制内容,各自以两字节长度前缀定界。可以把前八字节放入第一条WebSocket二进制消息,后五字节放入第二条:
| WebSocket消息 | 应用数据 | 内层解析结果 |
|---|---|---|
| M1,8字节 | 00 05 48 45 4c 4c 4f 00 | 第一条内层消息完整;保留下一长度前缀的首个00 |
| M2,5字节 | 04 ff 00 01 0a | 与已保留00组合出长度4,恢复第二条内容 |
若接收方错误地要求每条WebSocket消息恰好容纳一条完整内层请求,M1尾部会被判成垃圾、M2开头会被误当新头。两端可以另行设计消息对齐约定,但那是附加协议;在本文的流映射模型里,内层边界不依赖外层消息大小。
每条消息又能分成多个WebSocket帧,底层TCP还可以把帧任意拆分读取。完整恢复路径因而是TCP累计字节→WebSocket帧解析与去掩码→消息状态处理→适配后的内层字节流→代理请求解析。若有协商压缩,还要在正确位置解码扩展。任何一层都不能用自己的读取次数代替下一层的消息边界。
选择二进制消息的直接好处是允许任意字节,不必把密钥材料或目标数据强行解释成UTF-8。若改用文本消息承载Base64,两端必须明确约定解码,而且会增加编码量与处理;WebSocket本身并不要求所有二进制负载都先Base64。无理由的文本转换既不增加保密,也可能破坏原始字节。
面向数据报的映射可以采用另一种契约,例如一条二进制消息对应一个内层数据报,并在内部保留必要目标信息。此时不能无条件把所有消息串接,否则丢失报文边界。相同WebSocket承载可以服务流或数据报,但必须由上层协议定义,不能从“二进制消息”四字推断业务语义。
适配还涉及缓存。消息API可能先把完整消息排进用户态队列,再异步写向网络;反方向可能要求完整消息到齐才交付。它们不一定像一个简单TCP读写循环那样自然传递背压。实现需要限制单帧、单消息、未发送队列和累计内层输入,并定义关闭时怎样处理尚未交付内容。
安全边界同样取决于终止点。如果反向代理只转送升级后的字节,最终服务解释WebSocket;如果它自己终止WebSocket再生成另一条后端会话,它便能取得本层应用负载。外部wss只保护其TLS端点之间;里面若还有网站TLS或独立代理加密,就要继续追踪内层保护,而不能由一个wss地址推断所有节点都看不到业务。
实际Xray的WebSocket包装器怎样把消息映射成流,将在第20章结合固定源码定位。本节先建立正确的分析问题,避免把特定库的消息大小、写入策略和关闭行为当成WebSocket标准本身。
10.7 帧开销怎样随长度变化
没有扩展时,WebSocket每帧线上长度等于负载N加头部h。h由基础头、扩展长度和客户端掩码键组成,不包含HTTP握手、TLS记录、TCP/IP或链路开销。令e(N)在N≤125时为0,126—65535时为2,更大合法长度时为8,则:
| 负载N,字节 | 客户端头长 | 客户端总长 | 服务器头长 | 服务器总长 |
|---|---|---|---|---|
| 0 | 6 | 6 | 2 | 2 |
| 5 | 6 | 11 | 2 | 7 |
| 125 | 6 | 131 | 2 | 127 |
| 126 | 8 | 134 | 4 | 130 |
| 65535 | 8 | 65543 | 4 | 65539 |
| 65536 | 14 | 65550 | 10 | 65546 |
零长度客户端帧仍有四字节键,不能因没有负载就省略。表格里的大长度只展示格式阈值,实际端点可以设置远小于理论范围的上限。
一个完整三字节二进制消息 abc,客户端使用教学键 11 22 33 44,可构造:
text
82 83 11 22 33 44 70 40 5082表示单帧完成的二进制消息,83为带掩码的三字节长度;70 40 50依次恢复为 61 62 63。服务器发送同样二进制消息为 82 03 61 62 63。二者分别九字节与五字节,差别就是客户端方向的四字节键。
长度标记也有跳变。126字节的服务器二进制帧,头部是 82 7e 00 7e,后面才有126字节负载;65536字节时,头部为 82 7f 00 00 00 00 00 01 00 00,后面才接负载。这两行仅展示完整头部,不冒充已经列出整帧内容。扩展长度按大端计算,并且不能用127形式表达本可用126形式容纳的长度。
对于五字节客户端消息,单帧头相对负载是 6/5=120%;对16 KiB负载,头为八字节,相对开销约 8/16384=0.0488%。同一协议对极小消息与大块传送呈现的比例非常不同,不应把一个数字当作所有负载的固定开销率。
主动分片会增加头部次数,但也可能改变长度档位。126字节若在客户端拆成两个63字节帧,头总长十二字节,比一个126字节帧的八字节多四;在服务器方向,两个两字节头刚好与一个四字节头一样大。若要比较更复杂分片,必须对每帧重新选e(N),而非简单用最大头长乘帧数。
分片还影响处理时机与缓存,而不只是字节数。很大的帧可以摊薄头部,却让控制帧需要等待当前帧传完;许多很小帧增加解析、调度和掩码键生成次数。哪种更合适取决于写入模式、端点API和控制延迟需求,没有一个帧大小能无条件最优。
掩码对负载逐字节变换,实现可以批量或向量化处理,是否额外分配与复制又取决于缓冲设计。协议规定了线上结果,不规定必须调用多少次系统调用、复制多少遍内存。把“需要掩码”直接换算成一个固定CPU百分比,或者把一帧等同于一次send,都是缺少实现和负载条件的推断。
因此本表只给出可由格式精确推出的成本。握手等待、TLS处理、网络拥塞与实际吞吐要在完整路径里继续计算。第30章会用相同计量原则把不同代理与承载放到可比较的条件下。
10.8 扩展、压缩与中间设备:同一种帧结构还有协商条件
WebSocket基础头留出RSV位,但这些位不是应用可以任意使用的私人空间。非零值需要握手中已经协商的扩展来定义;接收方不认识其含义时要按协议失败,不能忽略一切保留位后继续把负载交给代理解析器。
一个实际例子是RFC 7692的 permessage-deflate。它以消息为单位应用DEFLATE压缩,用RSV1标记消息经过该扩展处理;消息分片时,标记位在首个数据帧上,续帧按已经确定的消息状态解释。控制帧不因此变成压缩消息的一部分。压缩在消息与帧负载之间引入新步骤,不能等同于HTTP的Content-Encoding字段。RFC 7692 §7。
DEFLATE能够利用先前出现的字节模式,用较短的表示引用重复内容。编码器与解码器因而维护历史窗口。扩展可以协商是否在消息之间保留这份历史,称为context takeover;也可以约束窗口大小。保留历史可能提高重复消息的压缩效果,却需要跨消息状态与相应内存。关闭上下文接管,则让新消息不再依赖前一个消息的历史。
参数还区分方向。server_no_context_takeover限制服务器发送的新压缩消息如何初始化历史;client_no_context_takeover限制客户端方向。应按最终接受的参数判断,不能只因为客户端提议中出现某项,就假定协商结果已经包含它。接收方的解压状态必须与对方实际允许使用的历史一致。
这份扩展也不是把一个完整gzip文件塞进负载那么简单。RFC 7692规定压缩消息结尾的特殊处理:发送表示中移除相应尾部四字节 00 00 ff ff,接收者在消息结束时按规范补回,再执行DEFLATE解码。分片中的非末部分不能各自重复做消息结尾操作。掩码应用于实际帧负载,接收端先去掩码,再按扩展恢复消息数据。RFC 7692 §7.2。
所以10.7的N指线上帧负载长度:启用扩展后,它未必等于原始业务内容长度。解压后的消息还应有独立上限;只限制一帧输入大小,不能阻止一个短压缩输入产生很大输出。压缩也需要CPU和状态,不能仅看节省的运输字节。
是否值得压缩还依赖内容。重复文本可能从历史引用受益,已经压缩的文件或经过良好加密的记录通常没有同样的可利用冗余;具体收益必须在负载条件下判断。若受攻击者影响的数据与秘密共享压缩上下文,压缩后长度还可能成为侧信道,安全传输本身并不消除这个问题。RFC 7692 §8明确提醒压缩与安全通道组合的相关风险。
中间设备兼容性又与这些端点功能分开。反向代理需要正确支持升级及升级后的长连接;它可能配置空闲超时、帧或消息大小限制,也可能在终止后用另一套参数建立后端会话。HTTP握手成功只确认当前会话被接受,不证明某个业务的所有尺寸、持续时间和扩展组合都已兼容。
WebSocket因此适合提供一种受控双向消息承载,但不承诺不可识别、无限长寿命或无额外成本。握手路径、固定URI、方向掩码和控制节奏都可能是可观察特征;是否适合某条代理路径,应结合端点、网关与内层协议分析,而不是把“HTTP升级”理解成任何网络环境都接受的保证。
本章的基础机制依 RFC 6455 §4—7、§10,压缩扩展依 RFC 7692 §7—8。后面HTTP/2会提供不同的多流承载,理解它之前,必须继续保留本章建立的区别:消息类型不等于帧类型,扩展解码不等于应用解析,外层安全也不等于所有终止节点都无法读取负载。