写点什么

netty 系列之: 搭建客户端使用 http1.1 的方式连接 http2 服务器

发布于: 1 小时前

简介对于 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 的方式处理 http2netty 当然考虑到了客户的这种需求,所以提供了两个对应的类,分别是: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("") {@Overrideprotected 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/


最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧等你来发现!


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

发布于: 1 小时前阅读数: 5
用户头像

关注公众号:程序那些事,更多精彩等着你! 2020.06.07 加入

最通俗的解读,最深刻的干货,最简洁的教程,众多你不知道的小技巧,尽在公众号:程序那些事!

评论

发布
暂无评论
netty系列之:搭建客户端使用http1.1的方式连接http2服务器