Netty启动对HTTP/3的孵化器支持


经过一个多月的开发,我们终于可以发布我们的第一个孵化器版本Netty / Incubator / Codec / Quic。
为了能够在不“影响”网络核心的情况下开发令人兴奋的新功能,我们决定在“孵化器”中开始开发此类功能。这意味着这些功能将在单独的存储库中开发,并且只有在我们可以保证不再需要破坏API并考虑将这些产品准备就绪后,才会合并回核心netty存储库。这有望有助于澄清代码的期望和稳定性,因此人们应该充分意识到API可能会发生变化。除此之外,它还允许使用不同的发布时间表。
您需要为您的平台包括正确的依赖项。目前,我们仅支持macOS x86_64或linux x86_64。也就是说,这种情况将来可能会改变。
Maven / Linux的示例为:

<dependency>
    <groupId>io.netty.incubator</groupId>
    <artifactId>netty-incubator-codec-quic</artifactId>
    <version>0.0.1.Final</version>
    <classifier>linux-x86_64</classifier>
</dependency>

或者,如果您使用macOS:

<dependency>
    <groupId>io.netty.incubator</groupId>
    <artifactId>netty-incubator-codec-quic</artifactId>
    <version>0.0.1.Final</version>
    <classifier>osx-x86_64</classifier>
</dependency>

 
如何设置QUIC服务器/客户端?
// We just want to support HTTP 0.9 as application protocol
byte[] proto = new byte[] {
        0x08, 'h', 't', 't', 'p', '/', '0', '.', '9'
};

NioEventLoopGroup group = new NioEventLoopGroup(1);
ChannelHandler codec = new QuicServerCodecBuilder()
        .certificateChain("./src/test/resources/cert.crt")
        .privateKey("./src/test/resources/cert.key")
        .applicationProtocols(proto)
        .maxIdleTimeout(5000, TimeUnit.MILLISECONDS)
        // Configure some limits for the maximal number of streams (and the data) that we want to handle.
        .initialMaxData(10000000)
        .initialMaxStreamDataBidirectionalLocal(1000000)
        .initialMaxStreamDataBidirectionalRemote(1000000)
        .initialMaxStreamsBidirectional(100)
        .initialMaxStreamsUnidirectional(100)

        // Setup a token handler. In a production system you would want to implement and provide your
        // custom one.
        .tokenHandler(InsecureQuicTokenHandler.INSTANCE)
        // ChannelHandler that is added into QuicChannel pipeline.
        .handler(new ChannelInboundHandlerAdapter() {
            @Override
            public void channelActive(ChannelHandlerContext ctx) {
                QuicChannel channel = (QuicChannel) ctx.channel();
                // Create streams etc..
            }

            public void channelInactive(ChannelHandlerContext ctx) {
                ((QuicChannel) ctx.channel()).collectStats().addListener(f -> {
                    if (f.isSuccess()) {
                        LOGGER.info("Connection closed: {}", f.getNow());
                    }
                });
            }

            @Override
            public boolean isSharable() {
                return true;
            }
        })
        .streamHandler(new ChannelInitializer<QuicStreamChannel>() {
            @Override
            protected void initChannel(QuicStreamChannel ch)  {
                // Add a LineBasedFrameDecoder here as we just want to do some simple HTTP 0.9 handling.
                ch.pipeline().addLast(new LineBasedFrameDecoder(1024))
                        .addLast(new ChannelInboundHandlerAdapter() {
                    @Override
                    public void channelRead(ChannelHandlerContext ctx, Object msg) {
                        ByteBuf byteBuf = (ByteBuf) msg;
                        try {
                            if (byteBuf.toString(CharsetUtil.US_ASCII).trim().equals("GET /")) {
                                ByteBuf buffer = ctx.alloc().directBuffer();
                                buffer.writeCharSequence("Hello World!\r\n", CharsetUtil.US_ASCII);
                                // Write the buffer and shutdown the output by writing a FIN.
                                ctx.writeAndFlush(buffer).addListener(QuicStreamChannel.SHUTDOWN_OUTPUT);
                            }
                        } finally {
                            byteBuf.release();
                        }
                    }
                });
            }
        }).build();
try {
    Bootstrap bs = new Bootstrap();
    Channel channel = bs.group(group)
            .channel(NioDatagramChannel.class)
            .handler(codec)
            .bind(new InetSocketAddress(9999)).sync().channel();
    channel.closeFuture().sync();
} finally {
    group.shutdownGracefully();
}

QUIC客户端:

// We just want to support HTTP 0.9 as application protocol
byte[] proto = new byte[] {
        0x08, 'h', 't', 't', 'p', '/', '0', '.', '9'
};

NioEventLoopGroup group = new NioEventLoopGroup(1);
try {
    ChannelHandler codec = new QuicClientCodecBuilder()
            .applicationProtocols(proto)
            .maxIdleTimeout(5000, TimeUnit.MILLISECONDS)
            .initialMaxData(10000000)
            // As we don't want to support remote initiated streams just setup the limit for
            // local initiated streams in this example.
            .initialMaxStreamDataBidirectionalLocal(1000000)
            .build();

    Bootstrap bs = new Bootstrap();
    Channel channel = bs.group(group)
            .channel(NioDatagramChannel.class)
            .handler(codec)
            .bind(0).sync().channel();

    QuicChannel quicChannel = QuicChannel.newBootstrap(channel)
            .streamHandler(new ChannelInboundHandlerAdapter() {
                @Override
                public void channelActive(ChannelHandlerContext ctx) {
                    // As we did not allow any remote initiated streams we will never see
                    // this method called. That said just let us keep it here to demonstrate
                    // that this handle would be called for each remote initiated stream.
                    ctx.close();
                }
            })
            .remoteAddress(new InetSocketAddress(NetUtil.LOCALHOST4, 9999))
            .connect()
            .get();

    QuicStreamChannel streamChannel = quicChannel.createStream(QuicStreamType.BIDIRECTIONAL,
            new ChannelInboundHandlerAdapter() {
                @Override
                public void channelRead(ChannelHandlerContext ctx, Object msg) {
                    ByteBuf byteBuf = (ByteBuf) msg;
                    System.err.println(byteBuf.toString(CharsetUtil.US_ASCII));
                    byteBuf.release();
                }

                @Override
                public void userEventTriggered(ChannelHandlerContext ctx, Object evt) {
                    if (evt == ChannelInputShutdownReadComplete.INSTANCE) {
                        // Close the connection once the remote peer did send the FIN
                        // for this stream.
                        ((QuicChannel) ctx.channel().parent()).close(true, 0,
                                ctx.alloc().directBuffer(16)
                                        .writeBytes(new byte[]{'k', 't', 'h', 'x', 'b', 'y', 'e'}));
                    }
                }
            }).sync().getNow();
    // Write the data and send the FIN. After this its not possible anymore to write any more data.
    streamChannel.writeAndFlush(Unpooled.copiedBuffer("GET /\r\n", CharsetUtil.US_ASCII))
            .addListener(QuicStreamChannel.SHUTDOWN_OUTPUT);

    // Wait for the stream channel and quic channel to be closed (this will happen after we
    // received the FIN).  After this is done we will close the underlying datagram channel.
    streamChannel.closeFuture().sync();
    quicChannel.closeFuture().sync();
    channel.close().sync();
} finally {
    group.shutdownGracefully();
}

项目本身位于GitHub上