netty http3代码解析(netty quic)

源代码连接:https://download.csdn.net/download/aashuii/88034681

1、依赖

以jdk20为例,使用的库如下:

    implementation "io.netty:netty-all:4.1.94.Final"
    implementation "io.netty:netty-codec-http:4.1.94.Final"
    implementation "io.netty.incubator:netty-incubator-codec-http3:0.0.19.Final"
    implementation 'io.netty.incubator:netty-incubator-codec-classes-quic:0.0.46.Final'
    //implementation 'io.netty.incubator:netty-incubator-codec-quic:0.0.19.Final'
    implementation 'io.netty.incubator:netty-incubator-codec-native-quic:0.0.46.Final'
    implementation 'org.bouncycastle:bcpkix-jdk15on:1.69'
    implementation 'org.bouncycastle:bcprov-jdk15on:1.69'
    implementation 'org.bouncycastle:bctls-jdk15on:1.69'
    implementation 'org.bouncycastle:bcutil-jdk15on:1.69'

使用时调用netty-incubator-codec-http3,netty-incubator-codec-http3调用netty-incubator-codec-quic,netty-incubator-codec-quic调用netty-incubator-codec-native-quic(在netty quic源码库中codec-native-quic下)。
native-quic会生成quiche的相关jar包和so。
使用时先下载对应netty quic版本编译出so。

注意:netty-incubator-codec-quic和netty-incubator-codec-classes-quic中很多类是冲突的,我们要依赖的是netty-incubator-codec-classes-quic而不是netty-incubator-codec-quic!!

2、HTTP3使用代码

2.1 服务器代码

public class hyperHttpServer {
    private QuicServerCodecBuilder quicBuilder;
    private int port;
    Router<HttpHandler> router = new Router<HttpHandler>();

    public hyperHttpServer(int portServ) {
        port = portServ;
        quicBuilder = Http3.newQuicServerCodecBuilder();
        // token未加密,后续优化
        quicBuilder.tokenHandler(InsecureQuicTokenHandler.INSTANCE);
        //maxIdleTimeout(5000, TimeUnit.MILLISECONDS)
        quicBuilder.initialMaxData(10000000);
        quicBuilder.initialMaxStreamDataBidirectionalLocal(1000000);
        quicBuilder.initialMaxStreamDataBidirectionalRemote(1000000);
        quicBuilder.initialMaxStreamsBidirectional(100);
    }

    public void setSslContext(final PrivateKey key, final String keyPassword, final X509Certificate... certChain) {
        QuicSslContext sslContext = QuicSslContextBuilder.forServer(key, keyPassword, certChain)
                .applicationProtocols(Http3.supportedApplicationProtocols()).earlyData(true).build();

        quicBuilder.sslContext(sslContext);
    }
        public final void addPostHandler(String key, HttpHandler handler) {
        router.POST(key, handler);
    }

    public final void build() throws Exception {
        NioEventLoopGroup group = new NioEventLoopGroup(1);
        System.out.println("http router: "+ router.toString());
        quicBuilder.handler(new HyperRouterServerChannelInitializer(router));

        try {
            Bootstrap bs = new Bootstrap();
            Channel channel = bs.group(group)
                    .channel(NioDatagramChannel.class)
                    .handler(quicBuilder.build())
                    .bind(new InetSocketAddress(port)).sync().channel();
            channel.closeFuture().sync();
        } finally {
            group.shutdownGracefully();
        }
    }

2.2 服务器使用例子

public class AppServerExample {
    public static void main(String[] args) throws Exception {
        int port = 50030;
        hyperHttpServer server = new hyperHttpServer(port);
        SelfSignedCertificate cert = new SelfSignedCertificate();
        server.setSslContext(cert.key(), null, cert.cert());

        server.addPostHandler("/example", new ExampleHandler());
        server.build();
    }
}

2.3 客户端代码

public class hyperHttpClient {
    private QuicClientCodecBuilder quicBuilder;
    private Channel channel;
    private NioEventLoopGroup group;

    public hyperHttpClient() {
        quicBuilder = Http3.newQuicClientCodecBuilder();
        quicBuilder.initialMaxStreamsBidirectional(100);
        quicBuilder.initialMaxStreamsUnidirectional(100);
        quicBuilder.initialMaxData(10000000);
        quicBuilder.initialMaxStreamDataBidirectionalLocal(1000000);
        quicBuilder.initialMaxStreamDataBidirectionalRemote(1000000);
        quicBuilder.initialMaxStreamDataUnidirectional(1000000);
    }

