夜雨聆风学习资料网

ARTICLE · 1177319

Netty HTTP/1.1 编解码源码:请求如何在 Pipeline 中被拆装(源码系列第17篇)

Netty HTTP/1.1 编解码源码:请求如何在 Pipeline 中被拆装(源码系列第17篇)

Netty 不会天然把一个 HTTP 请求变成一个 FullHttpRequest。HttpServerCodec 的入站 Decoder 按状态机产出 HttpRequest、若干 HttpContent 和 LastHttpContent,出站 Encoder 再把响应对象编码成字节;HttpObjectAggregator 只是可选的聚合层,用内存换取完整消息接口,并且必须设置上限。

HTTP/1.1 建立在 TCP 字节流上,同样存在半包、粘包和一次 read 包含多条消息的问题。除此之外,它还有请求行、Header、固定长度 Body、Chunked Body、100-continue、keep-alive 和 pipelining 等协议状态。一个健壮的 HTTP Codec 必须把这些状态逐步解析,不能假设一次 Socket read 就是一条请求。

源码基线:

  • • 项目:Netty
  • • 版本:netty-4.1.135.Final
  • • Commit:f05f765d81460799c53123a207f665bf3b465171
  • • 重点模块:codec-http
  • • 重点类与方法:HttpServerCodec、HttpObjectDecoder.decode、readHeaders、HttpObjectEncoder.encode
  • • 官方源码:https://github.com/netty/netty/tree/netty-4.1.135.Final/codec-http/src/main/java/io/netty/handler/codec/http

这张图展示了双向结构:入站字节先经过 HttpRequestDecoder,按对象流传播;可选 Aggregator 把多个对象聚成完整请求;业务返回 HttpResponse 和 Content,出站 HttpResponseEncoder 逆向编码。入站与出站共用一个 HttpServerCodec 外壳,但内部是两个 Handler。

1. HttpServerCodec 是入站 Decoder 与出站 Encoder 的组合

HttpServerCodec 继承 CombinedChannelDuplexHandler。构造时把请求解码器和响应编码器组合起来:

public HttpServerCodec(HttpDecoderConfig config) {    init(new HttpServerRequestDecoder(config),         new HttpServerResponseEncoder());}

这说明服务端两个方向的职责不同:

  • • 入站:ByteBuf -> HttpRequest/HttpContent;
  • • 出站:HttpResponse/HttpContent -> ByteBuf。

Pipeline 的方向规则仍与第 7 篇一致。请求从 Head 向 Tail 找入站 Handler,响应从当前 Context 向 Head 找出站 Handler。业务若在 Codec 之前调用 ctx.write,可能跳过响应 Encoder;初始化时应让协议 Codec 位于业务 Handler 前面。

2. Decoder 首先解析 initial line,再进入 Header 状态

HttpObjectDecoder 继承 ByteToMessageDecoder,内部状态机大致包括:

SKIP_CONTROL_CHARS  -> READ_INITIAL  -> READ_HEADER  -> READ_FIXED_LENGTH_CONTENT  -> READ_VARIABLE_LENGTH_CONTENT  -> READ_CHUNK_SIZE  -> READ_CHUNKED_CONTENT  -> READ_CHUNK_DELIMITER  -> READ_CHUNK_FOOTER

decode() 在 READ_INITIAL 阶段解析请求行:

METHOD SP URI SP VERSION CRLF

例如:

POST /orders HTTP/1.1

输入不足时状态机保留当前进度,等待下次 ByteBuf 累积。解析成功后创建 HttpMessage,再调用 readHeaders(buffer) 读取 Header。它不是对整段字符串做一次 split,而是受长度上限约束地增量解析。

3. Header 结束后,消息体边界由协议字段决定

Header 解析完成后,Decoder 根据状态和字段选择 Body 读取方式:

条件
解码路径
产出
没有 Body
直接结束消息
HttpRequest
 + LastHttpContent
