HTTP的早期版本采用关闭连接的办法来划定报文的结束。但是,没有实体长度信息的话,客户端无法判断连接关闭到底是因为报文结束才关闭的还是因为服务器崩溃才关闭的。而且,对于现在常用的长连接(keep alive),更是需要一种确定实体长度的方法。
这就是Content-Length出现的原因。首部的这个属性可以指出了实体部分的字节长度,这样客户端就知道报文到什么时候结束了。
但是,有一种情况,使用持久连接(长连接)时可以没有Content-Length首部。即采用分块编码(chunk)的时候。使用分块编码的时候,数据是分块发送的,每块都有大小说明。有些动态内容生成可能比较耗时,服务器希望提高响应速度,就会选择边生成响应内容边发送的方式。但是,内容没有完全生成之前是无法计算Content-Length的,所以,取而代之的就是分块的方法。
那么现在,问题来了,怎样确定实体主体的长度呢?
主体定界规则
理论上,实体主体的长度应该按照下面的规则来进行匹配。
1. 判断是否是不允许带有主体的特定的HTTP报文类型。这种情况下,就算出现Content-Length,也应该忽略它。会有Content-Length出现但是又没有实体的报文有以下几个常见的例子:
l HEAD响应。服务器发送的首部等价于GET请求发送的首部,但是服务器不会发送实体。
l 1XX、204以及304响应也可以有提示性的Content-Length首部,但是也都没有实体。
2. 如果报文含有描述传输编码的Transfer-Encoding首部(不采用默认的HTTP“恒等”编码),那实体就应由一个称为“零字节块”的特殊模式结束。具体编码方式如下:
l 由一系列分块组成,每个分块包含一个长度值和该分块的数据。长度值是十六进制形式的数,并通过CRLF与数据分隔开。
l 分块中的数据的大小以字节计算,不包括长度值与数据之间的CRLF序列以及分块结尾的CRLF序列。分块之间以CRLF隔开。
l 最后一个分块有点特别,它的长度值为0,表示“主体结束”。
l 如果客户端的TE首部中说明它可以接受拖挂的话,就可以在分块的报文最后加上拖挂。拖挂的内容是可选的元数据,客户端并不一定需要理解和使用(也就是说,客户端可以忽略并丢弃拖挂中的内容)。
3. 如果报文中含有Content-Length首部,前两个条件都不匹配,那么Content-Length就是主体的长度。这意味着如果同时出现Content-Length和Transfer-Encoding首部的时候,就必须忽略Content-Length首部。
4. 如果报文使用了multipart/byteranges(多部分/字节范围)媒体类型,没有使用Content-Length首部指出实体主体长度,那么报文中的每部分都要说明它自己的大小。然而这里的多部分类型是唯一的一种自定界的实体主体类型,因此,除非发送方知道接收方可以解析它,否则就不能发送这种媒体类型。后面关于这点会详细描述。
5. 如果上面规则都不匹配,实体就在连接关闭的时候结束。但是这种主体定界方式只能服务器在发回响应的时候使用。否则,连接都关闭了,服务器怎么发回响应呢。
关于多部分媒体类型
Content-Type首部的值是标准化的MIME类型。MIME类型由一个主媒体类型(如:text,image或audio等)后面跟一个斜杠以及一个子类型组成。其中,多部分媒体类型的MIME类型表述为multipart/byteranges,它表明实体主体有若干部分,每个部分都包含了完整文档中不同的字节范围。
HTTP的多部分主体主要用在以下两种情况中:
l 提交填写好的表单;
l 作为承载若干文档片段的范围响应。
对于多部分表单提交,可以使用multipart/form-data类型提交,通过boundary来指定分界符。例如:
Content-Type:multipart/form-data; boundary=[abcdefg…]
同样,对于多部分范围响应,也可以使用boundary来进行定界。具体内容不在此讨论。