引子
HTTP頭部Content-Length用于描述HTTP消息實體的傳輸長度,瀏覽器對比Content-Length和HTTP請求或者響應(yīng)body長度判斷一次HTTP傳輸過程,以獨立于TCP長連接。但是如果Content-Length與HTTP請求或者響應(yīng)body長度不一致時,本文深入實踐瀏覽器怎么處理這些異常情況。
Content-Length和Content-Type焦不離孟,關(guān)于Content-Type可以參考拙作HTTP Content-Type深入實踐。
情況1:HTTP Response頭部不顯示指定Content-Length
后端Spring boot+Java代碼:
package com.demo.web.http;
import com.google.common.collect.Maps;
import com.google.gson.Gson;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Map;
@Controller
@RequestMapping("http")
public class ContentTypeController {
private final static Gson GSON = new Gson();
@RequestMapping("/content-type-response")
public String contentType4Response() {
return "http/content-type-response";
}
@RequestMapping("content-type-response.json")
@ResponseBody
public void json4Response(HttpServletResponse response) throws IOException {
Map<String, Object> map = Maps.newHashMap();
map.put("name", "datou");
response.setContentType("application/json;charset=utf-8");
response.getWriter().write(GSON.toJson(map));
}
}
前端html+css+javascript+jquery代碼:
<!DOCTYPE HTML>
<html>
<head>
<title>HTTP response Content-Type Demo</title>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<script src="http://code.jquery.com/jquery-1.4.2.min.js" type="text/javascript"></script>
</head>
<body>
<p>Name: <span id="name"></span></p>
<button onclick="show()">show name</button>
<script>
function show() {
$.get("content-type-response.json", function (data) {
console.log(data);
$("#name").text(data.name);
});
}
</script>
</body>
</html>
訪問圖1紅色方框的域名,對應(yīng)圖2綠色方框的抓包,點擊“show name”按鈕,前端發(fā)送ajax請求服務(wù)端,對應(yīng)圖2藍色方框的抓包,即使服務(wù)端不顯示指定HTTP Response頭部Content-Length,實際的HTTP Response頭部Content-Length: 16,如圖1紅色方框,對應(yīng)圖2紅色方框的seq 712:848,其中136字節(jié)不只包含HTTP Response body。


情況2:HTTP Response頭部顯示指定Content-Length等于實際Response body長度
后端Spring boot+Java代碼,顯示指定Content-Length:response.setContentLength(16);
package com.demo.web.http;
import com.google.common.collect.Maps;
import com.google.gson.Gson;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Map;
@Controller
@RequestMapping("http")
public class ContentTypeController {
private final static Gson GSON = new Gson();
@RequestMapping("/content-type-response")
public String contentType4Response() {
return "http/content-type-response";
}
@RequestMapping("content-type-response.json")
@ResponseBody
public void json4Response(HttpServletResponse response) throws IOException {
Map<String, Object> map = Maps.newHashMap();
map.put("name", "datou");
response.setContentType("application/json;charset=utf-8");
response.setContentLength(16);
response.getWriter().write(GSON.toJson(map));
}
}
訪問前端頁面與情況1一樣,效果如圖1和圖2所示。
情況3:HTTP Response頭部顯示指定Content-Length小于實際Response body長度
后端Spring boot+Java代碼,顯示指定Content-Length:response.setContentLength(15);
訪問圖3域名,點擊“show name”按鈕,前端發(fā)送ajax請求服務(wù)端,服務(wù)端返回HTTP Response頭部Content-Length: 15,對應(yīng)圖2紅色方框的seq 712:847,相比圖2的紅色方框少一個字節(jié)。導致Response body不完整,前端也就不能解碼Content-Type:application/json;charset=UTF-8的字符串為json對象,所以圖3的Name:為空。


情況4:HTTP Response頭部顯示指定Content-Length大于實際Response body長度
后端Spring boot+Java代碼,顯示指定Content-Length:response.setContentLength(17);
訪問圖5域名,點擊“show name”按鈕,前端發(fā)送ajax請求服務(wù)端,服務(wù)端返回HTTP Response頭部Content-Length: 17,但是實際上HTTP Response body長度為16字節(jié),對應(yīng)圖2紅色方框的seq 712:848,與圖2紅色方框一致。
因為HTTP Response頭部Content-Length: 17,所以瀏覽器一直等待第17個字節(jié),不會解析實際上已經(jīng)接收完服務(wù)端發(fā)送的HTTP Response body(16字節(jié)長度),等待一段時間后瀏覽器報net::ERR_CONTENT_LENGTH_MISMATCH,同時圖5的Name:為空。


HTTP首部Content-Length使用場景
當客戶端向服務(wù)器請求一個靜態(tài)頁面或者一張圖片時,服務(wù)器可以很清楚的知道內(nèi)容大小,然后通過Content-length消息首部字段告訴客戶端需要接收多少數(shù)據(jù)。
HTTP首部定義Connection: keep-alive后,客戶端、服務(wù)端怎么知道本次傳輸結(jié)束呢?靜態(tài)頁面通過Content-Length提前告知對方數(shù)據(jù)傳輸大小。關(guān)于HTTP首部Connection詳解請查看拙作HTTP首部Connection實踐。