IMLC.ME
/netty/Netty 创建 HTTP 服务/

Netty 创建 HTTP 服务

引入 Netty 依赖

Maven
<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.77.Final</version>
</dependency>
Gradle(Groovy)
implementation 'io.netty:netty-all:4.1.77.Final'
Gradle(Kotlin)
implementation("io.netty:netty-all:4.1.77.Final")

启动 Netty 服务

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();
        }
    }
}

上方代码,使用 ServerBootstrap 创建了一个 NIO TCP 服务器。数据经过一系列 handlers 被解码、处理、构造响应、然后编码回 TCP 包。 这里的关键类是 HttpHelloWorldServerInitializer。负责将 TCP 数据解码成 HTTP 数据,并执行后续处理。

创建 HttpHelloWorldServerInitializer

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());
    }
}

HttpHelloWorldServerInitializer 非常简单。它构造了一条 ChannelPipeline。数据经过被该 Pipeline 处理,依次经过 HttpServerCodec、HttpServerExpectContinueHandler、HttpHelloWorldServerHandler。

HttpServerCodec 负责把 TCP 解码成 HTTP 层数据。
HttpServerExpectContinueHandler 见名知意,处理 HTTP 的 Continue 响应。
HttpHelloWorldServerHandler 则负责最后处理 HTTP 请求。

处理 HTTP 请求

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();
    }
}

channelRead0() 方法接受 HTTP 请求 HttpObject msg, 构造 FullHttpResponse response HTTP 响应,并返回 HTTP 结果 ctx.write(response)

开源项目应用

上述的例子只是一个粗浅的 HTTP Server。一个成熟的 HTTP 服务需要同时支持 HTTPS、HTTP/2、HTTP/2 Cleartext 和 WebSocket等其他情况。

需要的朋友可以参考其他开源项目的配置。这里列出两个比较流行的 Netty-based 的 HTTP 服务框架。

Ktor

Ktor NettyChannelInitializer

Vert.x

Vert.x NetServerImpl

参考

HTTP - Netty 官方示例