实体和编码
HTTP 要确保它所承载的“货物”满 足以下条件:
- 可以被正确地识别(通过 Content-Type 首部说明媒体格式,ContentLanguage 首部说明语言),以便浏览器和其他客户端能正确处理内容。
- 可以被正确地解包(通过 Content-Length 首部和 Content-Encoding 首部)。
- 是最新的(通过实体验证码和缓存过期控制)
- 符合用户的需要(基于 Accept 系列的内容协商首部)
- 在网络上可以快速有效地传输(通过范围请求、差异编码以及其他数据压缩方法)
- 完整到达、未被篡改(通过传输编码首部和 Content-MD5 校验和首部)
报文是箱子,实体是货物
如果把 HTTP 报文想象成因特网货运系统中的箱子,那么 HTTP 实体就是报文中实 际的货物。图 15-1 展示了一个简单的实体,装在 HTTP 响应报文中。
实体主体 实体主体中就是原始货物啦。任何其他描述性的信息都包含在首部中。因为货物(也就是实体主体)只是原始数据,所以需要实体首部来描述数据的意义。例如,Content-Type 实体首部告诉我们如何去解释数据(是图像还是文本等),而 Content-Encoding 实体首部告诉我们数据是不是已被压缩或者重编码。
Content-Length: 实体的大小
Content-Length 首部指示出报文中实体主体的字节大小。这个大小是包含了所有 内容编码的,比如,对文本文件进行了 gzip 压缩的话,Content-Length 首部就是 压缩后的大小,而不是原始大小。
检测截尾
HTTP 的早期版本采用关闭连接的办法来划定报文的结束。但是,没有 ContentLength 的话,客户端无法区分到底是报文结束时正常的连接关闭,还是报文传输中由于服务器崩溃而导致的连接关闭。客户端需要通过 Content-Length 来检测报文截尾。
错误的Content-Length
错误的 Content-Length 比缺少 Content-Length 还要糟糕。因为某些早期的客户端和服务器在 Content-Length 计算上存在一些众所周知的错误,有些客户端、 服务器以及代理中就包含了特别的算法,用来检测和纠正与有缺陷服务器的交互过程。HTTP/1.1 规定用户 Agent 代理应该在接收且检测到无效长度时通知用户。
Content-Length与持久连接
Content-Length 首部对于持久连接是必不可少的。如果响应通过持久连接传送, 就可能有另一条 HTTP 响应紧随其后。客户端通过 Content-Length 首部就可以知道报文在何处结束,下一条报文从何处开始。因为连接是持久的,客户端无法依赖连接关闭来判别报文的结束。如果没有 Content-Length 首部,HTTP 应用程序就不知道某个实体主体在哪里结束,下一条报文从哪里开始。
内容编码
HTTP 允许对实体主体的内容进行编码,比如可以使之更安全或进行压缩以节省空间(本章稍后将详细解释压缩的问题)。如果主体进行了内容编码,Content-Length 首部说明的就是编码后(encoded)的主体的字节长度,而不是未编码的原始主体的长度。
确定实体主体长度的规则
下面列出的规则说明了在若干不同的情况下如何正确计算主体的长度和结束位置。 这些规则应当按顺序应用,谁先匹配就用谁。
- 如果特定的 HTTP 报文类型中不允许带有主体,就忽略 Content-Length 首部,它是对(没有实际发送出来的)主体进行计算的。这种情况下,ContentLength 首部是提示性的,并不说明实际的主体长度。
- 如果报文中含有描述传输编码的 Transfer-Encoding 首部(不采用默认的 HTTP“恒等”编码),那实体就应由一个称为“零字节块”(zero-byte chunk) 的特殊模式结束,除非报文已经因连接关闭而结束。
- 如果报文中含有 Content-Length 首部(并且报文类型允许有实体主体),而 且没有非恒等的 Transfer-Encoding 首部字段,那么 Content-Length 的值 就是主体的长度。如果收到的报文中既有 Content-Length 首部字段又有非恒 等的 Transfer-Encoding 首部字段,那就必须忽略 Content-Length,因为 传输编码会改变实体主体的表示和传输方式(因此可能就会改变传输的字节数)。
- 如果报文使用了 multipart/byteranges(多部分 / 字节范围)媒体类型,并且没有用 Content-Length 首部指出实体主体的长度,那么多部分报文中的每个部 分都要说明它自己的大小。这种多部分类型是唯一的一种自定界的实体主体类 型,因此除非发送方知道接收方可以解析它,否则就不能发送这种媒体类型。
- 如果上面的规则都不匹配,实体就在连接关闭的时候结束。实际上,只有服务器可以使用连接关闭来指示报文的结束。客户端不能用关闭连接来指示客户端报文的结束,因为这样会使服务器无法发回响应。
实体摘要
尽管 HTTP 通常都是在像 TCP/IP 这样的可靠传输协议之上实现的,但仍有很多因素会导致报文的一部分在传输过程中被修改,比如有不兼容的转码代理,或者中间代理有误,等等。为检测实体主体的数据是否被不经意(或不希望有)地修改,发送方可以在生成初始的主体时,生成一个数据的校验和,这样接收方就可以通过检查这个校验和来捕获所有意外的实体修改了。
服务器使用 Content-MD5 首部发送对实体主体运行 MD5 算法的结果。只有产生响 应的原始服务器可以计算并发送 Content-MD5 首部。为了验证报文的完整性,客户端必须先进行传输编码的解码,然后计算所得到 的未进行传输编码的实体主体的 MD5。
媒体类型和字符集
Content-Type 首部字段说明了实体主体的 MIME 类型。
文本的字符编码
Content-Type 首部还支持可选的参数来进一步说明内容的类型。charset(字符 集)参数就是个例子,它说明把实体中的比特转换为文本文件中的字符的方法: Content-Type: text/html; charset=iso-8859-4
多部分媒体类型
MIME 中的 multipart(多部分)电子邮件报文中包含多个报文,它们合在一起作 为单一的复杂报文发送。每一部分都是独立的,有各自的描述其内容的集;不同的 部分之间用分界字符串连接在一起。
HTTP 也支持多部分主体。不过,通常只用在下列两种情形之一:提交填写好的表格,或是作为承载若干文档片段的范围响应。
内容编码
HTTP 应用程序有时在发送之前需要对内容进行编码。例如,在把很大的 HTML 文 档发送给通过慢速连接连上来的客户端之前 , 服务器可能会对它进行压缩,这样有助于减少传输实体的时间。服务器还可以把内容搅乱或加密,以此来防止未经授权的第三方看到文档的内容。
内容编码过程
- 网站服务器生成原始响应报文,其中有原始的 Content-Type 和 ContentLength 首部。
- 内容编码服务器(也可能就是原始的服务器或下行的代理)创建编码后的报文。 编码后的报文有同样的 Content-Type 但 Content-Length 可能不同(比如 主体被压缩了)。内容编码服务器在编码后的报文中增加 Content-Encoding 首部,这样接收的应用程序就可以进行解码了。
- 接收程序得到编码后的报文,进行解码,获得原始报文。
内容编码(gzip)示例
内容编码类型
HTTP 定义了一些标准的内容编码类型,并允许用扩展编码的形式增添更多的编码。 由互联网号码分配机构(IANA)对各种编码进行标准化,它给每个内容编码算法分配了唯一的代号。Content-Encoding 首部就用这些标准化的代号来说明编码时使用的算法。
- 内容编码代号
这些算法中,gzip 通常是效率最高的,使用最为广泛。
Accept-Encoding首部
为了避免服务器使用客户端不支持的编码方式,客户端就把自己支持的内容编码方式列表放在请求的 Accept-Encoding 首部里发出去。如果 HTTP 请求中没有包含 Accept-Encoding 首部,服务器就可以假设客户端能够接受任何编码方式(等价 于发送 Accept-Encoding: * )。
传输编码和分块编码
前一节讨论的内容编码,是对报文的主体进行的可逆变换。内容编码是和内容的具体格式细节紧密相关的。例如,你可能会用 gzip 压缩文本文件,但不是 JPEG 文 件,因为 JPEG 这类东西用 gzip 压缩的不够好。
本节讨论传输编码。传输编码也是作用在实体主体上的可逆变换,但使用它们是由于架构方面的原因,同内容的格式无关。如图所示,使用传输编码是为了改变报文中的数据在网络上传输的方式。
- 内容编码和传输编码的对比
可靠传输
在 HTTP 中,只有少数一些情况下,所传输的报文主体可能 会引发问题。其中两种情况如下所述。
- 未知的尺寸 如果不先生成内容,某些网关应用程序和内容编码器就无法确定报文主体的最终 大小。通常,这些服务器希望在知道大小之前就开始传输数据。因为 HTTP 协议 要求 Content-Length 首部必须在数据之前,有些服务器就使用传输编码来发 354 372 | 第 15 章 送数据,并用特别的结束脚注表明数据结束。
- 安全性 你可以用传输编码来把报文内容扰乱,然后在共享的传输网络上发送。不过,由 于像 SSL 这样的传输层安全体系的流行,就很少需要靠传输编码来实现安全性了。
Transfer-Encoding首部
HTTP 协议中只定义了下面两个首部来描述和控制传输编码。
- Transfer-Encoding 告知接收方为了可靠地传输报文,已经对其进行了何种编码。
- TE 用在请求首部中,告知服务器可以使用哪些传输编码扩展。
下面的例子中,请求使用了 TE 首部来告诉服务器它可以接受分块编码
GET /new_products.html HTTP/1.1
Host: www.joes-hardware.com
User-Agent: Mozilla/4.61 [en] (WinNT; I)
TE: trailers, chunked
对它的响应中包含 Transfer-Encoding 首部,用于告诉接收方已经用分块编码对 报文进行了传输编码:
HTTP/1.1 200 OK
Transfer-Encoding: chunked
Server: Apache/3.0
HTTP/1.1 规定在 TE 首部和 Transfer-Encoding 首部中使用传输编码值。最新的 HTTP 规范只定义了一种传输编码,就是分块编码。
分块编码
分块编码把报文分割为若干个大小已知的块。块之间是紧挨着发送的,这样就不需 要在发送之前知道整个报文的大小了。
分块与持久连接
若客户端和服务器之间不是持久连接,客户端就不需要知道它正在读取的主体的长度,而只需要读到服务器关闭主体连接为止。
当使用持久连接时,在服务器写主体之前,必须知道它的大小并在 ContentLength 首部中发送。如果服务器动态创建内容,就可能在发送之前无法知道主体的长度。
分块编码为这种困难提供了解决方案,只要允许服务器把主体逐块发送,说明每块的大小就可以了。因为主体是动态创建的,服务器可以缓冲它的一部分,发送其大小和相应的块,然后在主体发送完之前重复这个过程。服务器可以用大小为 0 的块作为主体结束的信号,这样就可以继续保持连接,为下一个响应做准备。