乐趣区

关于java:netty系列之搭建客户端使用http11的方式连接http2服务器

简介

对于 http2 协定来说,它的底层跟 http1.1 是齐全不同的,然而为了兼容 http1.1 协定,http2 提供了一个从 http1.1 降级到 http2 的形式,这个形式叫做 cleartext upgrade, 也能够简称为 h2c。

在 netty 中,http2 的数据对应的是各种 http2Frame 对象,而 http1 的数据对应的是 HttpRequest 和 HttpHeaders。一般来说要想从客户端发送 http2 音讯给反对 http2 的服务器,那么须要发送这些 http2Frame 的对象,那么可不可以像 http1.1 这样发送 HttpRequest 对象呢?

明天的文章将会给大家揭秘。

应用 http1.1 的形式解决 http2

netty 当然思考到了客户的这种需要,所以提供了两个对应的类,别离是:InboundHttp2ToHttpAdapter 和 HttpToHttp2ConnectionHandler。

他们是一对办法,其中 InboundHttp2ToHttpAdapter 将接管到的 HTTP/2 frames 转换成为 HTTP/1.x objects,而 HttpToHttp2ConnectionHandler 则是相同的将 HTTP/1.x objects 转换成为 HTTP/2 frames。这样咱们在程序中只须要解决 http1 的对象即可。

他们的底层实际上调用了 HttpConversionUtil 类中的转换方法,将 HTTP2 对象和 HTTP1 对象进行转换。

解决 TLS 连贯

和服务器一样,客户端的连贯也须要辨别是 TLS 还是 clear text,TLS 简略点,只须要解决 HTTP2 数据即可,clear text 简单点,须要思考 http 降级的状况。

先看下 TLS 的连贯解决。

首先是创立 SslContext,客户端的创立和服务器端的创立没什么两样,这里要留神的是 SslContextBuilder 调用的是 forClient() 办法:

SslProvider provider =
                    SslProvider.isAlpnSupported(SslProvider.OPENSSL)? SslProvider.OPENSSL : SslProvider.JDK;
            sslCtx = SslContextBuilder.forClient()
                    .sslProvider(provider)
                    .ciphers(Http2SecurityUtil.CIPHERS, SupportedCipherSuiteFilter.INSTANCE)
                    // 因为咱们的证书是自生成的,所以须要信赖放行
                    .trustManager(InsecureTrustManagerFactory.INSTANCE)
                    .applicationProtocolConfig(new ApplicationProtocolConfig(
                            Protocol.ALPN,
                            SelectorFailureBehavior.NO_ADVERTISE,
                            SelectedListenerFailureBehavior.ACCEPT,
                            ApplicationProtocolNames.HTTP_2,
                            ApplicationProtocolNames.HTTP_1_1))
                    .build();

而后将 sslCtx 的 newHandler 办法传入到 pipeline 中:

pipeline.addLast(sslCtx.newHandler(ch.alloc(), CustHttp2Client.HOST, CustHttp2Client.PORT));

最初退出 ApplicationProtocolNegotiationHandler,用于 TLS 扩大协定的协商:

pipeline.addLast(new ApplicationProtocolNegotiationHandler("") {
            @Override
            protected void configurePipeline(ChannelHandlerContext ctx, String protocol) {if (ApplicationProtocolNames.HTTP_2.equals(protocol)) {ChannelPipeline p = ctx.pipeline();
                    p.addLast(connectionHandler);
                    p.addLast(settingsHandler, responseHandler);
                    return;
                }
                ctx.close();
                throw new IllegalStateException("未知协定:" + protocol);
            }
        });

如果是 HTTP2 协定,则须要向 pipline 中退出三个 handler,别离是 connectionHandler,settingsHandler 和 responseHandler。

connectionHandler 用于解决客户端和服务器端的连贯,这里应用 HttpToHttp2ConnectionHandlerBuilder 来构建一个上一节提到的 HttpToHttp2ConnectionHandler,用来将 http1.1 对象转换成为 http2 对象。

Http2Connection connection = new DefaultHttp2Connection(false);
        connectionHandler = new HttpToHttp2ConnectionHandlerBuilder()
                .frameListener(new DelegatingDecompressorFrameListener(
                        connection,
                        new InboundHttp2ToHttpAdapterBuilder(connection)
                                .maxContentLength(maxContentLength)
                                .propagateSettings(true)
                                .build()))
                .frameLogger(logger)
                .connection(connection)
                .build();

