《HTTP 权威指南》Part 2 —— HTTP结构

485 阅读45分钟

Web 服务器

Web 服务器的实现

Web 服务器实现了 HTTP 和相关的 TCP 连接处理。负责管理 Web 服务器提供的资 源,以及对 Web 服务器的配置、控制及扩展方面的管理。

Web 服务器逻辑实现了 HTTP 协议、管理着 Web 资源,并负责提供 Web 服务器的 管理功能。Web 服务器逻辑和操作系统共同负责管理 TCP 连接。底层操作系统负责管理底层计算机系统的硬件细节,并提供了 TCP/IP 网络支持、负责装载 Web 资源的文件系统以及控制当前计算活动的进程管理功能。

Web服务器会做些什么

  • 建立连接——接受一个客户端连接,或者如果不希望与这个客户端建立连接,就将其关闭。
  • 接收请求——从网络中读取一条 HTTP 请求报文。
  • 处理请求——对请求报文进行解释,并采取行动。
  • 访问资源——访问报文中指定的资源。
  • 构建响应——创建带有正确首部的 HTTP 响应报文。
  • 发送响应——将响应回送给客户端。
  • 记录事务处理过程——将与已完成事务有关的内容记录在一个日志文件中。

接口客户端连接

客户端请求一条到 Web 服务器的 TCP 连接时,Web 服务器会建立连接,判断连接的另一端是哪个客户端,从 TCP 连接中将 IP 地址解析出来。

一旦新连接建立起来并被接受,服务器就会将新连接添加到其现存 Web 服务器连接列表中,做好监视连 接上数据传输的准备。

接受请求报文

连接上有数据到达时,Web 服务器会从网络连接中读取数据,并将请求报文中的内 容解析出来。 解析请求报文时,Web 服务器会:

  • 解析请求行,查找请求方法、指定的资源标识符(URI)以及版本号,各项之间由一个空格分隔,并以一个回车换行(CRLF)序列作为行的结束;
  • 读取以 CRLF 结尾的报文首部;
  • 检测到以 CRLF 结尾的、标识首部结束的空行(如果有的话);
  • 如果有的话(长度由 Content-Length 首部指定),读取请求主体。

Web 服务器需要从网络中读取数据,将部分报文数据临时存储在内存中,直到收到足以进行解析的数据并理解其意义为止。

处理请求

一旦 Web 服务器收到了请求,就可以根据方法、资源、首部和可选的主体部分来对 请求进行处理了。

对资源的映射及访问

Web 服务器是资源服务器。它们负责发送预先创建好的内容,比如 HTML 页面或 JPEG 图片,以及运行在服务器上的资源生成程序所产生的动态内容。

构建响应

一旦 Web 服务器识别出了资源,就执行请求方法中描述的动作,并返回响应报文。 响应报文中包含有响应状态码、响应首部,如果生成了响应主体的话,还包括响应主体。

发送响应

Web 服务器通过连接发送数据时也会面临与接收数据一样的问题。服务器可能有很多条到各个客户端的连接,有些是空闲的,有些在向服务器发送数据,还有一些在向客户端回送响应数据。

  • 对非持久连接而言, 服务器应该在发送了整条报文之后,关闭自己这一端的连接。
  • 对持久连接来说,连接可能仍保持打开状态,在这种情况下,服务器要特别小心, 要正确地计算 Content-Length 首部,不然客户端就无法知道响应什么时候结束了

记录日志

最后,当事务结束时,Web 服务器会在日志文件中添加一个条目,来描述已执行的事务。大多数 Web 服务器都提供了几种日志配置格式。

代理

Web 代理(proxy)服务器是网络的中间实体。代理位于客户端和服务器之间,扮演 “中间人”的角色,在各端点之间来回传送 HTTP 报文。

Web 的中间实体

私有代理和共享代理

  • 单个客户端专用的代理被称为私有代理
  • 众多客户端共享的代理被称为公共代理。大多数代理都是公共的共享代理。集中式代理的成本效率更高,更容易管理。

代理与网关的对比

严格来说,代理连接的是两个或多个使用相同协议的应用程序,而网关连接的则是两个或多个使用不同协议的端点。网关扮演的是“协议转换器”的角色,即使客户端和服务器使用的是不同的协议,客户端也可以通过它完成与服务器之间的事务处理。

为什么使用代理

代理服务器可以实现各种时髦且有用的功能。

  • 改善安全性,提高性能,节省费用。
  • 可以看到并接触到所有流过的 HTTP 流量,因此代理可以监视流量并对其进行修改,以实现很多有用的增值 Web 服务。

几种通过代理实现的功能:

  • 儿童过滤器
  • 文档访问控制
  • 安全防火墙
  • Web 缓存
  • 反向代理
  • 内容路由器
  • 转码器
  • 匿名者

代理会去往何处

代理服务器的部署

可以根据其目标用途,将代理放在任意位置。

  • 出口代理

    将代理固定在本地网络的出口点,以便控制本地网络与大型因特网之间的流量。可以在公司网络中使用出口代理,提供针对公司外部恶意黑客的防火墙保护,或降低带宽费用,提高因特网流量的性能。 image.png

  • 入口代理(访问代理)

    代理常被放在 ISP 访问点上,用以处理来自客户的聚合请求。ISP 使用缓存代理 来存储常用文档的副本,以提高用户(尤其是高速连接用户)的下载速度,降低 因特网带宽耗费 image.png

  • 反向代理

    代理通常会被部署在网络边缘,在 Web 服务器之前,作为替代物(也常被称为反向代理)使用。替代物可以提高 Web 服务器的 安全特性,或者将快速的 Web 服务器缓存放在较慢的服务器之前,以提高性能。

    反向代理通常会直接冒用 Web 服务器的名字和 IP 地址,这样所有的请求就会被发送给代理而不是服务器了。 image.png

