netty系列之:netty對http2訊息的封裝

flydean發表於2021-10-25

簡介

無論是什麼協議,如果要真正被使用的話,需要將該協議轉換成為對應的語言才好真正的進行應用,本文將從http2訊息的結構出發,探討一下netty對http2訊息的封裝,帶大家領略一下真正的框架應該做到什麼程度。

http2訊息的結構

http2和http1.1不同的是它使用了新的二進位制分幀,通過客戶端和伺服器端建立資料流steam來進行客戶端和伺服器端之間訊息的互動。其中資料流是一個雙向位元組流,用來傳送一條或者多條訊息。

訊息是客戶端和服務端傳送的一個邏輯上完整的資料。根據資料大小的不同,可以將訊息劃分為不同的幀Frame。也就是說message是由不同的frame組成的。

frame就是http2中進行通訊的最小單位,根據上一節的介紹,我們知道frame有這樣幾種:

  • DATA frame
  • HEADERS frame
  • PRIORITY frame
  • RST_STREAM frame
  • SETTINGS acknowledgment frame
  • SETTINGS frame
  • PING frame
  • PING acknowledgment
  • PUSH_PROMISE frame
  • GO_AWAY frame
  • WINDOW_UPDATE frame
  • Unknown Frame

我們看一下http2中stream和frame的一個大體的結構:

在http2中,一個TCP連線,可以承載多個資料流stream,多個stream中的不同frame可以交錯傳送。

每個frame通過stream id來標記其所屬的stream。

有了上面的http2的基本概念,我們接下來就看下netty對http2的封裝了。

netty對http2的封裝

Http2Stream

作為一個TCP連線下面的最大的單位stream,netty中提供了介面Http2Stream。注意,Http2Stream是一個介面,它有兩個實現類,分別是DefaultStream和ConnectionStream。

Http2Stream中有兩個非常重要的屬性,分別是id和state。

id前面已經介紹了,是stream的唯一標記。這裡要注意由客戶端建立的 Stream ID 必須是奇數,而由服務端建立的 Stream ID 必須是偶數。另外Stream ID 為 0 的流有特殊的作用,它是CONNECTION_STREAM_ID,1 表示HTTP_UPGRADE_STREAM_ID。

state表示stream的狀態,具體而言,stream有下面幾個狀態:

        IDLE(false, false),
        RESERVED_LOCAL(false, false),
        RESERVED_REMOTE(false, false),
        OPEN(true, true),
        HALF_CLOSED_LOCAL(false, true),
        HALF_CLOSED_REMOTE(true, false),
        CLOSED(false, false);

為什麼狀態需要區分local和remote呢?這是因為stream連線的兩端,所以有兩端的狀態。

和stream狀態相對應的就是http2的生命週期了。netty提供了Http2LifecycleManager來表示對http2生命週期的管理:

    void closeStreamLocal(Http2Stream stream, ChannelFuture future);
    void closeStreamRemote(Http2Stream stream, ChannelFuture future);
    void closeStream(Http2Stream stream, ChannelFuture future);
    ChannelFuture resetStream(ChannelHandlerContext ctx, int streamId, long errorCode,
            ChannelPromise promise);
    ChannelFuture goAway(ChannelHandlerContext ctx, int lastStreamId, long errorCode,
            ByteBuf debugData, ChannelPromise promise);
    void onError(ChannelHandlerContext ctx, boolean outbound, Throwable cause);

分別是關閉stream,重置stream,拒絕新建stream:goAway,和處理出錯狀態這幾種。

Http2Frame

stream之後,就是真實承載http2訊息的Http2Frame了。在netty中,Http2Frame是一個介面,它有很多具體的實現。

Http2Frame的直接子類包括HTTP2GoAwayFrame、HTTPPingFrame、Http2SettingsFrame和HTTP2SettingsAckFrame。

其中goAway表示不接受新的stream,ping用來進行心跳檢測。SETTINGS用來修改連線或者 Stream 流的配置。

netty中專門有一個Http2Settings類和其對應。

在這個類中定義了一些特別的setting名字:

SETTINGS 名字含義
SETTINGS_HEADER_TABLE_SIZE對端索引表的最大尺寸
SETTINGS_ENABLE_PUSH是否啟用伺服器推送功能
SETTINGS_MAX_CONCURRENT_STREAMS接收端允許的最大併發 Stream 數量
SETTINGS_INITIAL_WINDOW_SIZE傳送端的視窗大小,用於 Stream 級別流控
SETTINGS_MAX_FRAME_SIZE設定幀的最大大小
SETTINGS_MAX_HEADER_LIST_SIZE對端頭部索引表的最大尺寸

除了上面講的4個frame之外,其他的frame實現都繼承自Http2StreamFrame,具體而言有PriorityFrame,ResetFrame,HeadersFrame,DataFrame,WindowUpdateFrame,PushPromiseFrame和UnknownFrame。

各個frame分別代表了不同的功能。這裡最重要的就是Http2HeadersFrame和Http2DataFrame。

Http2HeadersFrame主要是客戶端傳送給伺服器端的http2請求。

具體而言除了標準的http1.1的header之外,http2還支援下面的header:

      METHOD(":method", true),

        SCHEME(":scheme", true),

        AUTHORITY(":authority", true),

        PATH(":path", true),

        STATUS(":status", false),

        PROTOCOL(":protocol", true);

對於Http2DataFrame來說,他本身是一個ByteBufHolder,用來傳遞具體的資料資訊。data frame的Payload直接儲存在ByteBuf中。

總結

以上就是netty對http2訊息的封裝了。

本文的例子可以參考:learn-netty4

本文已收錄於 http://www.flydean.com/28-netty-wrap-http2/

最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!

歡迎關注我的公眾號:「程式那些事」,懂技術,更懂你!

相關文章