Introduction
For the http2 protocol, its bottom layer is completely different from http1.1, but in order to be compatible with the http1.1 protocol, http2 provides a way to upgrade from http1.1 to http2. This method is called cleartext upgrade, which can also be referred to as “cleartext upgrade”. h2c.
In netty, http2 data corresponds to various http2Frame objects, and http1 data corresponds to HttpRequest and HttpHeaders. Generally speaking, if you want to send http2 messages from the client to the server that supports http2, you need to send these http2Frame objects. Can you send HttpRequest objects like http1.1?
Today’s article will reveal the secret to everyone.
Use http1.1 to process http2
Of course, netty takes into account the needs of customers, so it provides two corresponding classes, namely: InboundHttp2ToHttpAdapter and HttpToHttp2ConnectionHandler.
They are a pair of methods, where InboundHttp2ToHttpAdapter converts received HTTP/2 frames into HTTP/1.x objects, while HttpToHttp2ConnectionHandler converts HTTP/1.x objects into HTTP/2 frames on the contrary. In this way, we only need to deal with the object of http1 in the program.
Their bottom layer actually calls the conversion method in the HttpConversionUtil class to convert the HTTP2 object and the HTTP1 object.
Handling TLS connections
Like the server, the client connection also needs to distinguish between TLS and clear text. TLS is simpler and only needs to process HTTP2 data. Clear text is more complicated and needs to be considered for http upgrades.
Let's first look at the connection handling of TLS.
The first is to create the SslContext. The creation of the client is no different from the creation of the server. It should be noted here that SslContextBuilder calls the forClient() method:
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();
Then pass the newHandler method of sslCtx into the pipeline:
pipeline.addLast(sslCtx.newHandler(ch.alloc(), CustHttp2Client.HOST, CustHttp2Client.PORT));
Finally, add ApplicationProtocolNegotiationHandler for TLS extension protocol negotiation:
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);
}
});
If it is the HTTP2 protocol, you need to add three handlers to pipline, namely connectionHandler, settingsHandler and responseHandler.
The connectionHandler is used to handle the connection between the client and the server. Here, HttpToHttp2ConnectionHandlerBuilder is used to construct a HttpToHttp2ConnectionHandler mentioned in the previous section, which is used to convert the http1.1 object into an http2 object.
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();
But the connection is actually two-way. HttpToHttp2ConnectionHandler converts http1.1 to http2. It is actually an outbound processor. We also need an inbound processor to convert the received http2 object into an http1.1 object. Here Realized by adding framelistener.
The frameListener passes in a DelegatingDecompressorFrameListener, which passes in the InboundHttp2ToHttpAdapterBuilder introduced in the previous section to convert the http2 object.
The settingsHandler is used to process Http2Settings inbound messages, and the responseHandler is used to process FullHttpResponse inbound messages.
These two are custom handler classes.
Handling h2c messages
As can be seen from the above code, we only processed the HTTP2 protocol in the TLS Protocol Negotiation. If it is the HTTP1 protocol, an error will be reported directly. If it is the HTTP1 protocol, it can be achieved through clear text upgrade, which is the h2c protocol.
Let's take a look at the handlers that h2c needs to add:
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());
}
First add HttpClientCodec as the source encoding handler, and then add HttpClientUpgradeHandler as the upgrade handler. Finally, add a custom CustUpgradeRequestHandler and event logger UserEventLogger.
The custom CustUpgradeRequestHandler is responsible for creating an upgradeRequest and sending it to the channel when channelActive.
Because the upgradeCodec already contains the connectionHandler for handling http2 connections, you also need to manually add settingsHandler and responseHandler.
ctx.pipeline().addLast(custHttp2ClientInitializer.settingsHandler(), custHttp2ClientInitializer.responseHandler());
send messages
After the handler is configured, we can directly send http2 messages in http1 mode.
First send a get request:
// 创建一个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());
Then there is a post request:
// 创建一个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());
It is not much different from a normal http1 request.
Summarize
By using InboundHttp2ToHttpAdapter and HttpToHttp2ConnectionHandler, you can easily use http1 to send http2 messages, which is very convenient.
For the examples in this article, please refer to: learn-netty4
This article has been included in http://www.flydean.com/30-netty-http2client-md/
The most popular interpretation, the most profound dry goods, the most concise tutorial, and many tips you don't know are waiting for you to discover!
Welcome to pay attention to my official account: "Program those things", know technology, know you better!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。