代理的层次结构

可以通过代理层次结构(proxy hierarchy)将代理级联起来。在代理的层次结构中,会将报文从一个代理传给另一个代理,直到最终抵达原始服务器为止(然后通过代理传回给客户端)。

image.png

图中的代理层次结构是静态的——代理 1 总是会将报文转发给代理 2,代理 2 总是会将报文转发给代理 3。但是,层次不一定非得是静态的。代理服务器可以根据众多因素,将报文转发给一个不断变化的代理服务器和原始服务器集。

代理层次结构可以是动态的,随请求而变的

  • 负载均衡

    子代理可能会根据当前父代理上的工作负载级别来决定如何选择一个父代理,以均衡负载。

  • 地理位置附近的路由

    子代理可能会选择负责原始服务器所在物理区域的父代理。

  • 协议 / 类型路由

    子代理可能会根据 URI 将报文转发到不同的父代理和原始服务器上去。某些特定类型的 URI 可能要通过一些特殊的代理服务器转发请求,以便进行特殊的协议处理。

  • 基于订购的路由

    如果发布者为高性能服务额外付费了,它们的 URI 就会被转发到大型缓存或压缩引擎上去,以提高性能。

代理是如何获取流量的

笔注:意思就是请求如何到达代理的

下图,有四种常见方式可以使客户端流量流向代理

  • (a) 修改客户端

    很多 Web 客户端,包括网景和微软的浏览器,都支持手工和自动的代理配置。

  • (b) 修改网络

    网络基础设施可以通过若干种技术手段,在客户端不知道,或没有参与的情况 下,拦截网络流量并将其导入代理。这种拦截通常都依赖于监视 HTTP 流量的交换设备及路由设备,在客户端毫不知情的情况下,对其进行拦截,并将流量导入一个代理这种代理被称为拦截代理。

  • (c) 修改 DNS 的命名空间

    放在 Web 服务器之前的代理服务器——替代物,会直接假扮 Web 服务器的名 字和 IP 地址,这样,所有的请求就会发送给这些替代物,而不是服务器了。要实现这一点,可以手工编辑 DNS 名称列表,或者用特殊的动态 DNS 服务器根据需要来确定适当的代理或服务器。

  • (d) 修改 Web 服务器

    也可以将某些 Web 服务器配置为向客户端发送一条 HTTP 重定向命令(响应码 305),将客户端请求重定向到一个代理上去。收到重定向命令后,客户端会与代理进行通信。

image.png

客户端的代理设置

所有现代的 Web 浏览器都允许用户对代理的使用进行配置。实际上,很多浏览器都 提供了多种配置代理的方式,其中包括手工配置、预先配置浏览器、代理的自动配置、WPAD 的代理发现。

手工配置

显式地设置要使用的代理。

客户端代理配置:PAC文件

手工代理配置很简单但有些死板。只能为所有内容指定唯一的一个代理服务器,而且不支持故障转移。手工代理配置还会给大型组织带来管理问题。如果配置过的浏览器基数很大,那么需要进行修改的时候,重新配置每个浏览器是非常困难,甚至是不可能的。

PAC 文件是一些小型的 JavaScript 程序,可以在运行过程中计算代理设置,因此, 是一种更动态的代理配置解决方案。访问每个文档时,JavaScript 函数都会选择恰当的代理服务器。

要使用 PAC 文件,就要用 JavaScript PAC 文件的 URI 来配置浏览器[配置方式与 手工配置类似,但要在“automatic configuration”(自动配置)框中提供一个 URI]。 浏览器会从这个 URI 上获取 PAC 文件,并用 JavaScript 逻辑为每次访问计算恰当 的代理服务器。PAC 文件的后缀通常是 .pac,MIME 类型通常是 application/x-nsproxy-autoconfig。

客户端代理配置:WPAD 协议

另一种浏览器配置机制是 WPAD 协议。WPAD 协议的算法会使用发现机制的逐级上升策略自动地为浏览器查找合适的 PAC 文件。实现 WPAD 协议的客户端 需要:

  • 用 WPAD 找到 PAC 的 URI;
  • 从指定的 URI 获取 PAC 文件;
  • 执行 PAC 文件来判定代理服务器;
  • 为请求使用代理服务器

与代理请求有关的棘手问题

代理URI与服务器URI的不同

Web 服务器报文和 Web 代理报文的语法是一样的。客户端向服务器而不是代理发送请求时,HTTP 请求报文中的 URI 会有所不同。

  • 客户端向 Web 服务器发送请求时,请求行中只包含部分 URI(没有方案、主机或端 口),如下例所示:

    GET /index.html HTTP/1.0 
    User-Agent: SuperBrowser v1.3
    
  • 但当客户端向代理发送请求时,请求行中则包含完整的 URI。例如:

    GET http://www.marys-antiques.com/index.html 
    HTTP/1.0 User-Agent: SuperBrowser v1.3
    

在原始的 HTTP 设计中,客户端会直接与单个服务器进行对话。不存在虚拟主机,也没有为代理制定什么规则。单个的服务器都知道自己的主机名和端口,所以,为了避免发送冗余信息,客户端只需发送部分 URI 即可,无需发送方案和主机(以及端口)。

代理出现之后,使用部分 URI 就有问题了。代理需要知道目标服务器的名称,这样 它们才能建立自己与服务器的连接。基于代理的网关要知道 URI 的方案才能连接到 FTP 资源和其他方案上去。HTTP/1.0 要求代理请求发送完整的 URI,解决了这个问题,但它为服务器请求保留部分 URI 的形式(已经有相当多的服务器都改为支持完整 URI 了)。

