Http協議簡單解析

HTTP協議的基本格式:

Request:

第一行爲Request Line,分爲三個區域,第一個爲請求方法,第二個爲請求鏈接,第三個爲協議類型,結尾加上\r\n。之後的N行爲Request Header,基本格式爲xxx: xxxxx,最後都是以\r\n結束。當請求頭結束後,加上\r\n表示Request請求寫入結束,調用flush方法傳給Server服務器。附一段代碼:

PrintStream ps = new PrintStream(out);
        ps.write("GET /zlv2Excel.do?reqCode=initDir HTTP/1.1\r\n".getBytes());
        ps.write("Host: 120.26.136.141:8080\r\n".getBytes());
        ps.write("Connection: keep-alive\r\n".getBytes());
        ps.write("Accept: text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2\r\n".getBytes());
        ps.write("User-Agent: Chrome/38.0.2125.122\r\n".getBytes());
        ps.write("\r\n".getBytes());
        ps.flush();

Response:

1.第一類爲包含 Content-Length:xxxx 信息的。瀏覽器通過Content-Length的長度,判斷響應實體已經結束。如果Content-Length比實際長度短,會造成內容被截取;如果Content-Length比實際長度長,會造成pending(服務器一直等待讀取狀態)。所以Content-Length必須真實反映實體長度。但實際上,如果實體是文件或者動態生成的,想要獲取長度就不那麼容易。真要計算,也需要開一個額外的buffer去緩存計算,這樣會有額外的內存開銷,也更增加了響應時間。下面附一段Content-Length:xxxx的響應體:

72 84 84 80 47 49 46 49 32 50 48 48 32 79 75 13 10 
69 120 112 105 114 101 115 58 32 84 104 117 44 32 48 49 32 74 97 110 32 49 57 55 48 32 48 48 58 48 48 58 48 48 32 71 77 84 13 10 
83 101 116 45 67 111 111 107 105 101 58 32 74 83 69 83 83 73 79 78 73 68 61 49 113 49 50 112 109 118 102 109 53 49 103 99 59 112 97 116 104 61 47 13 10 
67 111 110 116 101 110 116 45 84 121 112 101 58 32 116 101 120 116 47 104 116 109 108 59 32 99 104 97 114 115 101 116 61 85 84 70 45 56 13 10 
67 111 110 116 101 110 116 45 76 101 110 103 116 104 58 32 52 52 13 10 
67 111 110 110 101 99 116 105 111 110 58 32 107 101 101 112 45 97 108 105 118 101 13 10 
83 101 114 118 101 114 58 32 74 101 116 116 121 40 54 46 48 46 50 41 13 10 
13 10 
123 34 109 115 103 34 58 34 -25 -101 -82 -27 -67 -107 -27 -120 -101 -27 -69 -70 -26 -120 -112 -27 -118 -97 33 34 44 34 115 117 99 99 101 115 115 34 58 116 114 117 101 44 34 98 102 108 97 103 34 58 34 49 34 125 0 
nRead : 266

nRead是自己加的,13='\r',10='\n',32=' '(space),在13,10行之後,就是Response Body內容,最後一位長度值必須爲0,表示實體結束。


2.爲了解決上面提到的問題,Http新增了新的機制:不依賴頭部長度的信息,也能知道實體邊界。這就是Transfer-Encoding: chunked。在加入Transfer-Encoding: chunked後,代表報文采用了分塊編碼。分塊編碼的格式也很簡單,首先是長度值獨佔一行,長度不包括結尾的\r\n。最後一個分塊長度值必須爲0,對應的分塊沒有數據,表示實體結束。附一段響應體:

72 84 84 80 47 49 46 49 32 50 48 48 32 79 75 13 10 
83 101 114 118 101 114 58 32 65 112 97 99 104 101 45 67 111 121 111 116 101 47 49 46 49 13 10 
83 101 116 45 67 111 111 107 105 101 58 32 74 83 69 83 83 73 79 78 73 68 61 53 55 56 50 65 70 52 53 51 56 49 70 67 51 69 54 54 56 49 65 54 69 69 65 70 48 49 51 70 52 51 54 59 32 80 97 116 104 61 47 13 10 
67 111 110 116 101 110 116 45 84 121 112 101 58 32 116 101 120 116 47 104 116 109 108 59 99 104 97 114 115 101 116 61 117 116 102 45 56 13 10 
84 114 97 110 115 102 101 114 45 69 110 99 111 100 105 110 103 58 32 99 104 117 110 107 101 100 13 10 
68 97 116 101 58 32 87 101 100 44 32 49 49 32 74 97 110 32 50 48 49 55 32 48 55 58 50 51 58 52 55 32 71 77 84 13 10 
13 10 
51 56 13 10 
123 34 98 102 108 97 103 34 58 34 49 34 44 34 109 115 103 34 58 34 -25 -101 -82 -27 -67 -107 -27 -120 -101 -27 -69 -70 -26 -120 -112 -27 -118 -97 33 34 44 34 115 117 99 99 101 115 115 34 58 116 114 117 101 125 13 10 
0 
nRead : 277

51 56 代表下面分塊長度,最後分塊值爲0,代表實體結束。

Tomcat中默認用的是Transfer-Encoding: chunked,但是可以手動處理成Content-Length模式:response.addHeader("Content-Length", "100");

解析算法:

知道了HTTP協議的基本格式之後,就可以針對HTTP的數據結構進行解析處理。Socket流和文件流不一樣,文件流很容易知道文件末尾,到了文件末尾,直接把流close就結束了。但是Socket流不一樣,因爲連接一直保持,你無法知道它什麼時候結束,流也一直保持阻塞。HTTP協議就約定了數據傳輸的協議,比如用Content-Length獲取實體長度,用chunked分塊的第一行確定下一塊讀取的長度,這樣服務端就可以有目的性的讀取數據。

具體實現:JDK中在用HttpURLConnection處理http請求的時候,當返回類型爲chunked模式的時候,JDK調用ChunkedInputStream去處理流的數據;當返回類型爲Content-Length模式的時候,JDK調用KeepAliveStream處理流的數據。

順便說下個人覺得HTTP/1.1的最大問題,一個TCP鏈路同時只能傳輸一個HTTP請求。這意味着完成相應之前,這個連接不能用於其他請求。


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章