然而连贯其实是双向的,HttpToHttp2ConnectionHandler 是将 http1.1 转换成为 http2,它实际上是一个 outbound 处理器,咱们还须要一个 inbound 处理器,用来将接管到的 http2 对象转换成为 http1.1 对象,这里通过增加 framelistener 来实现。

frameListener 传入一个 DelegatingDecompressorFrameListener,其外部又传入了前一节介绍的 InboundHttp2ToHttpAdapterBuilder 用来对 http2 对象进行转换。

settingsHandler 用来解决 Http2Settings inbound 音讯,responseHandler 用来解决 FullHttpResponse inbound 音讯。

这两个是自定义的 handler 类。

解决 h2c 音讯

从下面的代码能够看出,咱们在 TLS 的 ProtocolNegotiation 中只解决了 HTTP2 协定,如果是 HTTP1 协定,间接会报错。如果是 HTTP1 协定,则能够通过 clear text upgrade 来实现,也就是 h2c 协定。

咱们看下 h2c 须要增加的 handler:

    private void configureClearText(SocketChannel ch) {HttpClientCodec sourceCodec = new HttpClientCodec();
        Http2ClientUpgradeCodec upgradeCodec = new Http2ClientUpgradeCodec(connectionHandler);
        HttpClientUpgradeHandler upgradeHandler = new HttpClientUpgradeHandler(sourceCodec, upgradeCodec, 65536);

        ch.pipeline().addLast(sourceCodec,
                              upgradeHandler,
                              new CustUpgradeRequestHandler(this),
                              new UserEventLogger());
    }

首先增加的是 HttpClientCodec 作为 source 编码 handler,而后增加 HttpClientUpgradeHandler 作为 upgrade handler。最初增加自定义的 CustUpgradeRequestHandler 和事件记录器 UserEventLogger。

自定义的 CustUpgradeRequestHandler 负责在 channelActive 的时候,创立 upgradeRequest 并发送到 channel 中。

因为 upgradeCodec 中曾经蕴含了解决 http2 连贯的 connectionHandler,所以还须要手动增加 settingsHandler 和 responseHandler。

ctx.pipeline().addLast(custHttp2ClientInitializer.settingsHandler(), custHttp2ClientInitializer.responseHandler());

发送音讯

handler 配置好了之后,咱们就能够间接以 http1 的形式来发送 http2 音讯了。

首先发送一个 get 申请:

// 创立一个 get 申请
                FullHttpRequest request = new DefaultFullHttpRequest(HTTP_1_1, GET, GETURL, Unpooled.EMPTY_BUFFER);
                request.headers().add(HttpHeaderNames.HOST, hostName);
                request.headers().add(HttpConversionUtil.ExtensionHeaderNames.SCHEME.text(), scheme.name());
                request.headers().add(HttpHeaderNames.ACCEPT_ENCODING, HttpHeaderValues.GZIP);
                request.headers().add(HttpHeaderNames.ACCEPT_ENCODING, HttpHeaderValues.DEFLATE);
                responseHandler.put(streamId, channel.write(request), channel.newPromise());

而后是一个 post 申请:

// 创立一个 post 申请
                FullHttpRequest request = new DefaultFullHttpRequest(HTTP_1_1, POST, POSTURL,
                        wrappedBuffer(POSTDATA.getBytes(CharsetUtil.UTF_8)));
                request.headers().add(HttpHeaderNames.HOST, hostName);
                request.headers().add(HttpConversionUtil.ExtensionHeaderNames.SCHEME.text(), scheme.name());
                request.headers().add(HttpHeaderNames.ACCEPT_ENCODING, HttpHeaderValues.GZIP);
                request.headers().add(HttpHeaderNames.ACCEPT_ENCODING, HttpHeaderValues.DEFLATE);
                responseHandler.put(streamId, channel.write(request), channel.newPromise());

和一般的 http1 申请没太大区别。

总结

通过应用 InboundHttp2ToHttpAdapter 和 HttpToHttp2ConnectionHandler 能够不便的应用 http1 的办法来发送 http2 的音讯,十分不便。

本文的例子能够参考:learn-netty4

本文已收录于 http://www.flydean.com/30-netty-http2client-md/

最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现!

欢送关注我的公众号:「程序那些事」, 懂技术,更懂你!

退出移动版