拦截代理会收到部分URI

客户端并不总是知道它 是在和代理进行对话,因为有些代理对客户端可能是不可见的。即使没有将客户端 配置为使用代理,客户端的流量也可能会经过替代物或拦截代理。在这两种情况下, 客户端都会认为它在与 Web 服务器进行对话,不会发送完整的 URI

代理既可以处理代理请求,也可以处理服务器请求

由于将流量重定向到代理服务器的方式有所不同,通用的代理服务器既应该支持请求报文中的完整 URI,也应该支持部分 URI。

  • 如果是显式的代理请求,代理就应该使用完整 URI;
  • 如果是 Web 服务器请求,就应该使用部分 URI 和虚拟 Host 首部。

转发过程中对URI的修改

代理服务器要在转发报文时修改请求 URI 的话,需要特别小心。对 URI 的微小修改,甚至是看起来无害的修改,都可能给下游服务器带来一些互操作性问题。

尤其是,现在已知有些代理会在将 URI 转发给下一跳节点之前将 URI“规范”为标准格式。有些看起来无害的转换行为,比如用显式的“:80”来取代默认的 HTTP 端口,或者用适当的换码转义符来取代非法的保留字符以校正 URI,就可能造成互操作性问题。

没有代理时URI的解析

没有代理时,浏览器会获 取你输入的 URI,尝试着寻找相应的 IP 地址。如果找到了主机名,浏览器会尝试相应的 IP 地址直到获得成功的连接为止。

有显式代理时URI的解析

使用显式代理时,用户的 URI 会被直接发送给代理,所以浏览器就不再执行所有这些便捷的扩展功能了。

有拦截代理时URI的解析

使用不可见的拦截代理时,对主机名的解析会略有不同,因为对客户端来说,是没有代理的!这种情况下的行为与使用服务器的情形很类似,浏览器会自动扩展主机名,直到 DNS 成功为止。

追踪报文

现在,在将 Web 请求从客户端传送到服务器的路径上,经过两个或多个代理是很常见的。随着代理的逐渐流行,我们要能够追踪经过代理的报文流,以检测出各种问题,其重要性就跟追踪经过不同交换机和路由器传输的 IP 分组流一样。

Via 首部

Via 首部字段列出了与报文途经的每个中间节点(代理或网关)有关的信息。报文 每经过一个节点,都必须将这个中间节点添加到 Via 列表的末尾。

下面的 Via 字符串告诉我们报文流经了两个代理。这个字符串说明第一个代理名 为 proxy-62.irenes-isp.net,它实现了 HTTP/1.1 协议,第二个代理被称为 cache.joes-hardware.com,实现了 HTTP/1.0:

Via: 1.1 proxy-62.irenes-isp.net, 1.0 cache.joes-hardware.com

Via的隐私和安全问题

有时候,我们并不希望在 Via 字符串中使用确切的主机名。总地来说,除非显式地允许了这种行为,否则,当代理服务器作为网络防火墙的一部分使用时,是不应该转发防火墙后面那些主机的名字和端口号的,因为防火墙后面的网络结构信息可能会被恶意群体利用。

如果不允许进行 Via 节点名转发,作为安全防线的一部分使用的代理就应该用适当 的假名来取代那台主机的名字。一般来说,即使隐藏了真实名称,代理也应该尝试 着为每台代理服务器保留一个 Via 路标条目。

对那些有着非常强烈的隐私要求,需要隐藏内部网络设计和拓扑结构的组织来说, 代理应该将一个(接收协议值相同的)有序 Via 路标条目序列合并成一个联合条目。比如,可以将:

Via: 1.0 foo, 1.1 devirus.company.com, 1.1 access-logger.company.com

压缩成:

Via: 1.0 foo, 1.1 concealed-stuff

除非这些条目都在同一个组织的控制之下,而且已经用假名取代了主机名,否则就 不能将其合并起来。同样,接收协议值不同的条目也不能合并起来。

TRACE方法

通过 HTTP/1.1 的 TRACE 方法,用户可以跟踪经代理链传输的请求报文,观察报文经过了哪些代理,以及每个代理是如何对请求报文进行修改的。TRACE 对代理流的调试非常有用。

代理认证

代理可以作为访问控制设备使用。HTTP 定义了一种名为代理认证(proxy authentication)的机制,这种机制可以阻止对内容的请求,直到用户向代理提供了 有效的访问权限证书为止。

  • 代理可以实现认证机制以控制对内容的访问

image.png

不同代理的互操作性

客户端、服务器和代理是由不同厂商构建的,实现的是不同版本的 HTTP 规范。它们支持的特性各不相同,也存在着不同的问题。代理服务器位于客户端和服务器设备之间,这些设备实现的协议可能有所不同,可能存在着很棘手的问题。

OPTIONS:发现对可选特性的支持

通过使用 OPTIONS,客户端可以在与服务器进行交互之前,确定服务器的能力,这样它就可以更方便地与具备不同特性的代理和服务器进行互操作了。

如果 OPTIONS 请求的 URI 是个星号(*),请求的就是整个服务器所支持的功能。 比如:

OPTIONS * HTTP/1.1 

如果 URI 是个实际资源地址,OPTIONS 请求就是在查询那个特定资源的可用特性:

OPTIONS http://www.joes-hardware.com/index.html HTTP/1.1

如果成功,OPTIONS 方法就会返回一个包含了各种首部字段的 200 OK 响应,这些 字段描述了服务器所支持的,或资源可用的各种可选特性。HTTP/1.1 在响应中唯一指定的首部字段是 Allow 首部,这个首部用于描述服务器所支持的各种方法(或者服务器上的特定资源)。

Allow首部

