分块传输编码(Chunked transfer encoding)是超文本传输协议(HTTP)中的一种数据传输机制,允许 HTTP 由应用服务器发送给客户端应用( 通常是网页浏览器)的数据可以分成多个部分。分块传输编码只在 HTTP 协议 1.1 版本(HTTP/1.1)中提供。
通常,HTTP 应答消息中发送的数据是整个发送的,Content-Length 消息头字段表示数据的长度。数据的长度很重要,因为客户端需要知道哪里是应答消息的结束,以及后续应答消息的开始。然而,使用分块传输编码,数据分解成一系列数据块,并以一个或多个块发送,这样服务器可以发送数据而不需要预先知道发送内容的总大小。通常数据块的大小是一致的,但也不总是这种情况。
HTTP 1.1 引入分块传输编码提供了以下几点好处:
分块就是把数据分成一块一块的再发送出去,浏览器收到后再组装起来,这种 “化整为零” 的思路在 HTTP 协议里就是 “chunked” 分块传输编码,在响应报文里用头字段 “Transfer-Encoding: chunked” 来表示,意思是报文里的 body 部分不是一次性发过来的,而是分成了许多的块(chunk)逐个发送。
分块传输也可以用于 “流式数据”,例如由数据库动态生成的表单页面,这种情况下 body 数据的长度是未知的,无法在头字段 “Content-Length” 里给出确切的长度,所以也只能用 chunked 方式分块发送。
“Transfer-Encoding: chunked” 和 “Content-Length” 这两个字段是互斥的,也就是说响应报文里这两个字段不能同时出现,一个响应报文的传输要么是长度已知,要么是长度未知(chunked),这一点你一定要记住。
下面我们来看一下分块传输的编码规则,其实也很简单,同样采用了明文的方式,很类似响应头。
1.每个分块包含两个部分,长度头和数据块;
2.长度头是以 CRLF(回车换行,即\r\n)结尾的一行明文,用 16 进制数字表示长度;
3.数据块紧跟在长度头后,最后也用 CRLF 结尾,但数据不包含 CRLF;
4.最后用一个长度为 0 的块表示结束,即 “0\r\n\r\n”。
有了分块传输编码,服务器就可以轻松地收发大文件了,但对于上 G 的超大文件,还有一些问题需要考虑。比如,你在看当下正热播的某穿越剧,想跳过片头,直接看正片,或者有段剧情很无聊,想拖动进度条快进几分钟,这实际上是想获取一个大文件其中的片段数据,而分块传输并没有这个能力。
“范围请求(range requests)的概念,允许客户端在请求头里使用专用字段来表示只获取文件的一部分。服务器会发送专用字段 Accept-Ranges: bytes” 明确告知客户端:“我是支持范围请求的”,范围请求不是 Web 服务器必备的功能,可以实现也可以不实现。
如果不支持的话该怎么办呢?服务器可以发送 “Accept-Ranges: none”,或者干脆不发送 “Accept-Ranges” 字段,这样客户端就认为服务器没有实现范围请求功能,只能老老实实地收发整块文件了。
客户端会发送 Range 请求字段,格式是 “bytes=x-y”,其中 x 和 Y 是以字节为单位的数据范围。Range 的格式也很灵活,起点 x 和终点 y 可以省略,能够很方便地表示正数或者倒数的范围。
假设文件是 100 个字节,那么:
例如客户端请求:
GET /16-2 HTTP/1.1
Host: www.haicoder.net
Range: bytes=0-31
服务端响应:
HTTP/1.1 206 Partial Content
Content-Length: 32
Accept-Ranges: bytes
Content-Range: bytes 0-31/96
服务端收到 Range 字段后,要做 4 件事: