簡介
netty為我們提供了很多http2的封裝,讓我們可以輕鬆的搭建出一個支援http2的伺服器。其中唯一需要我們自定義的就是http2 handler。
在之前的文章中,我們介紹了自定義http2handler繼承自Http2ConnectionHandler並且實現Http2FrameListener。這種實現方式是netty目前比較推薦的實現方式,今天給大家介紹的一種實現方式是netty中準備替換繼承Http2ConnectionHandler的實現方式,但是這種實現方式並不成熟,還在不斷的完善中。
今天給大家介紹一下這種實現方式。
Http2FrameCodec
這種實現方式的核心類是Http2FrameCodec。事實上Http2FrameCodec也是繼承自Http2ConnectionHandler。
它的主要作用是將HTTP/2中的frames和Http2Frame物件進行對映。Http2Frame是netty中對應所有http2 frame的封裝,這樣就可以在後續的handler中專注於處理Http2Frame物件即可,從而擺脫了http2協議的各種細節,可以減少使用者的工作量。
對於每個進入的HTTP/2 frame,Http2FrameCodec都會建立一個Http2Frame物件,並且將其傳遞給channelRead方法,用於對該物件進行處理。
通過呼叫write方法,可以對outbound的 Http2Frame 轉換成為http2 frame的格式。
Http2Frame、Http2FrameStream和Http2StreamFrame
netty中有三個非常類似的類,他們是Http2Frame、Http2FrameStream和Http2StreamFrame。
我們知道netty中一個tcp連線可以建立多個stream,Http2FrameStream就是和stream對應的類,這個類中包含了stream的id和stream當前的狀態。
一個stream中又包含了多個訊息,每個訊息都是由多個frame組成的,所以Http2Frame是和這些frame對應的netty類。
Http2StreamFrame本身也是一個frame,事實上它繼承自Http2Frame。
為什麼會有這個類呢?因為對應frame本身來說,一般情況下它是和一個特定的stream相關聯的,Http2StreamFrame表示這種關聯關係,可以通過它的set stream方法來指定其關聯的stream。如果想要該frame應用到整個連線而不是特定的某個stream,如果是關聯到整個連線,那麼stream()方法的返回就是null。
Http2FrameCodec的構造
雖然Http2FrameCodec有建構函式,但是netty推薦使用Http2FrameCodecBuilder來構造它:
Http2FrameCodecBuilder.forServer().build();
可以看到Http2FrameCodecBuilder有一個forServer還有一個forClient方法。他們一個是使用在伺服器端,一個是使用在客戶端。
主要是通過裡面的server屬性來進行區分。
Stream的生命週期
frame codec將會向有效的stream傳送和寫入frames。之前講過了 Http2StreamFrame 是和一個Http2FrameStream物件相關聯的。
對於一個有效的stream來說,如果任意一方傳送一個RST_STREAM frame,那麼該stream就會被關閉。
或者傳送方或者接收方任意一方傳送的frame中帶有END_STREAM標記,該stream也會被關閉。
流控制
Http2FrameCodec提供了對流的自動控制,但是我們仍然需要做一些操作,來對window進行更新。
具體而言,當我們在接收到Http2DataFrame訊息的時候,對訊息進行處理之後,需要增大window的大小,表示該data已經被處理了,可以有更多的空間去容納新的資料。
也就是說需要向ctx中寫入一個Http2WindowUpdateFrame,在這個Http2WindowUpdateFrame中需要傳入處理的data的大小和對應stream的id,下面是一個處理data frame的例子:
/**
* 處理data frame訊息
*/
private static void onDataRead(ChannelHandlerContext ctx, Http2DataFrame data){
Http2FrameStream stream = data.stream();
if (data.isEndStream()) {
sendResponse(ctx, stream, data.content());
} else {
// 不是end stream不傳送,但是需要釋放引用
data.release();
}
// 處理完data,需要更新window frame,增加處理過的Data大小
ctx.write(new DefaultHttp2WindowUpdateFrame(data.initialFlowControlledBytes()).stream(stream));
}
上的例子中,我們向DefaultHttp2WindowUpdateFrame傳入了對應的stream id,如果stream id為0,則表示處理的是整個connection,而不是單獨的某個stream。
除了window update frame之外,對於某個特定stream的初始window還可以傳送一個 Http2SettingsFrame,通過設定Http2Settings.initialWindowSize() 達到初始化的目的。
接收訊息
對於每個HTTP/2 stream來說,其中包含的frame可以分為 Http2HeadersFrame和Http2DataFrame,而Http2HeadersFrame必定是第一個接收到的frame,並且這個headerFrame還關聯了對應的stream物件:Http2FrameStream。
所以我們在處理的時候可以針對這兩種不同的frame分別進行處理:
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof Http2HeadersFrame) {
onHeadersRead(ctx, (Http2HeadersFrame) msg);
} else if (msg instanceof Http2DataFrame) {
onDataRead(ctx, (Http2DataFrame) msg);
} else {
super.channelRead(ctx, msg);
}
}
自定義handler
如果使用Http2FrameCodec,我們只需要在pipline中新增Http2FrameCodec之後,再新增自定義的handler即可:
ctx.pipeline().addLast(Http2FrameCodecBuilder.forServer().build(), new CustHttp2Handler());
因為Http2FrameCodec已經對http2中的frame進行了轉換,所以我們在CustHttp2Handler中只需要處理自定義邏輯即可。
netty推薦自定義的handler繼承自Http2ChannelDuplexHandler,因為它比普通的ChannelDuplexHandler多了一個建立newStream()和遍歷所有有效stream的 forEachActiveStream(Http2FrameStreamVisitor)方法。
總結
本文講解了Http2FrameCodec的原理,和與其搭配的handler實現中要注意的事項。
本文的例子可以參考:learn-netty4
本文已收錄於 http://www.flydean.com/31-netty-framecodec-http2/
最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!
歡迎關注我的公眾號:「程式那些事」,懂技術,更懂你!