可以将 Allow 首部作为请求首部,建议在新的资源上支持某些方法。并不要求服务器支持这些方法,但应该在相应的响应中包含一个 Allow 首部,列出它实际支持的方法。

缓存

Web 缓存是可以自动保存常见文档副本的 HTTP 设备。当 Web 请求抵达缓存时, 如果本地有“已缓存的”副本,就可以从本地存储设备而不是原始服务器中提取这个文档。

使用缓存有下列优点。

  • 缓存减少了冗余的数据传输,节省了你的网络费用。
  • 缓存缓解了网络瓶颈的问题。不需要更多的带宽就能够更快地加载页面。
  • 缓存降低了对原始服务器的要求。服务器可以更快地响应,避免过载的出现。
  • 缓存降低了距离时延,因为从较远的地方加载页面会更慢一些。

冗余的数据传输

有很多客户端访问一个流行的原始服务器页面时,服务器会多次传输同一份文档, 每次传送给一个客户端。一些相同的字节会在网络中一遍遍地传输。这些冗余的数 据传输会耗尽昂贵的网络带宽,降低传输速度,加重 Web 服务器的负载。有了缓 存,就可以保留第一条服务器响应的副本,后继请求就可以由缓存的副本来应对了, 这样可以减少那些流入 / 流出原始服务器的、被浪费掉了的重复流量。

带宽瓶颈

缓存还可以缓解网络的瓶颈问题。很多网络为本地网络客户端提供的带宽比为远程服务器提供的带宽要宽。客户端会以路径上最慢的网速访问服务器。 如果客户端从一个快速局域网的缓存中得到了一份副本,那么缓存就可以提高性能——尤其是要传输比较大的文件时。

瞬间拥塞

缓存在破坏瞬间拥塞(Flash Crowds)时显得非常重要。突发事件(比如爆炸性新闻、批量 E-mail 公告,或者某个名人事件)使很多人几乎同时去访问一个 Web 文档时,就会出现瞬间拥塞。由此造成的过多流量峰值可能会使网络和 Web 服务器产生灾难性的崩溃。

距离时延

即使带宽不是问题,距离也可能成为问题。每台网络路由器都会增加因特网流量的时延。即使客户端和服务器之间没有太多的路由器,光速自身也会造成显著的时延。

命中和未命中

这样看来缓存是有所帮助的。但缓存无法保存世界上每份文档的副本。

  • 可以用已有的副本为某些到达缓存的请求提供服务。这被称为缓存命中(cache hit),
  • 其他一些到达缓存的请求可能会由于没有副本可用,而被转发给原始服务器。这被称为缓存未命中(cache miss)。

image.png

几乎没人能够买得起一个大得足以装下 Web 上所有文档的缓存。即便可以买得起巨大的“整个 Web 的缓存”,有些文档也经常会发生变化,很多缓存中的内容都不是最新的。这样的话,在很多缓存中都无法对其进行及时的更新

再验证

HTTP 为我们提供了几个用来对已缓存对象进行再验证的工具,但最常用的是 IfModified-Since 首部。将这个首部添加到 GET 请求中去,就可以告诉服务器,只有在缓存了对象的副本之后,又对其进行了修改的情况下,才发送此对象。

这里列出了在 3 种情况下服务器收到 GET If-Modified-Since 请求时会发生的情况:

  • 再验证命中

    如果服务器对象未被修改,服务器会向客户端发送一个小的 HTTP 304 Not Modified 响应。

  • 再验证未命中

    如果服务器对象与已缓存副本不同,服务器向客户端发送一条普通的、带有完整 内容的 HTTP 200 OK 响应。

  • 对象被删除

    如果服务器对象已经被删除了,服务器就回送一个 404 Not Found 响应,缓存也 会将其副本删除。

命中率

由缓存提供服务的请求所占的比例被称为缓存命中率(cache hit rate,或称为缓存命中比例),有时也被称为文档命中率(document hit rate)。

字节命中率

由于文档并不全是同一尺寸的,所以文档命中率并不能说明一切。有些大型对象被访问的次数可能较少,但由于尺寸的原因,对整个数据流量的贡献却更大。因此,有些人更愿意使用字节命中率(byte hit rate)作为度量值(尤其那些按流量字节付 费的人!)。

字节命中率表示的是缓存提供的字节在传输的所有字节中所占的比例。通过这种度 量方式,可以得知节省流量的程度。100% 的字节命中率说明每个字节都来自缓存, 没有流量流到因特网上去。

区分命中和未命中的情况

不幸的是,HTTP 没有为用户提供一种手段来区分响应是缓存命中的,还是访问原始服务器得到的。在这两种情况下,响应码都是 200 OK,说明响应有主体部分。有 些商业代理缓存会在 Via 首部附加一些额外信息,以描述缓存中发生的情况。

客户端有一种方法可以判断响应是否来自缓存,就是使用 Date 首部。将响应中 Date 首部的值与当前时间进行比较,如果响应中的日期值比较早,客户端通常就可以认为这是一条缓存的响应。

缓存的拓扑结构

专用缓存被称为私有缓存(private cache)。

  • 私有缓存是个人的缓存,包含了单个用户最常用的页面。共享的缓存被称为公有缓存(public cache)。
  • 公有缓存中包含了某个用户团体的常用页面。

私有缓存

私有缓存不需要很大的动力或存储空间,这样就可以将其做得很小,很便宜。Web 浏览器中有内建的私有缓存——大多数浏览器都会将常用文档缓存在你个人电脑的磁盘和内存中,并且允许用户去配置缓存的大小和各种设置。

公有代理缓存

