視頻質量統計系統,需支持 10W+ TPS。選 Netty 因:異步非阻塞模型、輕量無容器開銷、資源佔用優於 Tomcat、官方宣稱 HTTP 支持完善。
GitHub 實戰項目:netty-http,已實現 GET/POST 解析、三種 Content-Type 處理、文件上傳、自定義 Decoder。
GET /test?name=XXG&age=23 HTTP/1.1 Host: localhost:8081 ...Headers...
參數在 URI 中,無 Message Body。
POST /test HTTP/1.1 Content-Type: application/x-www-form-urlencoded Content-Length: 27 name=XXG&age=23
參數在 Body 中,需 Content-Length 指定長度。
HttpRequestDecoder:ByteBuf → HttpRequest/HttpContentHttpResponseEncoder:HttpResponse/HttpContent → ByteBufHttpServerCodec:上述兩者組合HttpObjectAggregator:將 HttpMessage + HttpContent 聚合為 FullHttpRequest/Response,解決分片傳輸與 POST Body 解析問題Pipeline 配置:
ch.pipeline().addLast("codec", new HttpServerCodec())
.addLast("aggregator", new HttpObjectAggregator(65536));
QueryStringDecoder decoder = new QueryStringDecoder(uri, CharsetUtil.UTF_8); Map> params = decoder.parameters();
FullHttpRequest fullReq = (FullHttpRequest) msg;
// JSON
String json = fullReq.content().toString(CharsetUtil.UTF_8);
JSONObject obj = JSON.parseObject(json);
// form-urlencoded:QueryStringDecoder 或 HttpPostRequestDecoder
HttpPostRequestDecoder decoder = new HttpPostRequestDecoder(factory, request, CharsetUtil.UTF_8);
for (InterfaceHttpData data : decoder.getBodyHttpDatas()) {
if (data.getHttpDataType() == HttpDataType.Attribute) {
Attribute attr = (Attribute) data;
System.out.println(attr.getName() + "=" + attr.getValue());
}
}
// multipart 文件上傳
DiskFileUpload.baseDirectory = "/data/fileupload/";
for (InterfaceHttpData data : decoder.getBodyHttpDatas()) {
if (data.getHttpDataType() == HttpDataType.FileUpload) {
FileUpload file = (FileUpload) data;
if (file.isCompleted()) file.renameTo(new File(baseDir + file.getFilename()));
}
}
public class HttpJsonDecoder extends MessageToMessageDecoder{ protected void decode(ChannelHandlerContext ctx, HttpRequest msg, List
使用:.addLast("jsonDecoder", new HttpJsonDecoder()),後續 Handler 直接接收 JSONObject。
public class HttpProtobufDecoder extends MessageToMessageDecoder{ private final MessageLite prototype; public HttpProtobufDecoder(MessageLite prototype) { this.prototype = prototype.getDefaultInstanceForType(); } protected void decode(ChannelHandlerContext ctx, HttpRequest msg, List
Netty 4+ 使用引用計數管理 ByteBuf 生命周期,非 GC。Inbound Handler 必須在 finally 中 release():
public void channelRead(ChannelHandlerContext ctx, Object msg) {
try { ... } finally { ReferenceCountUtil.release(msg); }
}
或繼承 SimpleChannelInboundHandler 自動釋放。啟用洩漏檢測:-Dio.netty.leakDetectionLevel=advanced。
Nginx + Netty,Nginx 配置 proxy_http_version 1.1; proxy_set_header Connection ""; 開啟 Keep-Alive。Netty 響應必須:
ctx.writeAndFlush(resp).addListener(ChannelFutureListener.CLOSE);Content-Length 頭,不關閉連接,由客戶端按長度讀取private void writeResponse(Channel ch, HttpResponseStatus status, String msg, boolean forceClose) {
ByteBuf buf = Unpooled.wrappedBuffer(msg.getBytes());
FullHttpResponse resp = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, status, buf);
boolean close = isCloseRequest(); // 判斷 Connection: close 或 HTTP/1.0 無 Keep-Alive
if (!close && !forceClose) {
resp.headers().set(HttpHeaderNames.CONTENT_LENGTH, buf.readableBytes());
}
ChannelFuture f = ch.write(resp);
if (close || forceClose) f.addListener(ChannelFutureListener.CLOSE);
}
childOption(ChannelOption.SO_KEEPALIVE, true) 僅開啟 TCP 層,不等於 HTTP 長連接