Skip to content

HTTP server implementation with Netty

Implementing an HTTP server using [[Netty]] involves configuring an EventLoop group, setting up a ServerBootstrap, and defining a channel pipeline to handle HTTP-specific encoding and decoding.^[600-developer-big-data-netty-netty-01.md]

Core Components

EventLoop Groups

A standard Netty server implementation utilizes two EventLoopGroup instances.^[600-developer-big-data-netty-netty-01.md] * Boss Group: Responsible for accepting incoming connections. * Worker Group: Handles the traffic of accepted connections.

Both groups are typically instantiated using NioEventLoopGroup, which manages the IO operations and event processing.^[600-developer-big-data-netty-netty-01.md]

Channel Configuration

The server is configured using a ServerBootstrap helper class.^[600-developer-big-data-netty-netty-01.md] Key configuration steps include: 1. Group Assignment: Passing the boss and worker groups to the bootstrap. 2. Channel Type: Specifying NioServerSocketChannel.class as the channel implementation. 3. Handler Registration: Setting a childHandler to initialize the channel pipeline for each new connection.

Channel Initialization

The ChannelInitializer is used to configure the ChannelPipeline.^[600-developer-big-data-netty-netty-01.md] For an HTTP server, the pipeline typically requires: 1. HttpServerCodec: A combined codec that acts as both a decoder for requests and an encoder for responses. 2. Custom Handler: A user-defined handler, often extending SimpleChannelInboundHandler<HttpRequest>, to contain the business logic.

Request Handling Logic

The core logic for processing a request resides in the custom handler.^[600-developer-big-data-netty-netty-01.md]

  1. Read Request: Override channelRead0 to receive the incoming HttpRequest.
  2. Construct Response: Create a FullHttpResponse using DefaultFullHttpResponse.
    • Set the HTTP version (e.g., HTTP_1_1) and status (e.g., OK).
    • Populate the content, typically using Unpooled.copiedBuffer to wrap data like strings into a ByteBuf.
  3. Set Headers: Define Content-Type and Content-Length headers.
  4. Send Response: Use ctx.writeAndFlush(response) to send the data back to the client.
  5. Close Channel: Close the connection after the response is sent.

Server Lifecycle

The server is started by binding to a specific port (e.g., 8080) via serverBootstrap.bind().sync(), which blocks until the server is active.^[600-developer-big-data-netty-netty-01.md] To keep the server running and handle graceful shutdown, the application typically waits on the closeFuture() of the channel and finally invokes shutdownGracefully() on both event loop groups.^[600-developer-big-data-netty-netty-01.md]

Code Example

The following example demonstrates the structure of a basic HTTP server in Netty:

public class Netty01_Server {

    public static void main(String[] args) throws Exception {
        EventLoopGroup bossGroup = new [NioEventLoopGroup](<./nioeventloopgroup.md>)();
        EventLoopGroup workerGroup = new [NioEventLoopGroup](<./nioeventloopgroup.md>)();

        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap
                    .group(bossGroup, workerGroup)
                    .channel([NioServerSocketChannel](<./nioserversocketchannel.md>).class)
                    .childHandler(new MyChannelInitializer());

            [ChannelFuture](<./channelfuture.md>) [ChannelFuture](<./channelfuture.md>) = serverBootstrap.bind(8080).sync();
            [ChannelFuture](<./channelfuture.md>).channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

class MyChannelInitializer extends ChannelInitializer<SocketChannel> {

    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        [ChannelPipeline](<./channelpipeline.md>) pipeline = ch.pipeline();
        pipeline.addLast("HttpServerCodec", new HttpServerCodec());
        pipeline.addLast("MySimpleChannelInboundHandler", new MySimpleChannelInboundHandler());
    }
}

class MySimpleChannelInboundHandler extends SimpleChannelInboundHandler<HttpRequest> {

    @Override
    protected void channelRead0([ChannelHandlerContext](<./channelhandlercontext.md>) ctx, HttpRequest httpRequest)
            throws Exception {

        [ByteBuf](<./bytebuf.md>) content = Unpooled.copiedBuffer("Hello World", CharsetUtil.UTF_8);
        FullHttpResponse response =
                new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, content);
        response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain");
        response.headers().set(HttpHeaderNames.CONTENT_LENGTH, content.readableBytes());

        ctx.writeAndFlush(response);
        ctx.channel().close();
    }
}
^[600-developer-big-data-netty-netty-01.md]

Sources

^[600-developer-big-data-netty-netty-01.md]