Netty is a framework which you can add to your developer’s stack to create event based non blocking applications. You can easily adapt it to handle a variety of Protocols. In this tutorial we will show how to create and run a simple HTTP Server with Netty.
Project set up
Hard requirements:
- Java 8 or higher on your machine
- Maven to build your project
Firstly, bootstrap a Maven project with your IDE or from the Command Line:
$ mvn archetype:generate -DarchetypeGroupId=org.apache.maven.archetypes -DarchetypeArtifactId=maven-archetype-quickstart -DarchetypeVersion=1.4
Next, add the following Java Class:
package io.netty.example; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.Channel; import io.netty.channel.ChannelOption; import io.netty.channel.EventLoopGroup; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.handler.logging.LogLevel; import io.netty.handler.logging.LoggingHandler; import io.netty.handler.ssl.SslContext; import io.netty.handler.ssl.SslContextBuilder; import io.netty.handler.ssl.util.SelfSignedCertificate; public final class HttpHelloWorldServer { static final boolean SSL = System.getProperty("ssl") != null; static final int PORT = Integer.parseInt(System.getProperty("port", SSL? "8443" : "8080")); public static void main(String[] args) throws Exception { // Configure SSL. final SslContext sslCtx; if (SSL) { SelfSignedCertificate ssc = new SelfSignedCertificate(); sslCtx = SslContextBuilder.forServer(ssc.certificate(), ssc.privateKey()).build(); } else { sslCtx = null; } // Configure the server. EventLoopGroup bossGroup = new NioEventLoopGroup(1); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.option(ChannelOption.SO_BACKLOG, 1024); b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .handler(new LoggingHandler(LogLevel.INFO)) .childHandler(new HttpHelloWorldServerInitializer(sslCtx)); Channel ch = b.bind(PORT).sync().channel(); System.err.println("Open your web browser and navigate to " + (SSL? "https" : "http") + "://127.0.0.1:" + PORT + '/'); ch.closeFuture().sync(); } finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } }
The above Class binds a ServerBootStrap to a set of child Handlers such as:
A Logging Handler
The HttpHelloWorldServletInizializer
The latter, is merely a wrapper for the main three Handlers that our Netty HTTP Server requires:
package io.netty.example.http.helloworld; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.socket.SocketChannel; import io.netty.handler.codec.http.HttpServerCodec; import io.netty.handler.codec.http.HttpServerExpectContinueHandler; import io.netty.handler.ssl.SslContext; public class HttpHelloWorldServerInitializer extends ChannelInitializer<SocketChannel> { private final SslContext sslCtx; public HttpHelloWorldServerInitializer(SslContext sslCtx) { this.sslCtx = sslCtx; } @Override public void initChannel(SocketChannel ch) { ChannelPipeline p = ch.pipeline(); if (sslCtx != null) { p.addLast(sslCtx.newHandler(ch.alloc())); } p.addLast(new HttpServerCodec()); p.addLast(new HttpServerExpectContinueHandler()); p.addLast(new HttpHelloWorldServerHandler()); } }
- The HttpServerCodec is a combination of HttpRequestDecoder and HttpResponseEncoder which enables easier server side HTTP implementation.
- The HttpServerExpectContinueHandler sends a 100 CONTINUE HttpResponse to HttpRequests which contain a ‘expect: 100-continue’ header.
- Finally, the HttpHelloWorldServerHandler is our main HTTPServer handler which contains the logic. We will provide an implementation of it:
package io.netty.example.http.helloworld; import io.netty.buffer.Unpooled; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelFutureListener; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.codec.http.DefaultFullHttpResponse; import io.netty.handler.codec.http.FullHttpResponse; import io.netty.handler.codec.http.HttpObject; import io.netty.handler.codec.http.HttpRequest; import io.netty.handler.codec.http.HttpUtil; import static io.netty.handler.codec.http.HttpHeaderNames.CONNECTION; import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_LENGTH; import static io.netty.handler.codec.http.HttpHeaderNames.CONTENT_TYPE; import static io.netty.handler.codec.http.HttpHeaderValues.CLOSE; import static io.netty.handler.codec.http.HttpHeaderValues.KEEP_ALIVE; import static io.netty.handler.codec.http.HttpHeaderValues.TEXT_PLAIN; import static io.netty.handler.codec.http.HttpResponseStatus.OK; public class HttpHelloWorldServerHandler extends SimpleChannelInboundHandler<HttpObject> { private static final byte[] CONTENT = { 'H', 'e', 'l', 'l', 'o', ' ', 'W', 'o', 'r', 'l', 'd' }; @Override public void channelReadComplete(ChannelHandlerContext ctx) { ctx.flush(); } @Override public void channelRead0(ChannelHandlerContext ctx, HttpObject msg) { if (msg instanceof HttpRequest) { HttpRequest req = (HttpRequest) msg; boolean keepAlive = HttpUtil.isKeepAlive(req); FullHttpResponse response = new DefaultFullHttpResponse(req.protocolVersion(), OK, Unpooled.wrappedBuffer(CONTENT)); response.headers() .set(CONTENT_TYPE, TEXT_PLAIN) .setInt(CONTENT_LENGTH, response.content().readableBytes()); if (keepAlive) { if (!req.protocolVersion().isKeepAliveDefault()) { response.headers().set(CONNECTION, KEEP_ALIVE); } } else { // Tell the client we're going to close the connection. response.headers().set(CONNECTION, CLOSE); } ChannelFuture f = ctx.write(response); if (!keepAlive) { f.addListener(ChannelFutureListener.CLOSE); } } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { cause.printStackTrace(); ctx.close(); } }
As you can see, this Handler extends SimpleChannelInboundHandler using as type io.netty.handler.codec.http.HttpObject. By using it, it will automatically handle the Encoding/Decoding of HTTP request in bytes.
As default, the HTTP Server will merely return an array of bytes with the text “Hello World”.
Running the HTTP Server
To build your project, make sure you include the netty-all dependency in your project:
<dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.52.Final</version> </dependency>
Next, start the Class HttpHelloWorldServer:
Jan 05, 2022 10:23:37 AM io.netty.handler.logging.LoggingHandler channelRegistered INFO: [id: 0xbfd1ee2e] REGISTERED Jan 05, 2022 10:23:37 AM io.netty.handler.logging.LoggingHandler bind INFO: [id: 0xbfd1ee2e] BIND: 0.0.0.0/0.0.0.0:8080 Open your web browser and navigate to http://127.0.0.1:8080/ Jan 05, 2022 10:23:37 AM io.netty.handler.logging.LoggingHandler channelActive INFO: [id: 0xbfd1ee2e, L:/0:0:0:0:0:0:0:0:8080] ACTIVE
Then, connect to port 8080 with your browser:
Source code (derived and re-packaged from Netty distribution) available here: https://github.com/fmarchioni/mastertheboss/tree/master/netty/http-server