公有缓存是特殊的共享代理服务器,被称为缓存代理服务器(caching proxy server),或者更常见地被称为代理缓存(proxy cache)。代理缓存会从本地缓存中提供文档,或者代表用户与服务器进行联系。公有缓存会接受来自多个用户的访问,所以通过它可以更好地减少冗余流量。

代理缓存的层次结构

在实际中,实现层次化(hierarchy)的缓存是很有意义的,在这种结构中,在较小缓存中未命中的请求会被导向较大的父缓存(parent cache),由它来为剩下的那些 “提炼过的”流量提供服务。下图显示了一个两级的缓存层次结构。其基本思想是在靠近客户端的地方使用小型廉价缓存,而更高层次中,则逐步采用更大、功能更强的缓存来装载多用户共享的文档。

image.png

网状缓存、内容路由以及对等缓存

有些网络结构会构建复杂的网状缓存(cache mesh),而不是简单的缓存层次结构。网状缓存中的代理缓存之间会以更加复杂的方式进行对话,做出动态的缓存通信决策,决定与哪个父缓存进行对话,或者决定彻底绕开缓存,直接连接原始服务器。这种代理缓存会决定选择何种路由对内容进行访问、管理和传送,因此可将其称为内容路由器(content router)。

缓存的处理步骤

Web 缓存的基本工作 原理大多很简单。对一条 HTTP GET 报文的基本缓存处理过程包括 7 个步骤:接收 -> 解析 -> 查询 -> 新鲜度检测 -> 创建响应 -> 发送 -> 日志。

接收

在第一步中,缓存检测到一条网络连接上的活动,读取输入数据。高性能的缓存会同时从多条输入连接上读取数据,在整条报文抵达之前开始对事务进行处理。

解析

接下来,缓存将请求报文解析为片断,将首部的各个部分放入易于操作的数据结构中。这样,缓存软件就更容易处理首部字段并修改它们了。

查找

在第三步中,缓存获取了 URL,查找本地副本。本地副本可能存储在内存、本地磁 盘,甚至附近的另一台计算机中。专业级的缓存会使用快速算法来确定本地缓存中是否有某个对象。如果本地没有这个文档,它可以根据情形和配置,到原始服务器或父代理中去取,或者返回一条错误信息。

已缓存对象中包含了服务器响应主体和原始服务器响应首部,这样就会在缓存命中 时返回正确的服务器首部。已缓存对象中还包含了一些元数据(metadata),用来记 录对象在缓存中停留了多长时间,以及它被用过多少次等。

新鲜度检测

HTTP 通过缓存将服务器文档的副本保留一段时间。在这段时间里,都认为文档是 “新鲜的”,缓存可以在不联系服务器的情况下,直接提供该文档。但一旦已缓存副 本停留的时间太长,超过了文档的新鲜度限值(freshness limit),就认为对象“过时”了,在提供该文档之前,缓存要再次与服务器进行确认,以查看文档是否发生 了变化。客户端发送给缓存的所有请求首部自身都可以强制缓存进行再验证,或者完全避免验证,这使得事情变得更加复杂了。

创建响应

我们希望缓存的响应看起来就像来自原始服务器的一样,缓存将已缓存的服务器响应首部作为响应首部的起点。然后缓存对这些基础首部进行了修改和扩充。

缓存负责对这些首部进行改造,以便与客户端的要求相匹配。比如,服务器返回 的可能是一条 HTTP/1.0 响应(甚至是 HTTP/0.9 响应),而客户端期待的是一条 HTTP/1.1 响应,在这种情况下,缓存必须对首部进行相应的转换。缓存还会向其中插入新鲜度信息(Cache-Control、Age 以及 Expires 首部),而且通常会包含一 个 Via 首部来说明请求是由一个代理缓存提供的。

发送

一旦响应首部准备好了,缓存就将响应回送给客户端。和所有代理服务器一样,代理缓存要管理与客户端之间的连接。

日志

大多数缓存都会保存日志文件以及与缓存的使用有关的一些统计数据。每个缓存事务结束之后,缓存都会更新缓存命中和未命中数目的统计数据(以及其他相关的度量值),并将条目插入一个用来显示请求类型、URL 和所发生事件的日志文件。

保持副本的新鲜

HTTP 有一些简单的机制可以在不要求服务器记住有哪些缓存拥有其文档副本的情况下,保持已缓存数据与服务器数据之间充分一致。HTTP 将这些简单的机制称为文档过期(document expiration)和服务器再验证(server revalidation)。

  • 缓存 GET 请求的流程图

image.png

文档过期

通过特殊的 HTTP Cache-Control 首部和 Expires 首部,HTTP 让原始服务器向每个文档附加了一个“过期日期”。就像一牛奶盒上的过期日期一样,这些首部说明了在多长时间内可以将这些内容视为新鲜的。

image.png

在缓存文档过期之前,缓存可以以任意频率使用这些副本,而无需与服务器联 系——当然,除非客户端请求中包含有阻止提供已缓存或未验证资源的首部。但一旦已缓存文档过期,缓存就必须与服务器进行核对,询问文档是否被修改过,如果被修改过,就要获取一份新鲜(带有新的过期日期)的副本。

过期日期和使用期

服务器用 HTTP/1.0+ 的 Expires 首部或 HTTP/1.1 的 Cache-Control: max-age 响应首 部来指定过期日期,同时还会带有响应主体。Expires 首部和 Cache-Control: max-age 首部所做的事情本质上是一样的,但由于 Cache-Control 首部使用的是相对时间而不是绝对日期,所以我们更倾向于使用比较新的 Cache-Control 首部。 绝对日期依赖于计算机时钟的正确设置。

服务器再验证

