netty series: build a client to connect to the http2 server using http1.1

brief introduction For http2 protocol, its bot...
brief introduction

For http2 protocol, its bottom layer is completely different from http1.1, but in order to be compatible with http1.1 protocol, http2 provides a way to upgrade from http1.1 to http2, which is called cleartext upgrade, or h2c for short.

In netty, http2 data corresponds to various http2Frame objects, while http1 data corresponds to HttpRequest and HttpHeaders. Generally speaking, if you want to send http2 messages from the client to the server supporting http2, you need to send these http2Frame objects. Can you send HttpRequest objects like http1.1?

Today's article will reveal the secret to you.

Use http1.1 to handle http2

netty, of course, takes into account the needs of customers, so it provides two corresponding classes: InboundHttp2ToHttpAdapter and HttpToHttp2ConnectionHandler.

They are a pair of methods, in which InboundHttp2ToHttpAdapter converts the received HTTP/2 frames into HTTP/1.x objects, while HttpToHttp2ConnectionHandler conversely converts HTTP/1.x objects into HTTP/2 frames. 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 HTTP2 objects and HTTP1 objects.

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 complex. HTTP upgrade needs to be considered.

Let's look at TLS connection processing first.

The first step is to create the SslContext. The creation of the client side is no different from that of the server side. Note that the method for client () is called by the SslContextBuilder:

SslProvider provider = SslProvider.isAlpnSupported(SslProvider.OPENSSL)? SslProvider.OPENSSL : SslProvider.JDK; sslCtx = SslContextBuilder.forClient() .sslProvider(provider) .ciphers(Http2SecurityUtil.CIPHERS, SupportedCipherSuiteFilter.INSTANCE) // Because our certificate is self generated, we need trust release .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 to negotiate TLS extension protocol:

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("Unknown protocol: " + protocol); } });

If it is an HTTP 2 protocol, you need to add three handlers to pipline, namely connectionHandler, settingsHandler and responseHandler.

connectionHandler is used to handle the connection between client and server. Here, HttpToHttp2ConnectionHandler builder is used to build an HttpToHttp2ConnectionHandler mentioned in the previous section to convert http1.1 objects into http2 objects.

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 bidirectional. HttpToHttp2ConnectionHandler converts http1.1 into http2. In fact, it is an outbound processor. We also need an inbound processor to convert the received http2 object into http1.1 object. This is realized by adding framelistener.

frameListener passes in a DelegatingDecompressorFrameListener, and the InboundHttp2ToHttpAdapterBuilder described in the previous section is passed in to convert http2 objects.

settingsHandler is used to process Http2Settings inbound message, and responseHandler is used to process FullHttpResponse inbound message.

These two are custom handler classes.

Processing h2c messages

As can be seen from the above code, we only process the HTTP2 protocol in the protocol negotiation of TLS. If it is the HTTP1 protocol, an error will be reported directly. If it is HTTP1 protocol, it can be implemented through clear text upgrade, that is, h2c protocol.

Let's look at h2c the handler to be added:

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 code handler, and then add HttpClientUpgradeHandler as the upgrade handler. Finally, add custom CustUpgradeRequestHandler and event logger UserEventLogger.

The custom CustUpgradeRequestHandler is responsible for creating an upgradeRequest and sending it to the channel when the channel is active.

Because the upgradeCodec already contains a connectionHandler to handle http2 connections, you need to manually add settingsHandler and responseHandler.

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

After the handler is configured, we can directly send http2 messages in the form of http1.

First send a get request:

// Create a get request 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:

// Create a post request 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's not much different from ordinary http1 requests.

summary

By using InboundHttp2ToHttpAdapter and HttpToHttp2ConnectionHandler, you can easily use the http1 method to send http2 messages, which is very convenient.

Examples of this article can be referred 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 find!

Welcome to my official account: "those things in procedure", understand technology, know you better!

3 November 2021, 23:01 | Views: 9497

Add new comment

For adding a comment, please log in
or create account

0 comments