HTTP: The Definitive Guide
第一部分 HTTP: Web 的基础
第1章 HTTP 概述
- HTTP 客户端和 HTTP 服务器共同构成了万维网的基本组件。
- 服务器资源名被称为
统一资源标识符(Uniform Resource Identifier, URI)
。 统一资源定位符(URL)
是资源标识符最常见的形式。- 一个 HTTP 事务由一条(从客户端发往服务器的)请求命令和一个(从服务器发回客户端的)响应结果组成。
PUT
: 将来自客户端的数据存储到一个命名的服务器资源中去HEAD
仅发送命名资源响应中的 HTTP 首部- TCP 为 HTTP 提供了:
- 无差错的数据传输
- 按序传输(数据总是会按照发送的顺序到达)
- 未分段的数据流(可以在任意时刻以任意尺寸将数据发送出去)。
域名服务
: Domain Name Service, DNS- Telnet 程序可以将键盘连接到某个目标 TCP 端口, 并将此 TCP 端口的输出回送到显示屏上。如果想要更灵活的工具, 可以试试nc(netcat)。通过nc可以很方便地操纵基于UDP和TCP的流量(包括HTTP)
- Web 缓存或代理缓存是一种特殊的 HTTP 代理服务器, 可以将经过代理传送的常用文档复制保存起来。
- HTTP 隧道通常用来在一条或多条 HTTP 连接上转发非 HTTP 数据,转发时不会窥探数据。
第2章 URL 与资源
- URL 提供了一种统一的资源命名方式:
方案://服务器位置/路径
的结构。 - 片段 #, 在 URL 片段的上下文中, 服务器会发送整个对象, 由 Agent 代理将片段标识符应用于资源。
第3章 HTTP 报文
- 状态码:
- 200 到 299 之间的状态码表示成功。
- 300 到 399 之间的代码表示资源已经被移走了。
- 400 到 499 之间的状态码表示客户端的请求出错了。
- 500 到 599 之间的代码表示服务器出错了。
- HTTP 的版本号不会被当作小数处理。如 HTTP/2.22 就比 HTTP/2.3 的版本要高。
- GET 方法和 HEAD 方法都被认为是安全的, 这就意味着使用 GET 或 HEAD 方法的 HTTP 请求都不会产生什么动作。
- TRACE 方法允许客户端在最终将请求发送给服务器时,看看它变成了什么样子。行程最后一站的服务器会弹回一条 TRACE 响应, 并在响应主体中携带它收到的原始请求报文。
- Location : 告知客户端实体实际上位于何处;用于将接收端定位到资源的位置上(URL)去。
第4章 连接管理
- TCP流是分段的、由IP分组传送
<源 IP 地址、 源端口号、 目的 IP 地址、 目的端口号>
这4个值一起唯一地定义了一条连接。两条不同的 TCP 连接不能拥有4个完全相同的地址组件值。- 与建立 TCP 连接, 以及传输请求和响应报文的时间相比,事务处理时间可能是很短的。如果连接只用来传送少量数据,这些交换过程就会严重降低 HTTP 的性能。
- HTTP 事务的时延有以下几种主要原因。
- DNS 解析地址可能会花费数十秒的时间(缓存)
- 每条新的 TCP 连接都会有连接建立时延。(最多一两秒)
- 客户端请求传输
- 服务器回送响应
- TCP 网络时延的大小取决于硬件速度、网络和服务器的负载,请求和响应报文的尺寸, 以及客户端和服务器之间的距离。
TCP 慢启动
。 TCP 连接会随着时间进行自我“调谐”, 起初会限制连接的最大速度, 如果数据成功传输, 会随着时间的推移提高传输的速度。这种调谐被称为TCP 慢启动(slow start)
, 用于防止因特网的突然过载和拥塞。- 提高 HTTP 连接性能的几个技术:
- 并行连接
- 持久连接
- 管道化连接
- 复用连接
- 并行连接的速度可能会更快,但不一定总是更快。受限于客户端带宽和服务器并发能力。打开大量连接会消耗很多内存资源, 从而引发自身的性能问题。实际上, 浏览器确实使用了并行连接,但它们会将并行连接的总数限制为一个较小的值(通常是4个)。服务器可以随意关闭来自特定客户端的超量连接。
- 在事务处理结束之后仍然保持在打开状态的 TCP 连接被称为持久连接。重用已对目标服务器打开的持久连接,就可以避开缓慢的连接建立阶段。而且,已经打开的连接还可以避免慢启动的拥塞适应阶段,以便更快速地进行数据的传输。
- 持久连接与并行连接配合使用可能是最高效的方式。持久连接有两种类型: 比较老的 HTTP/1.0+ “keep-alive” 连接, 以及现代的 HTTP/1.1 “persistent” 连接。
- HTTP/1.1 持久连接在默认情况下是激活的。要在事务处理结束之后将连接关闭, HTTP/1.1 应用程序必须向报文中显式地添加一个 Connection:close 首部。
- 如果一个事务,不管是执行一次还是很多次,得到的结果都相同,这个事务就是幂等的。实现者们可以认为 GET、HEAD、PUT、DELETE、TRACE 和 OPTIONS 方法都共享这一特性。要发送一条非幂等请求,就需要等待来自前一条请求的响应状态。
- 如果另一端向你已经关闭的输入信道发送数据,操作系统就会向另一端的机器回送一条 TCP “连接被对端重置”(Reset by peer)的报文。大部分操作系统都会将这种情况作为很严重的错误来处理,删除对端还未读取的所有缓存数据。
第二部分 HTTP 结构
第5章 Web 服务器
- 流行的通用日志格式的第二个字段中就包含了每条 HTTP 请求的 ident 用户名。
- 进程是一个独立的程序控制流, 有自己的变量集。线程是一种更快、更高效的进程版本。
- 负载均衡: 如果一个超载的服务器收到一条请求,服务器可以将客户端
重定向
到一个负载不太重的服务器上去。
第6章 代理
- 代理连接的是两个或多个使用相同协议的应用程序,而网关连接的则是两个或多个使用不同协议的端点。网关扮演的是“协议转换器”的角色。
- 反向代理: 代理可以假扮 Web 服务器。这些被称为替代物或反向代理的代理接收发给 Web 服务器的真实请求,但与 Web 服务器不同的是,它们可以发起与其他服务器的通信,以便按需定位所请求的内容。
- 自动扩展机制: 很多浏览器会尝试着加入前缀 www. 和后缀 .com, 以防止用户只输入了常见 Web 站点名的中间部分。
- Max-Forwards 请求首部字段包含了一个整数,用来说明这条请求报文还可以被转发的次数。如果 Max-Forwards 的值为零, 那么即使接收者不是原始服务器,它也必须将 TRACE 报文回送给客户端,而不应该继续转发。
第7章 缓存
- Web 缓存是可以自动保存常见文档副本的 HTTP 设备。
- 将
If-Modified-Since
首部添加到 GET 请求中去,就可以告诉服务器,只有在缓存了对象的副本之后,又对其进行了修改的情况下,才发送此对象。 - 对象在中等规模的 Web 缓存来说,40% 的命中率是很合理的。
- 客户端有一种方法可以判断响应是否来自缓存, 就是使用 Date 首部。将响应中的 Date 首部的值与当前时间进行比较,如果响应中的日期值比较早,客户端通常就可以认为这是一条缓存的响应。客户端也可以通过 Age 首部来检测缓存的响应,通过这个首部可以分辨出这条响应的使用期。
- 缓存不应该调整 Date 首部。Date 首部表示的是原始服务器最初产生这个对象的日期。
- 对一条HTTP GET 报文的基本缓存处理过程包括7个步骤:
- 接收——缓存从网络中读取请求报文
- 解析——缓存对报文进行解析, 提取 URL 和各种首部
- 查询——缓存查询是否有本地副本可用,没有就获取一份副本并保存到本地
- 新鲜度检测——缓存查看已缓存的副本是否足够新鲜,如果不是,就询问服务器是否有更新。
- 创建响应——缓存会用新的首部和已缓存的主体来构建一条响应报文
- 发送——缓存通过网络将响应发回给客户端
- 日志——记录日志来描述这个事务
- 服务器用 HTTP/1.0+ 的 Expires 首部或 HTTP/1.1 的 Cache-Control: max-age 响应首部来指定过期日期。Expires 指定了一个绝对的过期日期。max-age 值定义了文档的最大使用期。
- 标识为
Cache-Control: no-store
的响应会禁止缓存对响应进行复制。 标识为Cache-Control: no-cache
的响应实际上是可以存储在本地缓存区中的。只是在与原始服务器进行新鲜度再验证之前,缓存不能将其提供给客户端使用。
第8章 集成点:网关、隧道及中继
- 网关(gateway)可以作为某种翻译器使用,它抽象出了一种能够到达资源的方法。
- Web 网关在一侧使用 HTTP 协议,在另一侧使用另一种协议。可以用一个斜杠来分隔客户端和服务器端协议,如: <客户端协议>/<服务器端协议>。
服务器端协议>客户端协议>
- 服务器端网关通过 HTTP 与客户端对话, 通过其他协议与服务器通信(HTTP/*)
- 客户端网关通过其他协议与客户端对话,通过 HTTP 与服务器通信(*/HTTP)。
- CGI: 通用网关接口(Common Gateway Interface, CGI)。CGI 应用程序是独立于服务器的,所以,几乎可以用任意语言来实现。
- CONNECT 方法请求隧道网关创建一条到达任意目的的服务器和端口的 TCP 连接,并对客户端和服务器之间的后继数据进行盲转发。
- 对任何大规模部署来说, 都要非常认真地考虑使用真正的、完全遵循 HTTP 的代理服务器。
第9章 Web机器人
- 穷举搜索 URL 列表是根本不可能的。机器人至少要用到搜索树或散列表,以快速判定某个 URL 是否被访问过。用一个散列函数将每个 URL 都转换成一个定长的数字,这个数字在数组中有个相关的“存在位”。
- 以广度优先的方式来调度 URL 去访问 Web 站点, 就可以将环路的影响最小化。
第10章 HTTP-NG
- HTTP-NG 的主题: 模块化及功能增强。
- HTTP-NG 建议将协议模块化为三层, 而不是将连接管理、报文处理、服务器处理逻辑和协议方法都混合在一起:
- 第一层, 报文传输层,这一层不考虑报文的功能,而是致力于端点间报文的不透明传输。
- 第二层,远程调用层, 定义了请求/响应的功能,客户端可以通过这些功能调用对服务器资源的操作。
- 第三层,Web应用层,提供了大部分的内容管理逻辑。
第三部分:识别、认证与安全
第11章 客户端识别与 cookie 机制
- 胖URL: 一种在 URL 中 嵌入识别信息的技术。cookie: 一种功能强大且高效的持久身份识别技术。
- User-Agent 首部可以将用户所用浏览器的相关信息告知服务器,包括程序的名称和版本,通常还包含操作系统的相关信息。
- 在 HTTP 首部并不提供客户端的 IP 地址,但 WEB 服务器可以找到承载 HTTP 请求的 TCP 连接另一端的 IP 地址。有些代理为了绕过这个问题会添加特殊的 Client-IP 或 X-Forwarded-For 扩展首部来保存原始的 IP 地址。
- 可以笼统地将 cookie 分为两类: 会话 cookie 和持久 cookie。会话 cookie 是一种临时 cookie, 它记录了用户访问站点时的设置和偏好。用户退出浏览器时,会话 cookie 就被删除了。通常会用持久 cookie 维护某个用户会周期性访问的站点的配置文件或登录名。如果设置了 Discard 参数,或者没有设置 Expires 或 Max-Age 参数来说明扩展的过期时间, 这个 cookie 就是一个会话 cookie。
- 很多 Web 站点都会与第三方厂商达成协议, 由其来管理广告。这些广告被做得像 Web 站点的一个组成部分,而且它们确实发送了持久 cookie。
- cookie 规范版本 0 和版本 1 都不是作为 HTTP/1.1 规范的一部分提供的。
- Set-Cookie 的几个属性:
- Expires: 过期时间戳
- Domain: 可选, 浏览器只向指定域中的服务器主机名发送cookie。如果没有指定域,就默认为产生 Set-Cookie 响应的服务器的主机名。
- Path: 可选,通过这个属性可以为服务器上特定的文档分配 cookie
- Secure: 可选,如果包含了这一属性,就这有在 HTTP 使用 SSL 安全连接时才会发送 cookie.
- Discard: 可选,如果提供了这个属性,就会在客户端程序终止时, 指示客户端放弃这个 cookie.
- Max-Age: 可选, 这个属性的值是一个整数, 用于设置以秒为单位的 cookie 生存期.
第12章 基本认证机制
- 在基本认证中, Web 服务器可以拒绝一个事务, 质询客户端, 请用户提供有效的用户名和密码。服务器会返回 401 状态码。
- HTTP 基本认证将(由冒号分隔的)用户名和密码打包在一起,并用 Base-64 编码方式对其进行编码。
- 将基本认证与加密数据传输(比如 SSL)配合使用,向恶意用户隐藏用户名和密码,会使基本认证变得更加安全。
第13章 摘要认证
- 摘要认证遵循的箴言是:绝不通过网络发送密码。
- 摘要是“对信息主体的浓缩”。摘要是一种单向函数,主要用于将无限的输入值转换为有限的浓缩输出值。常见的摘要函数 MD5, 会将任意长度的字节序列转换为一个128位的摘要。
- 重放攻击指的就是有人将从某个事务中窃取的认证证书用于另一个事务。缓解这个问题的方法之一就是让服务器产生的随机数包含根据客户端 IP 地址、时间戳、资源 Etag 和私有服务器秘钥算出的摘要。
- 防止恶意代理攻击和中间人攻击的唯一简便的方式就是使用 SSL。
第14章 安全 HTTP
- HTTPS 在 HTTP 下面提供了一个传输级的密码安全层。
- 对称加密技术: 编码时使用的秘钥值和解码时的一样。流行的对称秘钥加密算法包括:DES、Triple-DES、RC2、RC4。对于对称秘钥加密技术,128 位的秘钥被认为是非常强大的。
- 公开秘钥非对称加密系统(如: RSA)所面临的共同挑战是,要确保即便有人拥有了下面所有的线索,也无法计算出保密的私有秘钥:
- 公开秘钥
- 一小段截下来的密文
- 一条报文及与之相关的密文
- 破解代码找到相应的私有密钥的难度仍相当于对一个极大的数进行质因数分解的困难程度。
- 公开密钥加密算法的计算可能会很慢。比较常见的做法是在两节点间通过便捷的公开密钥加密技术建立起安全通信,然后再用那条安全的通道产生并发送临时的随机对称密钥,通过更快的对称加密技术对其余的数据进行加密。
- 数字签名通常是用非对称公开密钥技术产生的。
- RSA加密系统中可以将解码函数 D 作为签名函数使用, 是因为 D 已经将私有密钥作为输入使用了。在 RSA 加密系统中, 以任意顺序应用 D 和 E 函数时,两者都会相互抵消。因此 E(D(stuff)) = stuff = D(E(stuff))。
- 服务器证书中包含很多字段,其中包括:
- Web 站点的名称和主机名
- Web 站点的公开密钥
- 签名颁发机构的名称
- 来自签名颁发机构的签名
- HTTPS 就是在安全的传输层上发送的 HTTP。它在将 HTTP 报文发送给 TCP 之前,先将其发送给了一个安全层,对其进行加密。HTTPS 将 HTTP 协议与一组强大的对称、非对称和基于证书的加密技术结合在一起,使得 HTTPS 不仅很安全,而且很灵活。
- SSL 是个二进制协议,与 HTTP 完全不同,其流量是承载在另一个端口上的(SSL 通常是由端口 443 承载的)。
- 客户端和服务端 SSL 握手的过程中,它们要完成以下工作:
- 交换协议版本号
- 选择一个两端都了解的密码(加密方法)
- 对两端的身份进行认证
- 生成临时的会话密钥,以便加密信道。
- 验证证书步骤如下:
- 日期检测
- 签名颁发者可信度检测
- 签名检测
- 站点身份检测: 为防止服务器复制其他人的证书,或拦截其他人的流量,大部分浏览器都会试着去验证证书中的域名与它们所对话的服务器的域名是否匹配。
- 只要客户端开始用服务器的公开密钥对发往服务器的数据进行加密,代理就再也不能读取 HTTP 首部了! 代理不能读取 HTTP 首部,就无法知道向何处转发。一种常见的技术就是 HTTPS SSL 隧道协议。 使用 HTTPS 隧道协议, 客户端首先要告知代理,它想要连接的安全主机和端口。这是在开始加密之前,以明文形式告知的。HTTP 通过新的名为 CONNECT 的扩展方法来发送明文形式的端点信息。CONNECT 方法会告诉代理,打开一条到所期望主机和端口号的连接。这项工作完成之后,直接在客户端和服务器之间以隧道形式传输数据。
第四部分: 实体、编码和国际化
第15章 实体和编码
- 缓存代理服务器通常不会为没有显式 Content-Length 首部的 HTTP 主体做缓存,以此来减小缓存已截尾报文报文的风险。
- 如果主体进行了内容编码, Content-Length 首部说明的就是编码(Encoded)后的主体的字节长度, 而不是未编码的原始主体的长度。
- 服务器使用 Content-MD5 首部发送对实体主体运行 MD5 算法的结果。Content-MD5 首部是在对内容做了所有需要的内容编码之后,还没有做任何传输编码之前,计算出来的。为了验证报文的完整性,客户端必须先进行传输编码的解码,然后计算所得到的未进行传输编码的实体主体的 MD5。
- MIME 类型由一个主媒体类型后面跟一条斜线以及一个子类型组成, 子类型用于进一步描述媒体类型。
- 内容编码服务器在编码后的报文中增加 Content-Encoding 首部,这样接收的应用程序就可以进行解码了。如果 HTTP 请求中没有包含 Accept-Encoding 首部,服务器就可以假设客户端能接收任何编码方式(等价于发送 Accept-Encoding: *)。客户端可以给每个钟编码附带 Q 值参数来说明编码的优先级。
- 经过内容编码的报文,只是对报文的实体部分进行了编码。而对于经过传输编码的报文来说,编码作用在整个报文上,报文自身的结构发生了改变。
- 分块编码是一种传输编码,因此是报文的属性,而不是主体的属性。服务器可以用大小为0的块作为主体结束的信号。
- HTTP 允许客户端实际上只请求文档的一部分,或者说某个范围(首部: Range:bytes=…), 假设客户端同时连接到多个服务器,为了加速下载文档而从不同的服务器下载同一个文档的不同部分。对于客户端在一个请求内请求多个不同范围的情况,返回的响应也是单个实体,它有一个多部分主题及 Content-Type: multipart/byteranges 首部。Range 首部在流行的点对点(Peer to Peer, P2P)文件共享客户端软件中得到广泛应用。
### 第16章 国际化
- 服务器通过 HTTP 协议的 Content-Type 首部中的 charset 参数和 Content-Language 首部告知客户端文档的字母表和语言。
- HTTP 字符集(charset)的值说明如何把实体内容的二进制码转换为特定字母表中的字符。
- 国际化字符系统的关键目标是把语义(字母)和表示(图形化的显示形式)隔离开来。HTTP 只关心字符数据和相关语言及字符集标签的传输。字符形状的显示是由用户的图形显示软件(包括浏览器、操作系统、字体等)完成的。
- 所有的标记都是不区分大小写的,但是,习惯上用全小写来表示一般的语言,而用全大写来表示特定的国家。第一个子标记通常是标准化的语言记号, 第二个子标记通常是标准化的国家记号。例如: Accept-Language: zh-CN;
- HTTP 首部必须由 US-ASCII 字符集中的字符构成。
第17章 内容协商与转码
- HTTP 协议定义了质量值,允许客户端为每种偏好类别列出多种选项,并为每种偏好选项关联一个优先次序。例如,客户端可以发送下列形式的 Accept-Language 首部: Accept-Language: en;a=0.5, fr;q=0.0, nl;q=1.0;
第五部分 内容发布与分发
第18章 Web 主机托管
- 大多数浏览器只是把 URL 的路径发送给服务器,关键的虚拟主机名信息被其丢掉了。替代的方法是, 把主机名(和端口号)放在所有请求的 Host 扩展首部中传送。
- 如果客户端显示地使用代理服务器,客户端就必须把原始服务器,而不是代理服务器的名字和端口放在 Host 首部中。
- HTTP/1.1 的 Web 服务器必须用 400 状态码来响应所有缺少 Host 首部字段的 HTTP/1.1 请求报文。
- CDN: 内容分发网络(CDN)就是对特定内容进行分发的专门网络。
- 服务器集群和分布式代理缓存或反向代理服务器分散了网络流量,可以避免阻塞。另外,加速网站访问的另一种方法是对内容进行编码以便更快地传输。
第19章 发布系统
- RPC: Remote Procedure Call, 远程过程调用。
第20章 重定向与负载均衡
- 服务器、代理、缓存和网关对客户端来说都是服务器。
- 通用的重定向方法:
- HTTP 重定向
- DNS 重定向
- IP 地址转发
- 最常见的重定向技术之一是 DNS 轮转。大多数 DNS 客户端只会使用多地址集中的第一个地址。为了均衡负载,大多数 DNS 服务器会在每次完成查询之后对地址进行轮转。
- Web 浏览器客户端连接到代理的方法: 显式的浏览器配置,动态自动配置以及透明拦截。 PAC 的基本思想是让浏览器去获取一个称为 PAC 的特殊文件, 这个文件说明了每个 URL 所关联的代理。PAC 文件是个 JavaScript 文件, 其中必须定义函数: function FindProxyForURL(url, host)
- ICP(因特网缓存协议) 允许缓存在其兄弟缓存中查找命中的内容。HTCP(超文本缓存协议)允许兄弟缓存之间通过 URL 和所有的请求及响应首部来相互查询文档是否存在,以降低错误命中的可能。
第21章 日志记录与使用情况跟踪
- 日志的记录出于两种原因: 查找服务器或代理中存在的问题, 或者是生成 Web 站点访问方式的统计信息。
- 通常, 用户设置都不清楚他们的 HTTP 事务已被记录——实际上,很多用户可能甚至都不知道他们访问 Web 上的内容时是在使用 HTTP 协议。