Content-Length: N
固定长度读取
一个或多个 HttpContent
Transfer-Encoding: chunked
Chunk 状态机
多个 Chunk + Last
连接关闭界定
可变长度读取
直到关闭

Content-Length 与 Transfer-Encoding 的组合必须严格校验,否则容易形成请求走私:前置代理和后端若对边界理解不同,同一串字节会被切成不同请求。实际部署必须保持 Netty 版本和代理安全策略更新,并避免在业务 Handler 中自行“修复”模糊报文。

4. Decoder 产出的是对象流,不保证完整请求

默认情况下,一次带 Body 的请求在 Pipeline 中类似:

HttpRequestHttpContentHttpContentLastHttpContent

这样设计能流式处理大 Body,避免等待整条消息进入内存。文件上传、代理转发和大模型流式输入都更适合逐块消费。

业务 Handler 必须管理对象生命周期:HttpContent 持有引用计数 ByteBuf,消费后需要释放,转交下游则由下一层接管。使用 SimpleChannelInboundHandler<HttpObject> 可以在约定范围内自动释放,但若 retain 后异步使用,仍要确保最终 release。

5. HttpObjectAggregator 用便利换内存

加入:

pipeline.addLast(new HttpServerCodec());pipeline.addLast(new HttpObjectAggregator(1024 * 1024));pipeline.addLast(new BusinessHandler());

业务将收到 FullHttpRequest,Header 和完整 Body 在一个对象中,普通 JSON API 编程更简单。但聚合意味着:在完整 Body 到达之前,连接持有累积内存。

maxContentLength 是安全边界,不是随手写个很大数字。估算总风险:

峰值聚合内存 ≈ 并发上传连接数 × 单连接允许 Body 上限

如果 1 万连接都允许聚合 10MB,理论风险远超常规堆和直接内存容量。大文件应流式落盘或转发,不应进 Aggregator。

6. 解码限制是协议防线,不只是性能参数

HttpDecoderConfig 可以配置请求行、Header、Chunk 等限制。至少关注:

  • • maxInitialLineLength:限制 URI 和请求行;
  • • maxHeaderSize:限制全部 Header;
  • • maxChunkSize:控制单次产出的 Content 大小;
  • • Header 校验与重复 Content-Length 策略;
  • • Chunk 扩展与 Trailer 限制。

限制过小会拒绝合法请求,过大则给慢速攻击和内存消耗留下空间。应根据网关规范、业务最大 Cookie/JWT、追踪 Header 和上传协议设定,并在边缘代理与 Netty 服务保持一致。

7. Encoder 需要知道响应是否包含 Body

出站通常写:

FullHttpResponse response = new DefaultFullHttpResponse(    HTTP_1_1, OK, content);response.headers().set(CONTENT_LENGTH, content.readableBytes());ctx.writeAndFlush(response);

HttpResponseEncoder 根据状态码、方法语义和 Header 决定是否编码 Body,以及采用固定长度还是 chunked。HEAD、204、304 等响应不能简单按普通 Body 处理。

HttpServerCodec 的 request decoder 还会记录请求方法队列,响应 encoder 可据此正确处理 HEAD 等语义。HTTP/1.1 同一连接上的请求和响应顺序不能随意打乱,否则方法与响应边界会错配。

8. Keep-Alive 依赖消息边界与连接策略共同成立

HTTP/1.1 默认倾向持久连接,但前提是双方能准确知道一条消息何时结束。若响应既没有 Content-Length,也没有 chunked,往往只能靠关闭连接界定 Body,连接就无法继续复用。

服务端通常:

boolean keepAlive = HttpUtil.isKeepAlive(request);if (keepAlive) {    response.headers().set(CONNECTION, KEEP_ALIVE);    ctx.writeAndFlush(response);} else {    ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);}

关闭动作绑定写 Future,确保响应有机会先发出。若直接 ctx.close(),缓冲中的最后响应可能被截断。

9. HTTP Pipelining 仍要求响应顺序

