引子
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实践。