仅仅是已缓存文档过期了并不意味着它和原始服务器上目前处于活跃状态的文档有实际的区别;这只是意味着到了要进行核对的时间了。这种情况被称为“服务器再验证”,说明缓存需要询问原始服务器文档是否发生了变化。

  • 如果再验证显示内容发生了变化,缓存会获取一份新的文档副本,并将其存储在 旧文档的位置上,然后将文档发送给客户端。

  • 如果再验证显示内容没有发生变化,缓存只需要获取新的首部,包括一个新的过 期日期,并对缓存中的首部进行更新就行了

用条件方法进行再验证

HTTP 定义了 5 个条件请求首部。对缓存再验证来说最有用的 2 个首部是 IfModified-Since 和 If-None-Match。所有的条件首部都以前缀“If-”开头。

If-Modified-Since:Date再验证

常见的缓存再验证首部是 If-Modified-Since。If-Modified-Since 再验证请求通常被称为 IMS 请求。只有自某个日期之后资源发生了变化的时候,IMS 请求才会指示服务器执行请求:

  • 如果自指定日期后,文档被修改了,If-Modified-Since 条件就为真,通常 GET 就会成功执行。携带新首部的新文档会被返回给缓存,新首部除了其他信 息之外,还包含了一个新的过期日期。

  • 如果自指定日期后,文档没被修改过,条件就为假,会向客户端返回一个小的 304 Not Modified 响应报文,为了提高有效性,不会返回文档的主体。这些首部是放在响应中返回的,但只会返回那些需要在源端更新的首部。比如, Content-Type 首部通常不会被修改,所以通常不需要发送。一般会发送一个新的过期日期。

If-None-Match:实体标签再验证

有些情况下仅使用最后修改日期进行再验证是不够的。

  • 文档可能会被周期性地重写(比如,从一个后台进程中写入),但实际包含 的数据常常是一样的。尽管内容没有变化,但修改日期会发生变化。
  • 文档可能被修改了,但所做修改并不重要,不需要让世界范围内的缓存都重装数据(比如对拼写或注释的修改)。
  • 服务器无法准确地判定其页面的最后修改日期。
  • 服务器提供的文档会在亚秒间隙发生变化(比如,实时监视器),对这些服务器来说,以一秒为粒度的修改日期可能就不够用了。

为了解决这些问题,HTTP 允许用户对被称为实体标签(ETag)的“版本标识符” 进行比较。实体标签是附加到文档上的任意标签(引用字符串)。它们可能包含了文 档的序列号或版本名,或者是文档内容的校验和及其他指纹信息。

当发布者对文档进行修改时,可以修改文档的实体标签来说明这个新的版本。这样, 如果实体标签被修改了,缓存就可以用 If-None-Match 条件首部来 GET 文档的新副本了。

强弱验证器

有时,服务器希望在对文档进行一些非实质性或不重要的修改时,不要使所有的已 缓存副本都失效。HTTP/1.1 支持“弱验证器”,如果只对内容进行了少量修改,就 允许服务器声明那是“足够好”的等价体

只要内容发生了变化,强验证器就会变化。弱验证器允许对一些内容进行修改,但 内容的主要含义发生变化时,通常它还是会变化的。有些操作不能用弱验证器来实 现(比如有条件地获取部分内容),所以,服务器会用前缀“W/”来标识弱验证器。

ETag: W/"v2.6" 
If-None-Match: W/"v2.6"

什么时候应该使用实体标签和最近修改日期

  • 服务器回送了一个实体标签,HTTP/1.1 客户端就必须使用实体标签验证器。
  • 服务器只回送了一个 Lst-Modified ,客户端就可以使用 I-Modified-Since 验证。
  • 实体标签和最后修改日期都提供了,客户端就应该使用这两种再验证方案,这样 HTTP/1.0 和 HTTP/1.1 缓存就都可以正确响应了。
  • 如果 HTTP/1.1 缓存或服务器收到的请求既带有 If-Modified-Since,又带有实体标签条件首部,那么只有这两个条件都满足时,才能返回 304 Not Modified 响应。

控制缓存的能力

服务器可以通过 HTTP 定义的几种方式来指定在文档过期之前可以将其缓存多长时间。按照优先级递减的顺序,服务器可以:

  • Cache-Control: no-store 首部到响应中去;
  • Cache-Control: no-cache 首部到响应中去;
  • Cache-Control: must-revalidate 首部到响应中去;
  • Cache-Control: max-age 首部到响应中去;
  • Expires 日期首部到响应中去;
  • 加过期信息,让缓存确定自己的过期日期。

no-Store与no-Cache响应首部

HTTP/1.1 提供了几种限制对象缓存,或限制提供已缓存对象的方式,以维持对象的 新鲜度。no-store 首部和 no-cache 首部可以防止缓存提供未经证实的已缓存对象:

Pragma: no-cache
Cache-Control: no-store 
Cache-Control: no-cache

标识为 no-store 的响应会禁止缓存对响应进行复制。缓存通常会像非缓存代理服务器一样,向客户端转发一条 no-store 响应,然后删除对象。

标识为 no-cache 的响应实际上是可以存储在本地缓存区中的。只是在与原始服务器进行新鲜度再验证之前,缓存不能将其提供给客户端使用。这个首部使用 donot-serve-from-cache-without-revalidation 这个名字会更恰当一些。

HTTP/1.1 中提供 Pragma: no-cache 首部是为了兼容于 HTTP/1.0+。除了与只理解 Pragma: no-cache 的 HTTP/1.0 应用程序进行交互时,HTTP 1.1 应用程序都应该使用 Cache-Control: no-cache。

max-age响应首部

Cache-Control: max-age 首部表示的是从服务器将文档传来之时起,可以认为此文档处于新鲜状态的秒数。还有一个 s-maxage 首部(注意 maxage 的中间没有连 字符),其行为与 max-age 类似,但仅适用于共享(公有)缓存

