簡介
在之前的文章中,我們實現了支援http2的netty伺服器,並且使用支援http2的瀏覽器成功的進行訪問。雖然瀏覽器非常通用,但是有時候我們也需要使用特定的netty客戶端去和伺服器進行通訊。
今天我們來探討一下netty客戶端對http2的支援。
配置SslContext
雖然http2並不強制要求支援TLS,但是現代瀏覽器都是需要在TLS的環境中開啟http2,所以對於客戶端來說,同樣需要配置好支援http2的SslContext。客戶端和伺服器端配置SslContext的內容沒有太大的區別,唯一的區別就是需要呼叫SslContextBuilder.forClient()而不是forServer()方法來獲取SslContextBuilder,建立SslContext的程式碼如下:
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();
如果使用SSL,那麼ssl handler必須是pipline中的第一個handler,所以將SslContext加入到pipline中的程式碼如下:
ch.pipeline().addFirst(sslCtx.newHandler(ch.alloc()));
客戶端的handler
使用Http2FrameCodec
netty的channel預設只能接收ByteBuf訊息,對於http2來說,底層傳輸的是一個個的frame,直接操作底層的frame對於普通程式設計師來說並不是特別友好,所以netty提供了一個Http2FrameCodec來對底層的http2 frame進行封裝成Http2Frame物件,方便程式的處理。
在伺服器端我們使用Http2FrameCodecBuilder.forServer()來建立Http2FrameCodec,在客戶端我們使用Http2FrameCodecBuilder.forClient()來建立Http2FrameCodec:
Http2FrameCodec http2FrameCodec = Http2FrameCodecBuilder.forClient()
.initialSettings(Http2Settings.defaultSettings())
.build();
然後將其加入到pipline中即可使用:
ch.pipeline().addLast(http2FrameCodec);
Http2MultiplexHandler和Http2MultiplexCodec
我們知道對於http2來說一個TCP連線中可以建立多個stream,每個stream又是由多個frame來組成的。考慮到多路複用的情況,netty可以為每一個stream建立一個單獨的channel,對於新建立的每個channel來說,都可以使用netty的ChannelInboundHandler來對channel的訊息進行處理,從而提升netty處理http2的效率。
而這個對stream建立新channel的支援,在netty中有兩個專門的類,他們是Http2MultiplexHandler和Http2MultiplexCodec。
他們的功能是一樣的,Http2MultiplexHandler繼承自Http2ChannelDuplexHandler,它必須和 Http2FrameCodec一起使用。而Http2MultiplexCodec本身就是繼承自Http2FrameCodec,已經結合了Http2FrameCodec的功能。
public final class Http2MultiplexHandler extends Http2ChannelDuplexHandler
@Deprecated
public class Http2MultiplexCodec extends Http2FrameCodec
但是通過檢查原始碼,我們發現Http2MultiplexCodec是不推薦使用的API,所以這裡我們主要介紹Http2MultiplexHandler。
對於Http2MultiplexHandler來說,每次新建立一個stream,都會建立一個新的對應的channel,應用程式使用這個新建立的channel來傳送和接收Http2StreamFrame。
新建立的子channel會被註冊到netty的EventLoop中,所以對於一個有效的子channel來說,並不是立刻就會被匹配到HTTP/2 stream上去,而是當第一個Http2HeadersFrame成功被髮送或者接收之後,才會觸發Event事件,進而進行繫結操作。
因為是子channel,所以對於connection level的事件,比如Http2SettingsFrame 和 Http2GoAwayFrame會首先被父channel進行處理,然後再廣播到子channel中進行處理。
同時,雖然Http2GoAwayFrame 和 Http2ResetFrame表示遠端節點已經不再接收新的frame了,但是因為channel本身還可能有queue的訊息,所以需要等待Channel.read()為空之後,才會進行關閉操作。
另外對於子channel來說,因為不能知道connection-level流控制window,所以如果有溢位的訊息會被快取在父channel的buff中。
有了Http2MultiplexHandler,將其加入client的pipline就可以讓客戶端支援多路的channel了:
ch.pipeline().addLast(new Http2MultiplexHandler(new SimpleChannelInboundHandler() {
@Override
protected void channelRead0(ChannelHandlerContext ctx, Object msg) {
// 處理inbound streams
log.info("Http2MultiplexHandler接收到訊息: {}",msg);
}
}))
使用子channel傳送訊息
從上面的介紹我們知道,一旦使用了Http2MultiplexHandler,那麼具體的訊息處理就是在子channel中了。那麼怎麼才能從父channel中獲取子channel,然後使用子channel來傳送資訊呢?
netty提供Http2StreamChannelBootstrap類,它提供了open方法,來建立子channel:
final Http2StreamChannel streamChannel;
try {
if (ctx.handler() instanceof Http2MultiplexCodec) {
streamChannel = ((Http2MultiplexCodec) ctx.handler()).newOutboundStream();
} else {
streamChannel = ((Http2MultiplexHandler) ctx.handler()).newOutboundStream();
}
我們要做的就是呼叫這個方法,來建立子channel:
final Http2StreamChannel streamChannel = streamChannelBootstrap.open().syncUninterruptibly().getNow();
然後將自定義的,專門處理Http2StreamFrame的Http2ClientStreamFrameHandler,新增到子channel的pipline中即可:
final Http2ClientStreamFrameHandler streamFrameResponseHandler =
new Http2ClientStreamFrameHandler();
streamChannel.pipeline().addLast(streamFrameResponseHandler);
準備完畢,構建http2訊息,使用streamChannel進行傳送:
// 傳送HTTP2 get請求
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);
總結
以上就是使用netty的framecode構建http2的客戶端和伺服器端進行通訊的基本操作了。
本文的例子可以參考:learn-netty4
本文已收錄於 http://www.flydean.com/32-netty-http2client-framecodec/
最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!
歡迎關注我的公眾號:「程式那些事」,懂技術,更懂你!