客户端可以在前一个响应返回前发送后续请求。Decoder 能连续产出多个请求,但业务若把请求并发分发到线程池,完成顺序可能改变。

HTTP/1.1 同连接响应必须保持请求顺序。解决方式包括:

  • • 同连接串行处理;
  • • 为请求编号,完成后按序写回;
  • • 限制或禁用 pipelining;
  • • 使用 Netty 对应的 pipelining Handler;
  • • 在协议层升级到支持多路复用的 HTTP/2。

只保证 EventLoop 上 write 串行,不代表业务异步结果按请求顺序回来。顺序控制必须显式设计。

图中第一次 read 可能只有半条请求行,Decoder 不产出对象;后续字节补齐 Header 后先产出 HttpRequest,再逐块产出 Content,LastHttpContent 标记消息边界。响应对象沿出站方向经过 Encoder,最终才成为 Socket 字节。

10. 慢请求会同时占连接、定时器与累积内存

攻击者可以非常慢地发送请求行、Header 或 Body,让连接长期占用资源。仅设置最大长度不够,因为对方可以永远不达到上限。

Pipeline 通常还需要:

  • • 读超时/请求头超时;
  • • Header 和 Body 长度上限;
  • • 单 IP 连接和并发请求限制;
  • • Aggregator 上限或流式处理;
  • • 写侧背压;
  • • 全局有界业务队列。

IdleStateHandler 可以提供底层读空闲信号,但“请求头必须在 5 秒内完整到达”是更具体的协议 deadline,应由 HTTP 层定时器或网关策略实现。

11. HTTP 排障应沿对象边界定位

现象
观察点
常见根因
业务收不到请求
Decoder 是否产出 HttpRequest
请求行/Header 不完整或超限
收到 Request 没有 Body
是否继续处理 HttpContent
误以为默认 FullHttpRequest
直接内存增长
Aggregator/Content 是否释放
慢 Body 或引用计数泄漏
Keep-Alive 第二请求失败
前一响应边界是否完整
Content-Length/LastContent 错误
响应顺序错
是否异步并发处理 pipelining
缺少按连接排序
400/413 激增
Decoder/Aggregator 限制
客户端异常或攻击流量

抓包能证明线上字节,Pipeline 日志能证明对象传播,两者结合才能判断问题在网络边界、Codec 还是业务 Handler。

12. HTTP Codec 的核心是把字节流变成受约束的对象流

主链如下:

Socket ByteBuf  -> HttpRequestDecoder state machine  -> HttpRequest  -> HttpContent...  -> LastHttpContent  -> optional HttpObjectAggregator  -> BusinessHandler  -> HttpResponse / HttpContent  -> HttpResponseEncoder  -> ByteBuf

完整请求接口很方便,但流式对象才是 Netty 默认模型。选择是否聚合,本质是在代码简单度、内存上限与大消息能力之间取舍。

下一篇在 HTTP Pipeline 上继续前进:WebSocket 握手成功后,Netty 会动态替换 HTTP Codec,把同一条 TCP 连接从 HTTP 对象流切换成 WebSocket Frame 流。协议升级真正改变的是 Pipeline 状态,而不是创建一条“特殊网络连接”。

参考资料

  1. 1. Netty HttpServerCodec.java:https://github.com/netty/netty/blob/netty-4.1.135.Final/codec-http/src/main/java/io/netty/handler/codec/http/HttpServerCodec.java
  2. 2. Netty HttpObjectDecoder.java:https://github.com/netty/netty/blob/netty-4.1.135.Final/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectDecoder.java
  3. 3. Netty HttpObjectAggregator.java:https://github.com/netty/netty/blob/netty-4.1.135.Final/codec-http/src/main/java/io/netty/handler/codec/http/HttpObjectAggregator.java
  4. 4. RFC 9112 HTTP/1.1:https://www.rfc-editor.org/rfc/rfc9112

相关学习资料