HttpServerCodec 仅负责HTTP协议层编解码,不聚合分片的HttpContent,需配合HttpObjectAggregator才能获得完整FullHttpRequest;后者将HttpRequest与HttpContent自动聚合成FullHttpRequest,且必须置于HttpServerCodec之后。

HttpServerCodec 是什么,为什么不能只加它就收得到完整请求
HttpServerCodec 只负责 HTTP 协议层的编解码:把字节流拆成 HttpRequest/HttpResponse,但默认不聚合分片的 HttpContent(比如大 Body、chunked 编码)。你直接在 channelRead 里拿到的可能是 HttpRequest + 若干个 HttpContent,甚至只有 HttpContent(没头),根本不是完整的 FullHttpRequest。
常见错误现象:
- instanceof FullHttpRequest 总是 false
- req.content().readableBytes() 返回 0,但明明 POST 了 JSON
- 日志里看到一堆 DefaultHttpContent 和 LastHttpContent
- 必须搭配
HttpObjectAggregator才能自动把多个消息聚合成FullHttpRequest -
HttpObjectAggregator的构造参数是最大聚合字节数,设太小会抛TooLongFrameException;设太大有内存风险,一般 64KB~1MB 足够 - 顺序不能错:
HttpServerCodec必须在HttpObjectAggregator前面,否则解码器看不到原始HttpContent
怎么写一个能稳定读取 body 的 ChannelInboundHandler
拿到 FullHttpRequest 后,body 内容才真正可用。但别直接调 content().toString(CharsetUtil.UTF_8) —— 它不处理 content-length 缺失、transfer-encoding: chunked 等边界情况,而 FullHttpRequest 已经帮你处理好了。
- 用
req.content().isReadable()判断是否有 body,而不是靠 method == POST - 用
req.content().retain().toString(CharsetUtil.UTF_8)读字符串(retain()防止被后续 handler 释放) - 如果要解析 JSON,推荐用
ByteBufInputStream包一层再交给 Jackson:new ObjectMapper().readValue(new ByteBufInputStream(req.content()), MyDto.class)
- 别忘了手动释放:
req.release(),否则内存泄漏(尤其在异常分支里容易漏)
GET 参数和 Header 怎么安全提取
Netty 不自动解析 URL 查询参数,FullHttpRequest 的 uri() 返回的是原始字符串(如 /api/user?id=123&name=foo),需要自己 parse。
- 用
QueryStringDecoder最省事:QueryStringDecoder decoder = new QueryStringDecoder(req.uri()); Map<String, List<String>> params = decoder.parameters();
- Header 大小写不敏感,但 Netty 内部用小写存储,所以用
req.headers().get("content-type")比get("Content-Type")更稳 - 注意
req.headers().get("cookie")返回的是原始字符串(a=b; c=d),不是 map,得用CookieDecoder解析 - 不要用
req.headers().entries()遍历取值——它返回的是视图,底层 key 是小写,value 可能被复用,多线程下不安全
为什么响应发不出去或浏览器卡住
常见原因是没设置 Content-Length 或没正确关闭连接。Netty 不像 Spring Boot 那样自动补 header,全靠你自己控制。
- 用
DefaultFullHttpResponse构造响应时,body 的ByteBuf必须和响应对象生命周期一致;推荐用Unpooled.copiedBuffer(...),避免引用外部 buffer - 记得显式设置
response.headers().setInt(HttpHeaderNames.CONTENT_LENGTH, response.content().readableBytes()) - 如果用 HTTP/1.0 或明确要关闭连接,加
response.headers().set(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE) - 别忘了写回 channel:
ctx.writeAndFlush(response),且确保在同一线程(通常就是 I/O 线程)执行,跨线程要ctx.executor().execute(...)
最常被忽略的一点:HttpObjectAggregator 会把请求转成 FullHttpRequest,但它不会自动把你的 FullHttpResponse 拆回 HttpResponse + HttpContent —— 这步由 HttpServerCodec 在出站时完成,所以只要 pipeline 顺序对,它就能工作。但如果中间加了自定义编码器或用了 write(new DefaultHttpResponse(...)) 这种非 full 类型,就可能断链。









