Why Choose Netty Over Tomcat for High‑Performance HTTP Servers
This article explains the motivations for using Netty's HTTP protocol stack instead of a traditional Tomcat web container, covering high‑concurrency support, lower resource consumption, detailed HTTP request/response handling, custom decoders, memory‑leak prevention, and keep‑alive connection management.
Recently I needed to build a video‑quality statistics system that collects data from PC, mobile, and web clients via a single HTTP request, parses it on the server, and performs real‑time and offline analysis.
Although the task could be handled by a simple Tomcat web project, I chose Netty because it naturally supports 100k+ TPS with its multithreaded, asynchronous, non‑blocking model, avoids the heavyweight Tomcat container, and offers better CPU, memory, and context‑switch usage.
Netty HTTP Basics
Understanding HTTP is essential: a request consists of a request line, headers, and an optional message body; a response includes a status line, headers, and a body. GET parameters are in the URI, while POST data resides in the message body.
Netty Built‑in HTTP Codecs
HttpRequestDecoder : decodes ByteBuf into HttpRequest and HttpContent.
HttpResponseEncoder : encodes HttpResponse or HttpContent into ByteBuf.
HttpServerCodec : combines the above two for easier server‑side implementation.
These codecs must be added to the ChannelPipeline, e.g.:
ch.pipeline().addLast("codec", new HttpServerCodec());For full POST handling, add HttpObjectAggregator to aggregate HttpMessage and HttpContent into a FullHttpRequest.
GET Request Parsing
Extract the URI and use QueryStringDecoder to split the path and parameters:
HttpRequest request = (HttpRequest) msg;
String uri = request.uri();
QueryStringDecoder decoder = new QueryStringDecoder(uri, Charsets.toCharset(CharEncoding.UTF_8));
Map<String, List<String>> params = decoder.parameters();POST Request Parsing
Convert the message to FullHttpRequest and handle different Content‑Types:
application/json : read the content bytes, convert to a string, and parse with JSON.parseObject.
application/x-www-form-urlencoded : reuse QueryStringDecoder on the body string.
multipart/form-data : use HttpPostRequestDecoder to obtain FileUpload objects and save them to disk.
FullHttpRequest full = (FullHttpRequest) msg;
String json = full.content().toString(Charsets.toCharset(CharEncoding.UTF_8));
JSONObject obj = JSON.parseObject(json);Custom Decoders
Implement a decoder by extending MessageToMessageDecoder and overriding decode. Example decoders include HttpJsonDecoder (produces a JSONObject) and HttpProtobufDecoder (produces a protobuf message).
public class HttpJsonDecoder extends MessageToMessageDecoder<HttpRequest> {
@Override
protected void decode(ChannelHandlerContext ctx, HttpRequest msg, List<Object> out) throws Exception {
FullHttpRequest req = (FullHttpRequest) msg;
String json = req.content().toString(Charsets.toCharset(CharEncoding.UTF_8));
out.add(JSON.parseObject(json));
}
}Common Pitfalls
Memory leaks : Netty uses reference‑counted objects (e.g., ByteBuf). Always release them in channelRead or use ReferenceCountUtil.release(msg). Failure to release leads to warnings like "ByteBuf.release() was not called before it's garbage‑collected".
HTTP keep‑alive : HTTP/1.1 defaults to persistent connections. If the server does not set Content‑Length or use chunked transfer encoding, the client cannot know when the response ends, causing long waits. Add the appropriate header or close the connection explicitly.
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);
if (!forceClose) {
resp.headers().set(HttpHeaderNames.CONTENT_LENGTH, buf.readableBytes());
}
ChannelFuture f = ch.writeAndFlush(resp);
if (forceClose) {
f.addListener(ChannelFutureListener.CLOSE);
}
}Understanding TCP keep‑alive vs. HTTP keep‑alive is also important: TCP keep‑alive probes the connection’s liveness, while HTTP keep‑alive allows multiple requests over the same TCP connection, reducing latency and resource usage.
Conclusion
The article provides a practical walkthrough of building a Netty‑based HTTP server, covering protocol basics, built‑in codecs, request parsing, custom decoders, and troubleshooting common issues such as memory leaks and persistent connections. The accompanying GitHub project ( https://github.com/cyfonly/netty-http ) demonstrates these concepts in a runnable example.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
21CTO
21CTO (21CTO.com) offers developers community, training, and services, making it your go‑to learning and service platform.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
