对于http2协定来说,它的底层跟http1.1是齐全不同的,然而为了兼容http1.1协定,http2提供了一个从http1.1降级到http2的形式,这个形式叫做cleartextupgrade,也能够简称为h2c。
简介
对于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/
最艰深的解读,最粗浅的干货,最简洁的教程,泛滥你不晓得的小技巧等你来发现!
欢送关注我的公众号:「程序那些事」,懂技术,更懂你!