Cache-Control: max-age=3600 Cache-Control: s-maxage=3600

Expires响应首部

不推荐使用 Expires 首部,它指定的是实际的过期日期而不是秒数。HTTP 设计者后来认为,由于很多服务器的时钟都不同步,或者不正确,所以最好还是用剩余秒数,而不是绝对时间来表示过期时间。

must-revalidate响应首部

可以配置缓存,使其提供一些陈旧(过期)的对象,以提高性能。如果原始服务器希望缓存严格遵守过期信息,可以在原始响应中附加一个 Cache-Control: mustrevalidate 首部。

Cache-Control: must-revalidate 响应首部告诉缓存,在事先没有跟原始服务器进行再验证的情况下,不能提供这个对象的陈旧副本。

试探性过期

如果响应中没有 Cache-Control: max-age 首部,也没有 Expires 首部,缓存可以计算出一个试探性最大使用期。可以使用任意算法,但如果得到的最大使用期大于 24 小时,就应该向响应首部添加一个 Heuristic Expiration Warning(试探性过期警告)首部。据我们所知,很少有浏览器会为用户提供这种警告信息。

客户端的新鲜度限制

Web 浏览器都有 Refresh(刷新)或 Reload(重载)按钮,可以强制对浏览器或 代理缓存中可能过期的内容进行刷新。Refresh 按钮会发布一个附加了 CacheControl 请求首部的 GET 请求,这个请求会强制进行再验证,或者无条件地从服 务器获取文档。Refresh 的确切行为取决于特定的浏览器、文档以及拦截缓存的配置。

设置缓存控制

不同的 Web 服务器为 HTTP Cache-Control 和 Expiration 首部的设置提供了一 些不同的机制。本节简要介绍了流行的 Apache Web 服务器是怎样支持缓存控制的

控制Apache的HTTP首部

Apache Web 服务器提供了几种设置 HTTP 缓存控制首部的机制。其中很多机制在 默认情况下都没有启动——你要启动它们。

缓存和广告

发布广告者的两难处境

缓存可以将那些 漂亮的文章和广告以更快,甚至更好看的方式显示在用户的显示器上,鼓励他们去 浏览更多的内容,看更多的广告。这就是内容提供商所希望的!吸引更多的眼球和 更多的广告!

同样,如果缓存工作得很好,原始服务器可能根本收不到任何 HTTP 访问,因为这些访问都被因特网缓存吸收了。但如果你的收益是基于访问次数的话,你就高兴不起来了。

发布者的响应

一种解决方案就是配置缓存,每次访问时都与原始服务器进行再验证。这样,每次访问时都会将命中推向原始服务器,但通常不会传送任何主体数据。当然,这样会降低事务处理的速度。

日志迁移

理想的解决方案是不需要将命中传递给服务器的。毕竟,缓存就可以记录下所有的 命中。缓存只要将命中日志发送给服务器就行了。

但是,命中日志很大,很难移动。而缓存日志并没有被标准化或被组织成独立的日 志,以传送给单独的内容提供商。而且,这里面还存在着认证和隐私问题。

命中计数和使用限制

RFC 2227,“HTTP 的简单命中计数和使用限制”中定义了一种简单得多的方案。这个协议向 HTTP 中添加了一个称为 Meter 的首部,这个首部会周期性地将对特定 URL 的命中次数回送给服务器。通过这种方式,服务器可以从缓存周期性地获取对 已缓存文档命中次数的更新。

而且,服务器还能控制在缓存必须向服务器汇报之前,其中的文档还可以使用多少次,或者为缓存文档设置一个时钟超时值。这种控制方式被称为使用限制;通过这种方式,服务器可以对缓存向原始服务器汇报之前,已缓存资源的使用次数进行控制。

网关、隧道及中继

介绍一些开发者用 HTTP 访问不同资源的方法,展示了开发者如何将 HTTP 作为框架启动其他协议和应用程序通信。

网关

HTTP 扩展和接口的发展是由用户需求驱动的。要在 Web 上发布更复杂资源的需求出现时,:单个应用程序无法处理所有这些能想到的资源。

为了解决这个问题,开发者提出了网关(gateway)的概念,网关可以作为某种翻译器使用,它抽象出了一种能够到达资源的方法。网关是资源和应用程序之间的粘合剂。

客户端和服务器端网关

  • 服务器端网关(server-side gateway)通过 HTTP 与客户端对话,通过其他协议与服务器通信(HTTP/*)。

  • 客户端网关(client-side gateway)通过其他协议与客户端对话,通过 HTTP 与服 务器通信(*/HTTP)

协议网关

将 HTTP 流量导向网关时所使用的方式与将流量导向代理的方式相同。最常见的方式是,显式地配置浏览器使用网关,对流量进行透明的拦截,或者将网关配置为替代者(反向代理)。

