1. 程式人生 > >http chunked傳輸

http chunked傳輸

一、Http協議中的chunked

一般http通訊時會使用content_length頭資訊來表示伺服器傳送的文件內容長度,該頭資訊定義域http1.0協議RFC 1945 10.4章節中,瀏覽器接收到此頭資訊後接受完content_length中定義的產度位元組後開始解析頁面,但是如果伺服器端有部分資料延遲傳送則會出現瀏覽器白屏,使用者體驗差。

解決方案是在http1.1協議中EFC 2616中14.41章節中定義的Transfer_encoding:chunked的頭資訊。chunked編碼定義在3.6.1中,所有HTTP1.1都應該支援使用chunked編碼動態的提供body內容的長度的方式。進行chunked編碼傳輸的http資料要在訊息頭部設定transfer_encoding:chunked表示content body將用chunked編碼傳輸內容。根據定義,瀏覽器不需要等到內容位元組全部下載完成,只要接收到一個chunked塊就可以解析頁面,並可以下載html中定義的頁面內容,包括js,css,image等

採用chunked編碼有兩種選擇一種是設定server的IO buffer長度讓server自動flush buffer中的內容另一種是手動呼叫IO中的flush函式,不同的語言IO中都有flush功能:

php:ob_flush();flush()

perl:STDOUT->autoflush(1);

java:out.flush()

python:sys.stout.flush()

ruby:stdout.flush

採用http1.1的transfer_encoding:chunked並且把IO的buffer flush下來,一遍瀏覽器更早的下載頁面配套資源,不可能在頭中包含content_length域來指明報文體長度,此時就需要通過transfer_encoding域來確定報文體長度

二、Chunked編碼的格式

chunked編碼一般使用若干個chunk串聯而成,最後一個表明長度為0的chunk表示結束。每一個chunked分為頭部和正文兩部分,頭部內容制定下一段正文的字元總數(非零開頭的十六進位制數字)和數量單位(一般不寫,表示位元組),正文部分就是指定長度的實際內容,兩部分之間用回車換行(CRLF)隔開,最後一個長度為0的chunk中的內容稱為footer的內容,是一些附加的header資訊(通常可以直接忽略)

chunked編碼的基本方法是將大塊資料分解成多塊小資料,每塊都可以自指定長度,其具體格式如下(BNF文法):
    Chunked-Body   =*chunk            //0至多個chunk
                    last-chunk         //最後一個chunk 
                    trailer                 //尾部
                    CRLF               //結束標記符

  chunk          = chunk-size [chunk-extension ] CRLF   
                       chunk-data CRLF
   chunk-size     =1*HEX
   last-chunk     =1*("0") [ chunk-extension ] CRLF

   chunk-extension= *( ";" chunk-ext-name[ "=" chunk-ext-val ] )
   chunk-ext-name = token
   chunk-ext-val  = token |quoted-string
   chunk-data     =chunk-size(OCTET)
  trailer        = *(entity-headerCRLF)   

解釋:
    Chunked-Body表示經過chunked編碼後的報文體。報文體可以分為chunk, last-chunk,trailer和結束符四部分。chunk的數量在報文體中最少可以為0,無上限;每個chunk的長度是自指定的,即,起始的資料必然是16進位制數字的字串,代表後面chunk-data的長度(位元組數)。這個16進位制的字串第一個字元如果是“0”,則表示chunk-size為0,該chunk為last-chunk,無chunk-data部分。可選的chunk-extension由通訊雙方自行確定,如果接收者不理解它的意義,可以忽略。
    trailer是附加的在尾部的額外頭域,通常包含一些元資料(metadata, meta means"about information"),這些頭域可以在解碼後附加在現有頭域之後。

在資料部分有可能有註釋位元組,註釋位元組是分號分隔,如果發現註釋的位元組應該跳過。

摘自rfc:

A server using chunked transfer-coding in aresponse MUST NOT use the trailer for any header fields unless at least one ofthe following is true:

使用transfer_encoding作為響應的伺服器不能使用trailer作為任何頭部域,除非以下條件滿足一條:

a)the request included a TE header fieldthat indicates "trailers" is acceptable in the transfer-coding of theresponse, as described in section 14.39; or,

a)請求包含一個TE頭部域表示接收方可以接受trailers,請參見14.39章節

b)the server is the origin server for theresponse, the trailer fields consist entirely of optional metadata, and therecipient could use the message (in a manner acceptable to the origin server)without receiving this metadata. In other words, the origin server is willingto accept the possibility that the trailer fields might be silently discardedalong the path to the client.

伺服器是原始響應伺服器,trailer域包括所有可選資料,並且接受者可以使用訊息(在某種意義上可以說接受原始伺服器)而不用接受這些資料。換句話說原始伺服器願意接受trailer域可能在傳遞給客戶端的過程中被默默地廢棄掉

This requirement prevents aninteroperability failure when the message is being received by an HTTP/1.1 (orlater) proxy and forwarded to an HTTP/1.0 recipient. It avoids a situationwhere compliance with the protocol would have necessitated a possibly infinitebuffer on the proxy.

這樣的設計阻止了這種失敗:訊息被一個http1.1或者更高版本的代理伺服器轉發給一個http1.0的接受者產生的失敗。從而避免了這種情況:遵守協議的代理伺服器必須擁有無限的buffer

An example process for decoding aChunked-Body is presented in appendix 19.4.6.

附件19.4.6中會給出一個解碼chunked_body的http響應的例子

All HTTP/1.1 applications MUST be able toreceive and decode the "chunked" transfer-coding, and MUST ignorechunk-extension extensions they do not understand.

所有的http1.1應用必須能夠接受和解碼chunked transfer_encoding,而且在沒有事先約定時必須忽略chunk_extension擴充套件

下面是抓包分析的chunked下載附件示例:


上面的一個chunk包沒有包含chunk_extension。在chunksize之後就是\r\n。下面是最後一個chunk:


上述解釋過於官方,簡而言之,chunked編碼的基本方法是將大塊資料分解成多塊小資料,每塊都可以自指定長度,其具體格式如下:


其C語言的解碼如下,java思路相同(以下程式碼摘自網路)

int nBytes;

char* pStart = a;    // a中存放待解碼的資料

char* pTemp;

char strlength[10];   //一個chunk塊的長度

chunk  : pTemp=strstr(pStart,"\r\n");

             if(NULL==pTemp)

             {

                      free(a);

                 a=NULL;

                     fclose(fp);

                     return -1;

             }

             length=pTemp-pStart;

            COPY_STRING(strlength,pStart,length);

            pStart=pTemp+2;

            nBytes=Hex2Int(strlength); //得到一個塊的長度,並轉化為十進位制

             if(nBytes==0)//如果長度為0表明為最後一個chunk

            {

                free(a);

                       fclose(fp);

                       return0;

               }

               fwrite(pStart,sizeof(char),nBytes,fp);//將nBytes長度的資料寫入檔案中

               pStart=pStart+nBytes+2; //跳過一個塊的資料以及資料之後兩個位元組的結束符

               fflush(fp);

               goto chunk; //goto到chunk繼續處理

如何將一個十進位制數轉化為十六進位制

char *buf = (char *)malloc(100);

char *d = buf;

int shift = 0;

unsigned long copy = 123445677;

while (copy) {

         copy >>= 4;

         shift++;

}//首先計算轉化為十六進位制後的位數

if (shift == 0)

         shift++;

shift <<= 2; //將位數乘於4,如果有兩位的話 shift為8

while (shift > 0) {

         shift -= 4;

         *(buf) =hex_chars[(123445677 >> shift) & 0x0F];

          buf++;

}

*buf = '\0';