<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    John Jiang

    a cup of Java, cheers!
    https://github.com/johnshajiang/blog

       :: 首頁 ::  :: 聯系 :: 聚合  :: 管理 ::
      131 隨筆 :: 1 文章 :: 530 評論 :: 0 Trackbacks
    探索HTTP/2: HTTP/2協議簡述
    HTTP/2的協議包含著兩個RFC:Hypertext Transfer Protocol Version 2 (RFC7540),即HTTP/2;HPACK: Header Compression for HTTP/2 (RFC7541),即HPACK。RFC7540描述了HTTP/2的語義,RFC7541則描述了用于HTTP/2的頭部壓縮的格式。本文只涉及HTTP/2協議,本系列的后續文章將會涉及HPACK協議。(2016.10.13最后更新)

    1. HTTP/2要解決的問題
         HTTP/1.0只允許在一個TCP連接中出現一個請求。后來的HTTP/1.1雖然引入了請求流水線,以允許在一個連接中發送多個請求,但這只是部分地解決了請求并發的問題。服務器端在返回響應時,還是必須要按照它接收到的請求的順序進行返回。如果排在前面的響應要消耗較長的時間,那依然會對后面的響應的造成阻塞,亦即線頭阻塞(Head-of-line blocking)。所以,客戶端必須要使用多條連接去發起多個的請求以實現并發,并進而減小延遲。更大的并發會增大服務器的負載,也會占用更大的網絡帶寬。另外,頭部通常會包含有大量的信息,如cookie,而這也會增加網絡傳輸的開銷。
         HTTP/2允許在同一個TCP連接中交錯地出現多個請求與響應,亦即多工(Multiplex)。同時,它使用了一個高效的編碼方法對頭部進行壓縮。HTTP/2還允許對請求進行優先級排序,以便讓更為重要的請求得以更快的完成,這會進一步提高性能。HTTP/2還改變了服務器端只能被動地向客戶端返回響應的定式,允許服務器端主動地向客戶端推送數據,這就可以減少客戶端發起請求的數量。
         總之,HTTP/2主要是解決性能問題。

    2. 發起HTTP/2
         HTTP/2會使用與HTTP/1相同的URI scheme,即http和https。而且實現HTTP/2的服務器端也不會使用不同的端口去分別支持HTTP/1和HTTP/2。這樣有利于平滑地從HTTP/1升級到HTTP/2。畢竟目前已部署的絕大部分網絡服務都只支持HTTP/1,當未來它們升級到HTTP/2時,如果換用了不同URI scheme或端口,那么肯定會對客戶端產生極大的影響。但是HTTP/2協議為運行在http和https上的HTTP/2分別定義了兩個不同的標識符:h2c和h2。h2c中的"c"指的是cleartext,即明文。本文后面會使用h2c指代運行在http2上(直接使用TCP)的HTTP/2,而用h2指代運行在https上(使用TLS)的HTTP/2。
         那么,支持HTTP/2的客戶端如何知道它所連接的服務器端是否也支持HTTP/2呢?
         對于h2c,支持HTTP/2的客戶端可以在發起的請求中使用HTTP/1.1的Upgrade頭部去嘗試要求服務器升級到HTTP/2。該請求的格式如下:
    GET / HTTP/1.1
    Host: server.example.com
    Connection: Upgrade, HTTP2-Settings
    Upgrade: h2c
    HTTP2-Settings: <base64url encoding of HTTP/2 SETTINGS payload>
    HTTP2-Settings是一個經由BASE64編碼過的字符串,其原始內容是客戶端將要發送的SETTINGS幀的載荷,即一些配置參數。
         如果服務器端支持HTTP/2,它就響應"101 Switching Protocols",表示可以進行升級。該響應的格式如下:
    HTTP/1.1 101 Switching Protocols
    Connection: Upgrade
    Upgrade: h2c
         如果服務器端不支持HTTP/2,則會忽略Upgrade請求頭部,后續依然使用HTTP/1.1。
         對于h2,會使用到協議Transport Layer Security (TLS) Application-Layer Protocol Negotiation Extension (RFC7301),即TLS-ALPN。該協議允許客戶端和服務器端就使用何種版本的HTTP進行協商。如果TLS-ALPN在現實中運行良好的話,也許某天還會使用該方法去協商使用別的協議。
         當客戶端與服務器端都同意使用HTTP/2時,雙方都需要各自發出一個連接序言(Connection Preface)以進行最后的確認。
         客戶端在接收到服務器端的"101 Switching Protocols"響應(針對h2c)或TLS連接的第一個應用數據字節(針對h2)之后會立即發出連接序言。該序言的開頭是"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"(其十六進制形式為"0x505249202a20485454502f322e300d0a0d0a534d0d0a0d0a")(1),后面必須再跟一個SETTINGS幀,哪怕這個幀是空的。
         服務器端的連接序言則由一個SETTINGS幀構成,該幀必須是服務器端在HTTP/2連接中發送的第一個幀。這個SETTINGS幀可以為空,也可以包含一些希望客戶端如何與自己進行通信的必要配置信息。

    3. 幀(Frame)
         HTTP/2消息使用二進制格式(實際編碼時使用十六進制書寫),相比于文本格式,這樣可以提高消息處理的效率。HTTP/2消息的最小單元為幀,它由頭部與載荷(Payload)組成。每個幀的長度必須是一個或多個8比特位字節(octet,下文將其簡寫為"字節")。
         幀頭部依次包含有如下的5個字段:
         長度(Length):該字段占用24個比特位,代表幀載荷的長度。該長度是一個24位的無符號整數。
         類型(Type):該字段占用8個比特位,代表幀的類型。
         標志(Flags):該字段占用8個比特位,代表幀所定義的一個或多個標志。并不是所有的幀都定義了標志。
         保留位(R):該字段占用1個比特位,其語義尚未被定義。在讀取幀時,該位需要被忽略;但在發送幀時,該位需要保持為0(0x0)。
         流標識符(Stream Identifier):該字段占用31個比特位,代表該幀所在流的標識符。
         在頭部之后,緊接著的就是載荷。載荷的結構與內容完全由幀的類型決定,它的長度也是不定的。

         HTTP/2定義了如下10種不同類型的幀。
         DATA:用于攜帶一組長度不定的字節。一個或多個DATA可作為請求或響應的載荷。
         HEADERS:用于開啟一個流,并可攜帶一個頭部塊片斷。頭部塊指由一個HEADERS/PUSH_PROMISE幀和緊隨它的零到多個CONTINUATION幀組成的集合,因為只有它們才可能攜帶頭部信息。這個集合可被分割成一個或一組字節,這樣的字節被稱為頭部塊片斷。頭部塊中各個特定類型的幀必須緊緊相鄰,不能出現其它類型的幀。
         PRIORITY:用于指定發送端建議的流優先級。
         RST_STREAM:用于立即終止流。當希望取消一個流或發生錯誤時,就可發送RST_STREAM幀。
         SETTINGS:用于攜帶可以影響兩端之間通信方式的配置參數。SETTINGS幀定義了一個ACK標志,用于指示該幀所設置的參數是否已被接收端獲知。當收到一個SETTINGS且其中的ACK標志為0時,接收端必須盡可能快的應用其中已被更新的參數。
         PUSH_PROMISE:用于向接收端通知發送端將要創建的流。當接收端接收到該幀時,新的流尚未被發送端創建,但發送端承諾會創建該流。該幀用于實現HTTP/2的重要特性"服務器端推送(Server Push)"。
         PING:用于測量發送端與接收端之間的最小往返時間。這與使用眾所周知的ping命令的目的相似,是為了測試某個空閑的連接是否還可用。
         GOAWAY:用于發起對連接的關閉,或觸發嚴重的錯誤條件。該幀允許一端,在完成對之前已創建的流的處理的同時,優雅地停止接收新的流。一端在創建新的流,另一端在發送GOAWAY,這兩者之間天然存在著競爭關系。為了就對這種情況,發送端在發送GOAWAY時會讓它攜帶上(該發送端所知曉的)接收端最后創建的流的標識符,當該GOAWAY被發送之后,發送端將會忽視由接收端創建的任何一個標識符比該標識符大的流。
         WINDOW_UPDATE:用于流量控制。該幀的載荷由一個單比特保留位和一個31比特位的無符號整數組成。該整數向該幀的接收端指示了其向當前流量控制窗口所能增加傳輸量的值。
         CONTINUATION:用于繼續發送頭部塊片斷。只要同一個流中前面的幀是HEADERS,PUSH_PROMISE或CONTINUATION,并且該幀沒有設置END_HEADERS標志,那么可無限量地發送CONTINUATION幀。
         部分幀,DATA,HEADERS和PUSH_PROMISE,的載荷中可能包含填白(Padding)。填白在業務上沒有實際的用處,它的出現是基于安全目的。比如,可以用它來擾亂實際數據的長度,以減輕特定的HTTP攻擊。

         發送端發送的幀的最大長度要尊重接收端設定的SETTINGS_MAX_FRAME_SIZE的值。但該值的范圍要介于2^14至2^24-1個字節之間。

    4. 流(Stream)
         流是用于在客戶端與服務器端之間進行幀傳送的通道,同一個TCP連接中可以同時有多個流,如下圖所示,
    ┌────────┐          Connection           ┌────────┐
    │        │ ============================= │        │
    │        │    --------------------- <-- Stream    │
    │        │    ┌─────┐┌─────────┐┌─┐      │        │
    │        │    └─────┘└─────────┘└─┘ <-- Frame     │
    │        │    ---------------------      │        │
    │ Client │                               │ Server │
    │        │    ----------                 │        │
    │        │    ┌──┐┌────┐                 │        │
    │        │    └──┘└────┘                 │        │
    │        │    ----------                 │        │
    │        │ ============================= │        │
    └────────┘                               └────────┘
    服務器端和客戶端可以交錯地向同一個連接中的不同流中傳送幀。可以把一個流看作HTTP/1中的一個連接。客戶端與服務器端在同一個流中的交互依然遵循發送請求-等待響應模式。兩端都可以創建新的流,共享對方創建的流,也可以關閉對方創建的流。幀在流中的順序是有意義的,接收端會以接收到的順序去處理幀。
         每個流都有一個標識符,是一個31比特位的無符合整數。在同一個連接中,流標識符是唯一的。由客戶端創建的流的標識符為奇數,由服務器創建的流的標識符為偶數。但標識符為0的流可看作連接,用于連接控制信息,創建新的流時不可使用該標識符。同一個連接中的任何一個流的標識符都不可重用,即便這個流已被關閉了。對于長時間沒有中斷的連接,可能會出現標識符不夠用的情況,那時就必須強制創建一個新的連接。
         HTTP/2協議為流的生命周期定義了7種狀態(2):idle,reserved(local),reserved(remote),open,half closed(local),half closed(remote)和closed。當一端接收或發送頭部塊或(幀DATA和HEADERS的)標志RST_STREAM后可使流的狀態發生轉變。
         使用流來實現多工就會引起對TCP連接使用的競爭,這會造成流的阻塞。基于幀WINDOW_UPDATE的流量控制方案可以確保相同連接中的流相互之間不會產生破壞性干擾。流量控制可以作用于兩個層面,即單個流或整個連接。只有幀DATA需要遵守流量控制,所有其它的幀所有消耗的空間均不會占用流量控制窗口。HTTP/2協議只是定義了WINDOW_UPDATE幀的結構和語義,協議的實現可以選擇任何適用自己的流量控制算法。
         流可以有優先級。客戶端在創建一個新的流時,可在HEADERS中指定優先級權重。在后續任何時間,通過PRIORITY可以改變流的優先級權重。在并發能力有限的情況下,高權重流的幀會被優先傳送。權重的值必須介于1至256之間,默認權重為16。流與流之間還可以有依賴關系,這種關系會組成一棵依賴關系樹。一個流能夠指定自己成為另一個流的子流。這一過程,可以是非排他的,也可以是排他的。非排他性依賴,是指一個流在將自己變成另一個流的子流的過程中,允許另一個流還有別的子流,即允許有自己的兄弟流存在。排他性依賴,指在前述過程中,不允許另一個流還有別的子流。如果另一個流已經有子流了,那么該流會把所有潛在的兄弟流先變成自己的子流,然后再使自己成為另一個流的唯一子流。其實,排他性依賴的作用就是為了能夠打破已有的關系樹,在既成的父子節點中插入新的節點。否則,只能為已有節點添加子節點,那么關系樹將不可能進行重構。所有的流在被創建時,默認成為標識符為0x0的流的子流。在"服務器端推送"中生成的"推送"流將自動地成為生成該推送流的流的子流,其默認權重也為16。

    5. 消息交換
    5.1 請求/響應交換
         HTTP/2沿襲了HTTP/1的語義,即所有的請求與響應語義均得到了保留,盡管傳遞這些語義的語法已經改變了。
         一個HTTP/2消息由如下幾個部分組成:
         [1]僅對于響應消息,可以包含一個攜帶有1xx響應頭部的頭部塊。該頭部塊由一個HEADERS幀和緊隨它的零到多個CONTINUATION幀組成。
         [2]一個頭部塊。該頭部塊由一個HEADERS幀和緊隨它的零到多個CONTINUATION幀組成。
         [3]零到多個攜帶有體部(Body)消息的DATA幀。HTTP/1中使用的"分塊(chunked)"體部將不適用于HTTP/2。因為一個體部可由多個DATA幀組成,所以HTTP/2的體部天然就是可分塊的。
         [4]一個可能存在的包含著尾部消息的頭部塊。該頭部塊由一個HEADERS幀和緊隨它的零到多個CONTINUATION幀組成。

         HTTP/2仍然沿用HTTP/1中的頭部字段,但字段名稱中的字母必須全部為小寫。另外,還將HTTP/1消息開始行(請求中的請求行與響應中的狀態行)中的消息,分解成了若干偽頭部字段,此類字段均以冒號(:)開頭。
         HTTP/1請求行格式為"method request-target HTTP-version",對應的HTTP/2偽頭部字段有:method=method和:path=request-target,但HTTP-version無對應字段,默認為HTTP/2。
         HTTP/1狀態行格式為"HTTP-version status-code reason-phrase",對應的HTTP/2偽頭部字段有:status=status-code。但HTTP-version無對應字段,默認為HTTP/2;reason-phrase也無對應字段,因為可以通過狀態代碼查找到其對應的reason-phrase。HTTP/2協議是在盡量減少冗余消息。
         HTTP/2協議還為請求頭部定義了另外兩個偽字段:
         :scheme:URI中的scheme部分。它可以不僅僅是http或https,因為有時候可能會與非HTTP服務進行交互。
         :authority:URI中的授權部分。即,scheme://user:password@host:port/path?query#fragment中的"user:password@host:port"。
         HTTP/2協議8.1.3節中給出一些簡單示例,展示了如何將HTTP/1消息對應到HTTP/2消息。
    5.2 服務器端推送
         HTTP/2的服務器端推送是傳統的請求/響應模式的一種特殊形式。服務器端在收到客戶端的請求(主請求)之后,為了主動向客戶端推送更多的內容,會自動地生成若干新的請求(推送請求)。服務器向客戶端發送的響應中,不僅包含對主請求的響應(主響應),還包含對推送請求的響應(推送響應)。
         客戶端可以通過發送包含有SETTINGS_MAX_CONCURRENT_STREAMS參數的SETTINGS幀去禁用服務器端推送,也可以通過發送RST_STREAM幀去取消已經發起的服務器端推送,但不能發送包含有END_STREAM標志的幀。

    (1)"PRI * HTTP/2.0\r\n\r\nSM\r\n\r\n"中的"PRI"與"SM"合起來就是"RRISM(棱鏡)"。呵呵,HTTPbis工作組這是想表達什么意思呢 ;-)
    (2)本系列的后續文章解讀了流的狀態。
    posted on 2016-09-19 11:36 John Jiang 閱讀(2668) 評論(0)  編輯  收藏 所屬分類: 原創HTTP/2探索HTTP/2
    主站蜘蛛池模板: 亚洲一级免费视频| 精品一区二区三区无码免费视频 | 久久久久久av无码免费看大片| 人人狠狠综合久久亚洲高清| 国产午夜无码精品免费看| 亚洲精品韩国美女在线| 女人被免费视频网站| 亚洲国产成人久久一区WWW| 久久国产精品一区免费下载| 性xxxx黑人与亚洲| 久久久久亚洲精品天堂久久久久久| 免费国产黄网站在线观看| 亚洲中文字幕乱码AV波多JI| 国产亚洲大尺度无码无码专线| jjizz全部免费看片| 国产特黄一级一片免费 | 又长又大又粗又硬3p免费视频| 亚洲色欲www综合网| 中文字幕亚洲专区| 国产精品免费观看久久| 久久嫩草影院免费看夜色| 亚洲欧美不卡高清在线| 亚洲精品视频在线观看视频| 亚洲一本大道无码av天堂| 免费精品一区二区三区在线观看| 免费国产叼嘿视频大全网站| 狠狠入ady亚洲精品| 亚洲特级aaaaaa毛片| 亚洲精品成人片在线观看精品字幕| 成年女人看片免费视频播放器| 未满十八18禁止免费无码网站| 欧洲精品码一区二区三区免费看| 亚洲中文无码永久免费| 精品亚洲A∨无码一区二区三区| 亚洲精品无码精品mV在线观看| 亚洲成a人片在线观看国产| www.亚洲一区| 亚洲精品老司机在线观看| 免费在线观看视频a| 亚洲?v无码国产在丝袜线观看| 免费又黄又爽的视频|