HTTP/*:服务器端Web网关

请求流入原始服务器时,服务器端 Web 网关会将客户端 HTTP 请求转换为其他协议。

HTTP/HTTPS:服务器端安全网关

一个组织可以通过网关对所有的输入 Web 请求加密,以提供额外的隐私和安全性保 护。客户端可以用普通的 HTTP 浏览 Web 内容,但网关会自动加密用户的对话。

  • 输入 HTTP/HTTPS 安全网关 image.png

HTTPS/HTTP客户端安全加速器网关

最近,将 HTTPS/HTTP 网关作为安全加速器使用的情况是越来越多了。这些 HTTPS/HTTP 网关位于 Web 服务器之前,通常作为不可见的拦截网关或反向代理使用。它们接收安全的 HTTPS 流量,对安全流量进行解密,并向 Web 服务器发送普通的 HTTP 请求。

  • HTTPS/HTTP 安全加速器网关 image.png

这些网关中通常都包含专用的解密硬件,以比原始服务器有效得多的方式来解密安全流量,以减轻原始服务器的负荷。这些网关在网关和原始服务器之间发送的是未加密的流量,所以,要谨慎使用,确保网关和原始服务器之间的网络是安全的。

资源网关

最常见的网关,应用程序服务器,会将目标服务器与网关结合在一个服务器中实现。应用程序服务器是服务器端网关,与客户端通过 HTTP 进行通信,并与服务器端的应用程序相连。

笔注:资源网关,可以帮助客户端实现对各种不同类型的资源请求。

  • 服务器网关应用程序机制 image.png

第一个流行的应用程序网关 API 就是通用网关接口(Common Gateway Interface, CGI)。CGI 是一个标准接口集,Web 服务器可以用它来装载程序以响应对特定 URL 的 HTTP 请求,并收集程序的输出数据,将其放在 HTTP 响应中回送。

CGI

通用网关接口(Common Gateway Interface, CGI),是第一个,可能仍然是得到最广泛使用的服务器扩展。在 Web 上广泛用于动态 HTML、信用卡处理以及数据库查询等任务。

隧道

我们已经讨论了几种不同的方式,通过这些方式可以用 HTTP 对不同类型的资源进行访问(通过网关),或者是用 HTTP 来启动应用程序到应用程序的通信。在本节中,我们要看看 HTTP 的另一种用法——Web 隧道(Web tunnel),这种方式可以通过 HTTP 应用程序访问使用非 HTTP 协议的应用程序

Web 隧道允许用户通过 HTTP 连接发送非 HTTP 流量,这样就可以在 HTTP 上捎带 其他协议数据了。使用 Web 隧道最常见的原因就是要在 HTTP 连接中嵌入非 HTTP 流量,这样,这类流量就可以穿过只允许 Web 流量通过的防火墙了。

笔注:就是隧道可以传输非 HTTP 协议的流量。不需要经过网关转换协议。隧道可以经由 HTTP 连接传输非 HTTP 流

用CONNECT建立HTTP隧道

Web 隧道是用 HTTP 的 CONNECT 方法建立起来的。CONNECT 方法并不是 HTTP/1.1 核心规范的一部分,但却是一种得到广泛应用的扩展。

CONNECT 方法请求隧道网关创建一条到达任意目的服务器和端口的 TCP 连接,并对客户端和服务器之间的后继数据进行盲转发

  • 用 CONNECT 建立一条 SSL 隧道 image.png

图中的例子描述了一条 SSL 隧道,其中的 SSL 流量是在一条 HTTP 连接上发送的,但是通过 CONNECT 方法可以与使用任意协议的任意服务器建立 TCP 连接的

CONNECT请求

除了起始行之外,CONNECT 的语法与其他 HTTP 方法类似。一个后面跟着冒号和 端口号的主机名取代了请求 URI。主机和端口都必须指定:

CONNECT home.netscape.com:443 HTTP/1.0 
User-agent: Mozilla/4.0

CONNECT响应

发送了请求之后,客户端会等待来自网关的响应。和普通 HTTP 报文一样,响应码 200 表示成功。按照惯例,响应中的原因短语通常被设置为“Connection Established”:

HTTP/1.0 200 Connection Established 
Proxy-agent: Netscape-Proxy/1.1

与普通 HTTP 响应不同,这个响应并不需要包含 Content-Type 首部。此时连接只是对原始字节进行转接,不再是报文的承载者,所以不需要使用内容类型了。

SSL隧道

最初开发 Web 隧道是为了通过防火墙来传输加密的 SSL 流量。很多组织都会将所有流量通过分组过滤路由器和代理服务器以隧道方式传输,以提升安全性。但有些协议,比如加密 SSL,其信息是加密的,无法通过传统的代理服务器转发。隧道会通过一条 HTTP 连接来传输 SSL 流量,以穿过端口 80 的 HTTP 防火墙。

SSL隧道 与 HTTP/HTTPS网关 的对比

HTTP/HTTPS网关

可以像其他协议一样,对 HTTPS 协议(SSL 上的 HTTP)进行网关操作:

  • 由网关 (而不是客户端)初始化与远端 HTTPS 服务器的 SSL 会话,
  • 然后代表客户端执行 HTTPS 事务。响应会由代理接收并解密,
  • 然后通过(不安全的)HTTP 传送给客户端。 这是网关处理 FTP 的方式,但这种方式有几个缺点:
  • 客户端到网关之间的连接是普通的非安全 HTTP;
  • 尽管代理是已认证主体,但客户端无法对远端服务器执行 SSL 客户端认证;
  • 网关要支持完整的 SSL 实现。

SSL隧道

对于 SSL 隧道机制来说,无需在代理中实现 SSL。SSL 会话是建立在产生请求的客户端和目的(安全的)Web 服务器之间的,中间的代理服务器只是将加密数据经过隧道传输,并不会在安全事务中扮演其他的角色。

隧道认证

在适当的情况下,也可以将 HTTP 的其他特性与隧道配合使用。尤其是,可以将代理的认证支持与隧道配合使用,对客户端使用隧道的权利进行认证。

隧道的安全性考虑

总的来说,隧道网关无法验证目前使用的协议是否就是它原本打算经过隧道传输的协议。

中继

HTTP 中继(relay)是没有完全遵循 HTTP 规范的简单 HTTP 代理。中继负责处理 HTTP 中建立连接的部分,然后对字节进行盲转发

某些简单盲中继实现中存在的一个更常见(也更声名狼藉的)问题是,由于它们无法正确处理 Connection 首部,所以有潜在的挂起 keep-alive 连接的可能。