    public void setSslContext() {
        QuicSslContext sslContext = QuicSslContextBuilder.forClient()
                .trustManager(InsecureTrustManagerFactory.INSTANCE) // trust all cert
                .applicationProtocols(Http3.supportedApplicationProtocols()).build();

        quicBuilder.sslContext(sslContext);
    }
    public final void build() throws Exception {
        group = new NioEventLoopGroup(1);

        try {
            Bootstrap bs = new Bootstrap();
            channel = bs.group(group)
                    .channel(NioDatagramChannel.class)
                    .handler(quicBuilder.build())
                    .bind(0).sync().channel();
        } catch (Exception e) {
            group.shutdownGracefully();
            throw (e);
        }
    }

    public final void close() {
        try {
            channel.closeFuture().sync();
        } catch (InterruptedException e) {
            group.shutdownGracefully();
            System.out.println("client close error: " + e.toString());
            e.printStackTrace();
            // throw(e);
        } finally {
            group.shutdownGracefully();
        }
    }

   //注意:这只是简单的示例代码,没有支持连接复用
    public final FullHttpResponse request(FullHttpRequest req) throws Exception {
      URI uri;
        String uriStr = req.uri();
        try {
            uri = new URI(uriStr);
        } catch (URISyntaxException e) {
            System.out.print("URI err: " + e.toString());
            throw e;
        }

        System.out.println("req: " + req );
        int contentLen = req.content().readableBytes();
        req.headers().set(HttpHeaderNames.CONTENT_LENGTH, contentLen);

        QuicChannel quicChannel = QuicChannel.newBootstrap(channel)
                .handler(new Http3ClientConnectionHandler())
                .remoteAddress(new InetSocketAddress(uri.getHost(), uri.getPort()))
                //设置netty http3的qlog
                .option(QuicChannelOption.QLOG,
                new QLogConfiguration("/var/log", "testTitle", "test"));
                .connect()
                .get();

        QuicStreamChannel streamChannel = Http3.newRequestStream(quicChannel,
                new Http3RequestStreamInboundHandler() {
                    @Override
                    protected void channelRead(ChannelHandlerContext ctx,
                            Http3HeadersFrame frame, boolean isLast) {
                        //just for debug
                        //do nothing
                        System.out.println("header frame: " + frame.toString());
                        System.out.println("last: " + isLast);
                        ctx.fireChannelRead(frame);
                    }

                    @Override
                    protected void channelRead(ChannelHandlerContext ctx,
                            Http3DataFrame frame, boolean isLast) {
                        //header1+data1 both last, bug in netty?
                        System.out.println("data frame: " + frame.toString());
                        System.out.println("last: " + isLast);
                        ctx.fireChannelRead(frame);
                    }
                }).sync().getNow();

        streamChannel.pipeline().addLast(new Http3FrameToHttpObjectCodec(false));

        Http11Handler http11 = new Http11Handler();
        Promise<FullHttpResponse>  promise = http11.getPromise();
        streamChannel.pipeline().addLast(http11);

        streamChannel.writeAndFlush(req).addListener(QuicStreamChannel.SHUTDOWN_OUTPUT).sync();
        FullHttpResponse resp = promise.get();
        System.out.println("resp: " + resp.toString());

        streamChannel.closeFuture().sync();
       
        quicChannel.close().sync();
        channel.close().sync();

        return resp;
    }

2.4 客户端使用例子

public class AppClientExample {
    public static void main(String[] args) throws Exception {
        String uri = "http://localhost:50030/example";
        hyperHttpClient client = new hyperHttpClient();

        client.setSslContext();
        try {
            client.build();
        } catch (Exception e) {
            throw (e);
        }

        String msg = "Hello";
        byte[] bytes = msg.getBytes(CharsetUtil.UTF_8);
        ByteBuf buf = Unpooled.wrappedBuffer(bytes);

        FullHttpRequest req = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, HttpMethod.POST, uri, buf, false);
        System.out.println("FullHttpRequest: "+req);

        FullHttpResponse resp = client.request(req);
        System.out.println("response:" + resp.toString());
        if (resp.content().readableBytes() !=0) {
          final byte[] bs = new byte[128];
          resp.content().getBytes(0,bs,0,  resp.content().readableBytes());
          String str = new String(Arrays.copyOfRange(bs,0, resp.content().readableBytes()), CharsetUtil.UTF_8);
          System.out.println("response content:" + str);
        } else {
            System.out.println("response content is null");
        }

        client.close();
    }
}

3、HTTP1.1接口转换

netty http3的example上的app直接发送和接收http3的header帧和data帧。
但是一般的应用更习惯于处理Http1.1类型的接口,对于netty来说就是FullHttpRequest和FullHttpResponse。
netty http3提供了一个转换接口Http3FrameToHttpObjectCodec,其中实现了inbound和outdound,也就是说两个方向(入方向和出方向)都可以转换。

3.1 客户端转换代码:

         //channel是由http3创建的
         QuicStreamChannel streamChannel = Http3.newRequestStream(quicChannel,
                new Http3RequestStreamInboundHandler() {
                //这里我放了一个pipeline处理点,用来观察收到的帧,具体略
                。。。。
         }
         
         //插入转换点
        streamChannel.pipeline().addLast(new Http3FrameToHttpObjectCodec(false));
 
        //Http11Handler是一个整合http消息的处理点
        Http11Handler http11 = new Http11Handler();
        Promise<FullHttpResponse>  promise = http11.getPromise();
        streamChannel.pipeline().addLast(http11);

        // 写入请求和FIN
        streamChannel.writeAndFlush(req).addListener(QuicStreamChannel.SHUTDOWN_OUTPUT).sync();
        //Http11Handler根据受到的消息组合好响应会通过promise通知过来
        FullHttpResponse resp = promise.get();

Http11Handler的实现(只支持了简单的一问一答,不支持流式):

public class Http11Handler extends ChannelInboundHandlerAdapter {
    FullHttpResponse rspFul;
    private Promise<FullHttpResponse> promise;

    public Promise<FullHttpResponse> getPromise() {
        EventExecutor exec = GlobalEventExecutor.INSTANCE;
        promise = new DefaultPromise<FullHttpResponse>(exec);
        return promise;
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        //first HttpRequest then LastHttpContent
        if (msg instanceof FullHttpResponse) {
            rspFul = (FullHttpResponse)msg;
            System.out.println("[FullHttpResponse]-" + rspFul.toString());
            String lenStr = rspFul.headers().get(HttpHeaderNames.CONTENT_LENGTH);
            //无法判断是不是还有data帧 使用content-length 暂不支持streaming
            if (lenStr == null || lenStr.isEmpty()) {
                promise.setSuccess(rspFul);
                ctx.close();
            }
        } else if (msg instanceof LastHttpContent content) {
            System.out.println("[LastHttpContent]: "+content);
            if (content.content().readableBytes() != 0) {
                rspFul.content().writeBytes(content.content());
            }
            promise.setSuccess(rspFul);
            ctx.close();
        } else if (msg instanceof HttpResponse resp) {
            System.out.println("[HttpResponse]-" + resp.toString());
            rspFul = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, resp.status(), Unpooled.EMPTY_BUFFER, resp.headers(),EmptyHttpHeaders.INSTANCE);
        }else {
            Class<?> c = msg.getClass();
            System.out.println("[unkown type]-"+ c.getName());
        }
    }

}

3.2 服务器端转换代码

protected void initChannel(QuicStreamChannel ch) {
        System.out.println("stream initChannel");
        System.out.println(ch.pipeline().toString());
        ch.pipeline().addLast(new Http3RequestStreamInboundHandler() {
        //这里我放了一个pipeline处理点来观察收到的帧
        //略
        }
        //这是转换的处理点
        ch.pipeline().addLast(new Http3FrameToHttpObjectCodec(true));
        //这里来回调HTTP1.1的处理函数
        ch.pipeline().addLast(new Http1RequestHandler());
        //这里打印出来有七八个处理点,前面都是源码中quic和http3添加的,后三个是上面三个点
        System.out.println(ch.pipeline().toString());

服务器处理回调:

class Http1RequestHandler extends ChannelInboundHandlerAdapter {
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
            //first HttpRequest then LastHttpContent
            if (msg instanceof FullHttpRequest reqFul ) {              
                FullHttpRequest reqFul = (FullHttpRequest)msg;
                System.out.println("222[FullHttpRequest]-" + reqFul.toString());
                HttpResponse resp = createResponse(reqFul, handler);
                flushResponse(ctx, reqFul, resp);
                ctx.close();
            } else if (msg instanceof LastHttpContent content) {
                System.out.println("222[LastHttpContent]-" + content.toString());
                reqFul.content().writeBytes(content.content());
                HttpResponse resp = createResponse(reqFul, handler);
                flushResponse(ctx, reqFul, resp);
                ctx.close();
            } else if (msg instanceof HttpRequest req) {
                //后面可能还有content
                System.out.println("222[HttpRequest]-" + req.toString());
                reqFul = new DefaultFullHttpRequest(HttpVersion.HTTP_1_1, req.method(), req.uri(),Unpooled.EMPTY_BUFFER, req.headers(),EmptyHttpHeaders.INSTANCE);
            } else if (msg instanceof DefaultHttpContent content) {
                System.out.println("222[DefaultHttpContent]-" + content.toString());
                reqFul.content().writeBytes(content.content());
            }
            else {
                Class<?> c = msg.getClass();
                System.out.println("[222unkown type]-"+ c.getName());
            }

        }
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值