新聞中心
Hi,大家好,我是承香墨影!

創(chuàng)新互聯(lián)是專業(yè)的隆林網(wǎng)站建設(shè)公司,隆林接單;提供成都網(wǎng)站設(shè)計、成都網(wǎng)站制作,網(wǎng)頁設(shè)計,網(wǎng)站設(shè)計,建網(wǎng)站,PHP網(wǎng)站建設(shè)等專業(yè)做網(wǎng)站服務(wù);采用PHP框架,可快速的進行隆林網(wǎng)站開發(fā)網(wǎng)頁制作和功能擴展;專業(yè)做搜索引擎喜愛的網(wǎng)站,專業(yè)的做網(wǎng)站團隊,希望更多企業(yè)前來合作!
HTTP 協(xié)議在網(wǎng)絡(luò)知識中占據(jù)了重要的地位,HTTP 協(xié)議最基礎(chǔ)的就是請求和響應(yīng)的報文,而報文又是由報文頭(Header)和實體組成。大多數(shù) HTTP 協(xié)議的使用方式,都是依賴設(shè)置不同的 HTTP 請求/響應(yīng) 的 Header 來實現(xiàn)的。
本系列《實用 HTTP》就拋開常規(guī)的 Header 講解式的表述方式,從實際問題出發(fā),來分析這些 HTTP 協(xié)議的使用方式,到底是為了解決什么問題?同時講解它是如何設(shè)計的和它實現(xiàn)原理。
HTTP 協(xié)議是一種無狀態(tài)的“松散協(xié)議”,它不會記錄不同請求的狀態(tài),并且因為它本身包含了兩端(客戶端和服務(wù)端),根據(jù)請求和響應(yīng)來區(qū)分,它大部分的內(nèi)容都只是一個建議,其實雙邊是可以不遵守此建議的。
前兩篇文章中,我們分別聊了 HTTP 的緩存機制 和 HTTP 內(nèi)容實體編碼壓縮機制 ,在說到實體編碼壓縮的時候,還提到了一個傳輸編碼,讓我們優(yōu)化傳輸?shù)姆绞?。實體編碼和傳輸編碼二者是相輔相成的,一般我們會配合使用。
本文就來聊聊 HTTP 的傳輸編碼機制。
二、HTTP 的傳輸編碼
2.1 什么是傳輸編碼?
傳輸編碼在 HTTP 的報文頭中,使用 Transfer-Encoding 首部進行標(biāo)記,它就是指明當(dāng)前使用的傳輸編碼。
Transfer-Encoding 會改變報文的格式和傳輸?shù)姆绞剑褂盟坏粫p少內(nèi)容傳輸?shù)拇笮?,甚至還有可能會使傳輸變大,看似是一個不環(huán)保的做法,但是其實是為了解決一些特殊問題。
簡單來說,傳輸編碼必須配合持久連接去使用,為了在一個持久連接中,將數(shù)據(jù)分塊傳輸,并標(biāo)記傳輸結(jié)束而設(shè)計的,后面會詳細講解。
在早年間的設(shè)計里,和內(nèi)容編碼使用 Accept-Encoding 來標(biāo)記客戶端接收的壓縮編碼類型一樣,傳輸編碼還需要配合 TE 這個請求報文頭來使用,用于指定支持的傳輸編碼。但是在最新的 HTTP/1.1 協(xié)議規(guī)范中,只定義了一種傳輸編碼:分塊編碼(chunked),所以并不需要再依賴 TE 這個頭部。
這些細節(jié),后面都會講到。既然傳輸編碼和持久連接是息息相關(guān)的,那我們就先來了解一下什么是持久連接。
2.2 持久連接(Persistent Connection)
持久連接通俗來講,就是長連接,英文叫 Persistent Connection,其實按字面意思理解就好了。
在早期的 HTTP 協(xié)議中,傳輸數(shù)據(jù)的順序大致分為發(fā)起請求、建立連接、傳輸數(shù)據(jù)、關(guān)閉連接等步驟,而持久連接,就是去掉關(guān)閉連接這個步驟,讓客戶端和服務(wù)端可以繼續(xù)通過此次連接傳輸內(nèi)容。
這其實也是為了提高傳輸效率,我們知道 HTTP 協(xié)議是建立在 TCP 協(xié)議之上的,自然有 TCP 一樣的三次握手、慢啟動等特性,這樣每一次連接其實都是一次寶貴的資源。為了盡可能的提高 HTTP 的性能,使用持久連接就顯得很重要了。為此在 HTTP 協(xié)議中,就引入了相關(guān)的機制。
在早期的 HTTP/1.0 協(xié)議中并沒有持久連接,持久連接的概念是在后期才引入的,當(dāng)時是通過 Connection:Keep-Alive 這個頭部來標(biāo)記實現(xiàn),用于通知客戶端或服務(wù)端相對的另一端,在發(fā)送完數(shù)據(jù)之后,不要斷開 TCP 連接,之后還需要再次使用。
而在 HTTP/1.1 協(xié)議中,發(fā)現(xiàn)持久連接的重要性了,它規(guī)定所有的連接必須都是持久的,除非顯式的在報文頭里,通過 Connection:close 這個首部,指定在傳輸結(jié)束之后會關(guān)閉此連接。
實際上在 HTTP/1.1 中Connect 這個頭部已經(jīng)沒有 Keep-Alive 這個取值了,由于歷史原因,很多客戶端和服務(wù)端,依然保留了這個報文頭。
長連接帶來了另外一個問題,如何判定當(dāng)前數(shù)據(jù)發(fā)送完成。
2.3 判斷傳輸完成
在早期不支持持久連接的時候,其實是可以依靠連接斷開來判定當(dāng)前傳輸已經(jīng)結(jié)束,大部分瀏覽器也是這么干的,但這并不是規(guī)范的操作。應(yīng)該使用 Content-Length 這個頭部,來指定當(dāng)前傳輸?shù)膶嶓w內(nèi)容長度。
下面舉個例子,在保持持久連接的情況下,依賴 Content-Length 來確定數(shù)據(jù)發(fā)送完畢。
Content-Length 在這里起到了一個響應(yīng)實體已經(jīng)發(fā)送結(jié)束的判斷依據(jù)。這樣的情況下,我們就要求 Content-Length 必須和內(nèi)容實體的長度一致,如果不一致,就會出現(xiàn)各種問題。
如上圖所示,如果 Content-Length 小于內(nèi)容實體的長度,則會截斷,反之則無法判定當(dāng)前響應(yīng)已經(jīng)結(jié)束,會將請求持續(xù)掛起造成 Padding 狀態(tài)。
理想情況下,我們在響應(yīng)一個請求的時候,就需要知道它的內(nèi)容實體的大小。但是在實際應(yīng)用中,有些時候內(nèi)容實體的長度并沒有那么容易獲得。例如內(nèi)容實體來自網(wǎng)絡(luò)文件、或者是動態(tài)生成的。這個時候如果依然想要提前獲取到內(nèi)容實體的長度,只能開一個足夠大的 Buffer,等內(nèi)容全部緩存好了再計算。
但這并不是一個好的方案,全部緩存到 Buffer 里,第一會消耗更多的內(nèi)存,第二也會更耗時,讓客戶端等待過久。
此時就需要一個新的機制,不依賴 Content-Length 的值,來判定當(dāng)前內(nèi)容實體是否傳輸完成,此時就需要 Transfer-Encoding 這個頭部來判定。
2.4 Transfer-Encoding:chunked
前面也提到,Transfer-Encoding 在最新的 HTTP/1.1 協(xié)議里,就只有 chunked 這個參數(shù),標(biāo)識當(dāng)前為分塊編碼傳輸。
分塊編碼傳輸既然只有一個可選的參數(shù),我們就只需要指定它為 Transfer-Encoding:chunked ,后續(xù)我們就可以將內(nèi)容實體包裝一個個塊進行傳輸。
分塊傳輸?shù)囊?guī)則:
1. 每個分塊包含一個 16 進制的數(shù)據(jù)長度值和真實數(shù)據(jù)。
2. 數(shù)據(jù)長度值獨占一行,和真實數(shù)據(jù)通過 CRLF(\r\n) 分割。
3. 數(shù)據(jù)長度值,不計算真實數(shù)據(jù)末尾的 CRLF,只計算當(dāng)前傳輸塊的數(shù)據(jù)長度。
4. 最后通過一個數(shù)據(jù)長度值為 0 的分塊,來標(biāo)記當(dāng)前內(nèi)容實體傳輸結(jié)束。
在這個例子中,首先在響應(yīng)頭部里標(biāo)記了 Transfer-Encoding: chunked,后續(xù)先傳遞了第一個分塊 “0123456780”,長度為 b(11 的十六進制),之后分別傳輸了 “Hello CxmyDev” 和 “123”,最后以一個長度為 0 的分塊標(biāo)記當(dāng)前響應(yīng)結(jié)束。
2.5 chunked 的拖掛
當(dāng)我們使用 chunked 進行分塊編碼傳輸?shù)臅r候,傳輸結(jié)束之后,還有機會在分塊報文的末尾,再追加一段數(shù)據(jù),此數(shù)據(jù)稱為拖掛(Trailer)。
拖掛的數(shù)據(jù),可以是服務(wù)端在末尾需要傳遞的數(shù)據(jù),客戶端其實是可以忽略并丟棄拖掛的內(nèi)容的,這就需要雙方協(xié)商好傳輸?shù)膬?nèi)容了。
在拖掛中可以包含附帶的首部字段,除了 Transfer-Encoding、Trailer 以及 Content-Length 首部之外,其他 HTTP 首部都可以作為拖掛發(fā)送。
一般我們會使用拖掛來傳遞一些在響應(yīng)報文開始的時候,無法確定的某些值,例如:Content-MD5 首部就是一個常見的在拖掛中追加發(fā)送的首部。和長度一樣,對于需要分塊編碼傳輸?shù)膬?nèi)容實體,在開始響應(yīng)的時候,我們也很難算出它的 MD5 值。
注意這里在頭部增加了 Trailder,用以指定末尾還會傳遞一個 Content-MD5 的拖掛首部,如果有多個拖掛的數(shù)據(jù),可以使用逗號進行分割。
三、內(nèi)容編碼和傳輸編碼結(jié)合
內(nèi)容編碼和傳輸編碼一般都是配合使用的。我們會先使用內(nèi)容編碼,將內(nèi)容實體進行壓縮,然后再通過傳輸編碼分塊發(fā)送出去??蛻舳私邮盏椒謮K的數(shù)據(jù),再將數(shù)據(jù)進行重新整合,還原成最初的數(shù)據(jù)。
四、傳輸編碼小結(jié)
我們對傳輸編碼應(yīng)該有一定的了解了。這里簡單總結(jié)一下:
1. 傳輸編碼使用 Transfer-Encoding 首部進行標(biāo)記,在最新的 HTTP/1.1 協(xié)議里,它只有 chunked 這一個取值,表示分塊編碼。
2. 傳輸編碼主要是為了解決持久連接里將數(shù)據(jù)分塊傳輸之后,判定內(nèi)容實體傳輸結(jié)束。
3. 分塊的格式:數(shù)據(jù)長度(16進制)+ 分塊數(shù)據(jù)。
4. 如果還有額外的數(shù)據(jù),可以在結(jié)束之后,使用 Trailer 進行拖掛傳輸額外的數(shù)據(jù)。
5. 傳輸編碼通常會配合內(nèi)容編碼一起使用。
此外,傳輸編碼應(yīng)該是所有 HTTP/1.1 的標(biāo)準(zhǔn)實現(xiàn),應(yīng)該都有支持,如果收到無法理解的經(jīng)過傳輸編碼的報文,應(yīng)該直接返回 501 Unimplemented 這個狀態(tài)碼來回復(fù)即可。
參考連接:
HTTP 協(xié)議中的 Transfer-Encoding:https://imququ.com/post/transfer-encoding-header-in-http.html
REC 7230, 3.3.1 Transfer-Encoding:https://tools.ietf.org/html/rfc7230#page-28
RFC 7230, section 4.4: Trailer:https://tools.ietf.org/html/rfc7230#section-4.4
RFC 7230, section 4.1.2: Chunked trailer part:https://tools.ietf.org/html/rfc7230#section-4.1.2
【本文為專欄作者“張旸”的原創(chuàng)稿件,轉(zhuǎn)載請通過微信公眾號聯(lián)系作者獲取授權(quán)】
戳這里,看該作者更多好文
網(wǎng)站題目:HTTP傳輸編碼增大了傳輸量,只為解決這一個問題|實用HTTP
轉(zhuǎn)載來于:http://m.5511xx.com/article/codseej.html


咨詢
建站咨詢
