netty series: handheld framecodec artifact to create multiplex http2 client

Posted by optikalefx on Sun, 05 Dec 2021 01:41:04 +0100

brief introduction

In the previous article, we implemented a netty server that supports http2 and successfully accessed it using a browser that supports http2. Although the browser is very common, sometimes we need to use a specific netty client to communicate with the server.

Today, let's talk about the support of netty client for http2.

Configure SslContext

Although http2 is not required to support TLS, modern browsers need to enable http2 in the TLS environment. Therefore, for the client, it is also necessary to configure the SslContext that supports http2. There is no great difference between the client and server in configuring the SslContext. The only difference is that you need to call the SslContextBuilder.forClient() instead of forServer() method to obtain the SslContextBuilder. The code for creating the SslContext is as follows:

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();

If SSL is used, the ssl handler must be the first handler in pipline, so the code to add SslContext to pipline is as follows:

ch.pipeline().addFirst(sslCtx.newHandler(ch.alloc()));

Client's handler

Using Http2FrameCodec

Netty's channel can only receive ByteBuf messages by default. For http2, the underlying frames are transmitted one by one. Direct operation of the underlying frames is not particularly friendly to ordinary programmers. Therefore, netty provides an Http2FrameCodec to encapsulate the underlying http2 frame s into Http2Frame objects to facilitate program processing.

On the server side, we use Http2FrameCodecBuilder.forServer() to create Http2FrameCodec. On the client side, we use Http2FrameCodecBuilder.forClient() to create Http2FrameCodec:

Http2FrameCodec http2FrameCodec = Http2FrameCodecBuilder.forClient()
            .initialSettings(Http2Settings.defaultSettings())
            .build();

Then add it to pipline to use:

        ch.pipeline().addLast(http2FrameCodec);

Http2MultiplexHandler and Http2MultiplexCodec

We know that for http2, multiple streams can be created in a TCP connection, and each stream is composed of multiple frame s. Considering multiplexing, netty can create a separate channel for each stream. For each newly created channel, netty's ChannelInboundHandler can be used to process the messages of the channel, so as to improve the efficiency of netty in processing http2.

For the support of stream to create new channel s, there are two special classes in netty: Http2MultiplexHandler and Http2MultiplexCodec.

Their functions are the same. Http2MultiplexHandler inherits from Http2ChannelDuplexHandler, which must be used with Http2FrameCodec. Http2MultiplexCodec itself inherits from Http2FrameCodec and has combined the functions of Http2FrameCodec.

public final class Http2MultiplexHandler extends Http2ChannelDuplexHandler

@Deprecated
public class Http2MultiplexCodec extends Http2FrameCodec

However, by checking the source code, we find that Http2MultiplexCodec is a deprecated API, so here we mainly introduce Http2MultiplexHandler.

For Http2MultiplexHandler, each time a new stream is created, a new corresponding channel will be created. The application uses the newly created channel to send and receive Http2StreamFrame.

The newly created subchannel will be registered in the EventLoop of netty, so for an effective subchannel, it will not be matched to the HTTP/2 stream immediately, but the Event event will be triggered after the first Http2HeadersFrame is successfully sent or received, and then the binding operation will be carried out.

Because it is a child channel, events at connection level, such as Http2SettingsFrame and Http2GoAwayFrame, will be processed by the parent channel first, and then broadcast to the child channel for processing.

At the same time, although Http2GoAwayFrame and Http2ResetFrame indicate that the remote node no longer receives new frames, because the channel itself may have queue messages, it needs to wait until Channel.read() is empty before closing.

In addition, for the child channel, because the connection level flow control window cannot be known, if there is an overflow message, it will be cached in the buff of the parent channel.

With Http2MultiplexHandler, the client can support multiple channel s by adding it to the client's pipeline:

ch.pipeline().addLast(new Http2MultiplexHandler(new SimpleChannelInboundHandler() {
            @Override
            protected void channelRead0(ChannelHandlerContext ctx, Object msg) {
                // Handling inbound streams
                log.info("Http2MultiplexHandler Message received: {}",msg);
            }
        }))

Sending messages using sub channel s

From the above introduction, we know that once Http2MultiplexHandler is used, the specific message processing is in the sub channel. So how can I get a child channel from the parent channel and then use the child channel to send information?

netty provides the Http2StreamChannelBootstrap class, which provides the open method to create sub channels:

        final Http2StreamChannel streamChannel;
        try {
            if (ctx.handler() instanceof Http2MultiplexCodec) {
                streamChannel = ((Http2MultiplexCodec) ctx.handler()).newOutboundStream();
            } else {
                streamChannel = ((Http2MultiplexHandler) ctx.handler()).newOutboundStream();
            }

All we need to do is call this method to create a sub channel:

final Http2StreamChannel streamChannel = streamChannelBootstrap.open().syncUninterruptibly().getNow();

Then add the customized Http2ClientStreamFrameHandler that specifically handles Http2StreamFrame to the pipline of the child channel:

final Http2ClientStreamFrameHandler streamFrameResponseHandler =
                    new Http2ClientStreamFrameHandler();
streamChannel.pipeline().addLast(streamFrameResponseHandler);

After preparation, build the http2 message and send it using streamChannel:

// Send HTTP2 get request
            final DefaultHttp2Headers headers = new DefaultHttp2Headers();
            headers.method("GET");
            headers.path(PATH);
            headers.scheme(SSL? "https" : "http");
            Http2HeadersFrame headersFrame = new DefaultHttp2HeadersFrame(headers, true);
            streamChannel.writeAndFlush(headersFrame);

summary

The above is the basic operation of using the framework code of netty to build the client-side and server-side communication of http2.