引子
HTTP頭部Content-Length用于描述HTTP消息實體的傳輸長度,瀏覽器對比Content-Length和HTTP請求或者響應body長度判斷一次HTTP傳輸過程,以獨立于TCP長連接。但是如果Content-Length與HTTP請求或者響應body長度不一致時,本文深入實踐瀏覽器怎么處理這些異常情況。
Content-Length和Content-Type焦不離孟,關于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紅色方框的域名,對應圖2綠色方框的抓包,點擊“show name”按鈕,前端發送ajax請求服務端,對應圖2藍色方框的抓包,即使服務端不顯示指定HTTP Response頭部Content-Length,實際的HTTP Response頭部Content-Length: 16
,如圖1紅色方框,對應圖2紅色方框的seq 712:848
,其中136字節不只包含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”按鈕,前端發送ajax請求服務端,服務端返回HTTP Response頭部Content-Length: 15
,對應圖2紅色方框的seq 712:847
,相比圖2的紅色方框少一個字節。導致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”按鈕,前端發送ajax請求服務端,服務端返回HTTP Response頭部Content-Length: 17
,但是實際上HTTP Response body長度為16字節,對應圖2紅色方框的seq 712:848
,與圖2紅色方框一致。
因為HTTP Response頭部Content-Length: 17
,所以瀏覽器一直等待第17個字節,不會解析實際上已經接收完服務端發送的HTTP Response body(16字節長度),等待一段時間后瀏覽器報net::ERR_CONTENT_LENGTH_MISMATCH
,同時圖5的Name:
為空。
HTTP首部Content-Length使用場景
當客戶端向服務器請求一個靜態頁面或者一張圖片時,服務器可以很清楚的知道內容大小,然后通過Content-length消息首部字段告訴客戶端需要接收多少數據。
HTTP首部定義Connection: keep-alive后,客戶端、服務端怎么知道本次傳輸結束呢?靜態頁面通過Content-Length提前告知對方數據傳輸大小。關于HTTP首部Connection詳解請查看拙作HTTP首部Connection實踐。