<HTTP权威指南>记录 ---- 网关、隧道及中继

网关、隧道及中继

Web是一种强大的内容发布工具。随着时间的流逝,人们已经从只在网上发送静态的在线文档,发展到共享更复杂的资源,比如数据库内容或动态生成的HTML页面。Web浏览器这样的HTTP应用程序为用户提供了一种统一的方式来访问因特网上的内容。HTTP也已成为应用程序开发者的一种基本构造模块,开发者们可以在HTTP上捎回其他的协议内容(比如,可以将其他协议的流量包裹在HTTP中)。Web上所有的资源都可以使用HTTP协议,而且其他应用程序和应用程序协议也可以利用HTTP来完成它们的任务。

网关

HTTP扩展和接口的发展是由用户需求驱动的。要在Web上发布更复杂资源的需求出现时,人们很快就明确了单个应用程序无法处理所有这些能想到的资源。为了解决这个问题,开发者提出了网关(gateway)的概念,网关可以作为某种翻译器使用,它抽象出了一种能够到达资源的方法。网关是资源和应用程序之间的粘合剂。应用程序可以(通过HTTP或其他已定义的接口)请求网关来处理某条请求,网关可以提供一条响应。网关可以向数据库发送查询语句,或者生成动态的内容,就像一个门一样:进去一条请求,出来一个响应。有些网关会自动将HTTP流量转换为其他协议,这样HTTP客户端无需了解其他协议,就可以与其他应用程序进行交互了。

客户端和服务器端网关:Web 网关在一侧使用HTTP协议,在另一侧使用另一种协议。因此,将 HTTP 客户端连接到 NNTP 新闻服务器的网关就是一个 HTTP/NNTP 网关。我们用术语服务器端网关和客户端网关来说明对话是在网关的哪一侧进行的。

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

协议网关

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

  • 服务端Web网关(HTTP/*):请求流入原始服务器时,服务器端Web网关会将客户端HTTP请求转换为其他协议。
  • 服务器端安全网关(HTTP/HTTPS):一个组织可以通过网关对所有的输入Web请求加密,以提供额外的隐私和安全性保护。客户端可以用普通的HTTP浏览Web内容,但网关会自动加密用户的对话。
  • 客户端安全加速器网关(HTTPS/HTTP):这些HTTPS/HTTP网关位于Web服务器之前,通常作为不可见的拦截网关或反向代理使用。它们接收安全的HTTPS流量,对安全流量进行解密,并向Web服务器发送普通的HTTP请求。这些网关中通常都包含专用的解密硬件,以比原始服务器有效得多的方式来解密安全流量,以减轻原始服务器的负荷。这些网关在网关和原始服务器之间发送的是未加密的流量,所以,要谨慎使用,确保网关和原始服务器之间的网络是安全的。

资源网关

最常见的网关,应用程序服务器,会将目标服务器与网关结合在一个服务器中实现。应用程序服务器是服务器端网关,与客户端通过HTTP进行通信,并与服务器端的应用程序相连。客户端是通过HTTP连接到应用程序服务器的。但应用程序服务器并没有回送文件,而是将请求通过一个网关应用编程接口(Application Programming Interface,API)发送给运行在服务器上的应用程序。第一个流行的应用程序网关API就是通用网关接口(Common Gateway Interface, CGI)。CGI是一个标准接口集,Web服务器可以用它来装载程序以响应对特定URL的HTTP请求,并收集程序的输出数据,将其放在HTTP响应中回送。在过去的几年中,商业Web服务器提供了一些更复杂的接口,以便将Web服务器连接到应用程序上去。早期的Web服务器是相当简单的,在网关接口的实现过程中采用的简单方式一直持续到了现在。

请求需要使用网关的资源时,服务器会请辅助应用程序来处理请求。服务器会将辅助应用程序所需的数据传送给它。通常就是整条请求,或者用户想在数据库上运行的请求(来自 URL 的请求字符串)之类的东西。然后,它会向服务器返回一条响应或响应数据,服务器则会将其转发给客户端。服务器和网关是相互独立的应用程序,因此,它们的责任是分得很清楚的。这个简单的协议(输入请求,转交,响应)就是最古老,也最常用的服务器扩展接口CGI的本质。

CGI

CGI是第一个,可能仍然是得到最广泛使用的服务器扩展。在Web上广泛用于动态HTML、数据库查询以及信用卡处理等任务。CGI应用程序是独立于服务器的,所以,几乎可以用任意语言来实现。CGI很简单,几乎所有的HTTP服务器都支持它。CGI的处理对用户来说是不可见的。从客户端的角度来看,就像发起一个普通请求一样。它完全不清楚服务器和CGI应用程序之间的转接过程。URL中出现字符cgi和可能出现的"?"是客户端发现使用了CGI应用程序的唯一线索。CGI在服务器和众多的资源类型之间提供了一种简单的、函数形式的粘合方式,用来处理各种需要的转换。这个接口还能很好地保护服务器,防止一些糟糕的扩展对它造成的破坏(如果这些扩展直接与服务器相连,造成的错误可能会引发服务器崩溃)。但是,这种分离会造成性能的耗费。为每条CGI请求引发一个新进程的开销是很高的,会限制那些使用CGI的服务器的性能,并且会加重服务端机器资源的负担。为了解决这个问题,开发了一种新型CGI——并将其恰当地称为快速CGI。这个接口模拟了CGI,但它是作为持久守护进程运行的,消除了为每个请求建立或拆除新进程所带来的性能损耗。

服务器扩展API

CGI协议为外部翻译器与现有的HTTP服务器提供了一种简洁的接口方式,但如果想要改变服务器自身的行为,或者只是想尽可能地提升能从服务器上获得的性能呢?服务器开发者为这两种需求提供了几种服务器扩展API,为Web开发者提供了强大的接口,以便他们将自己的模块与HTTP服务器直接相连。扩展API允许程序员将自己的代码嫁接到服务器上,或者用自己的代码将服务器的一个组件完整地替换出来。大多数流行的服务器都会为开发者提供一个或多个扩展API。这些扩展通常都会绑定在服务器自身的结构上,所以,大多数都是某种服务器类型特有的。Web服务器都提供了一些API接口,允许开发者通过这些接口改变服务器的行为,或者为不同的资源提供一些定制的接口。这些定制接口为开发者提供了强大的接口方式。

应用程序接口

随着Web应用程序提供的服务类型越来越多,有一点变得越来越清晰了:HTTP可以作为一种连接应用程序的基础软件来使用。在将应用程序连接起来的过程中,一个更为棘手的问题是在两个应用程序之间进行协议接口的协商,以便这些应用程序可以进行数据的交换——这通常都是针对具体应用程序的个案进行的。应用程序之间要配合工作,所要交互的信息比HTTP首部所能表达的信息要复杂得多。因特网委员会开发了一组允许Web应用程序之间相互通信的标准和协议。尽管Web服务可以用来表示独立的Web应用程序(构造模块),这里我们还是宽松地用这个术语来表示这些标准。Web服务的引入并不新鲜,但这是应用程序共享信息的一种新机制。Web服务是构建在标准的Web技术(比如HTTP)之上的。

隧道

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

用CONNECT建立HTTP隧道

Web隧道是用HTTP的CONNECT方法建立起来的。CONNECT方法并不是"HTTP/1.1"核心规范的一部分,但却是一种得到广泛应用的扩展。CONNECT方法请求隧道网关创建一条到达任意目的服务器和端口的TCP连接,并对客户端和服务器之间的后继数据进行盲转发。

CONNECT 方法如何建立起一条到达网关的隧道:

  • 客户端发送一条CONNECT请求给隧道网关,客户端的CONNECT方法请求隧道网关打开一条TCP连接;
  • 创建了TCP连接;
  • 一旦建立了TCP连接,网关就会发送一条"HTTP 200 Connection Established"响应来通知客户端;
  • 此时,隧道就建立起来了,客户端通过HTTP隧道发送的所有数据都会被直接转发给输出TCP连接,服务器发送的所有数据都会通过HTTP隧道转发给客户端。

CONNECT请求:除了起始行之外,CONNECT的语法与其他HTTP方法类似。一个后面跟着冒号和端口号的主机名取代了请求URI。主机和端口都必须指定(CONNECT home.netscape.com:443 HTTP/1.0)。和其他HTTP报文一样,起始行之后,有零个或多个HTTP请求首部字段。这些行照例以CRLF结尾,首部列表以一个仅有CRLF的空行结束。

CONNECT响应:发送了请求之后,客户端会等待来自网关的响应。和普通HTTP报文一样,响应码200表示成功。按照惯例,响应中的原因短语通常被设置为"Connection Established"(HTTP/1.0 200 Connection Established)。与普通 HTTP 响应不同,这个响应并不需要包含Content-Type首部。此时连接只是对原始字节进行转接,不再是报文的承载者,所以不需要使用内容类型了。

数据隧道、定时及连接管理

管道化数据对网关是不透明的,所以网关不能对分组的顺序和分组流作任何假设。一旦隧道建立起来了,数据就可以在任意时间流向任意方向了。作为一种性能优化方法,允许客户端在发送了CONNECT请求之后,接收响应之前,发送隧道数据。这样可以更快地将数据发送给服务器,但这就意味着网关必须能够正确处理跟在请求之后的数据。尤其是,网关不能假设网络I/O请求只会返回首部数据,网关必须确保在连接准备就绪时,将与首部一同读进来的数据发送给服务器。在请求之后以管道方式发送数据的客户端,如果发现回送的响应是认证请求,或者其他非200但不致命的错误状态,就必须做好重发请求数据的准备。如果在任意时刻,隧道的任意一个端点断开了连接,那个端点发出的所有未传输数据都会被传送给另一个端点,之后,到另一个端点的连接也会被代理终止。如果还有数据要传输给关闭连接的端点,数据会被丢弃。

SSL隧道

最初开发Web隧道是为了通过防火墙来传输加密的SSL流量。很多组织都会将所有流量通过分组过滤路由器和代理服务器以隧道方式传输,以提升安全性。但有些协议,比如加密SSL,其信息是加密的,无法通过传统的代理服务器转发。隧道会通过一条HTTP连接来传输SSL流量,以穿过端口80的HTTP防火墙。为了让SSL流量经现存的代理防火墙进行传输,HTTP中添加了一项隧道特性,在此特性中,可以将原始的加密数据放在HTTP报文中,通过普通的HTTP信道传送。通常会用隧道将非HTTP流量传过端口过滤防火墙。这一点可以得到很好的利用,比如,通过防火墙传输安全SSL流量。但是,这项特性可能会被滥用,使得恶意协议通过HTTP隧道流入某个组织内部。

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

可以像其他协议一样,对HTTPS协议(SSL上的HTTP)进行网关操作:由网关(而不是客户端)初始化与远端HTTPS服务器的SSL会话,然后代表客户端执行HTTPS事务。响应会由代理接收并解密,然后通过(不安全的)HTTP传送给客户端。注意,对于SSL隧道机制来说,无需在代理中实现SSL。SSL会话是建立在产生请求的客户端和目的(安全的)Web服务器之间的,中间的代理服务器只是将加密数据经过隧道传输,并不会在安全事务中扮演其他的角色。

这是网关处理FTP的方式。但这种方式有几个缺点:

  • 客户端到网关之间的连接是普通的非安全HTTP;
  • 尽管代理是已认证主体,但客户端无法对远端服务器执行SSL客户端认证;
  • 网关要支持完整的SSL实现。

隧道认证

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

隧道的安全性考虑

隧道网关无法验证目前使用的协议是否就是它原本打算经过隧道传输的协议。为了降低对隧道的滥用,网关应该只为特定的知名端口,比如HTTPS的端口443,打开隧道。

中继

HTTP中继(relay)是没有完全遵循HTTP规范的简单HTTP代理。中继负责处理HTTP中建立连接的部分,然后对字节进行盲转发。HTTP很复杂,所以实现基本的代理功能并对流量进行盲转发,而且不执行任何首部和方法逻辑,有时是很有用的。盲中继很容易实现,所以有时会提供简单的过滤、诊断或内容转换功能。但这种方式可能潜在严重的互操作问题,所以部署的时候要特别小心。有一些方法可以使中继稍微智能一些,以消除这些风险,但所有简化的代理都存在着出现互操作性问题的风险。要为某个特定目标构建简单的HTTP中继,一定要特别注意其使用方法。对任何大规模部署来说,都要非常认真地考虑使用真正的、完全遵循HTTP的代理服务器。

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

  • Web客户端向中继发送了一条包含"Connection: Keep-Alive"首部的报文,如果可能的话要求建立一条keep-alive连接。客户端等待响应,以确定它要求建立keep-alive信道的请求是否被认可了。
  • 中继收到了这条HTTP请求,但它并不理解Connection首部,因此会将报文一字不漏地沿着链路传递给服务器。但Connection首部是个逐跳首部;只适用于单条传输链路,是不应该沿着链路传送下去的。要有不好的事情发生了!
  • 经过中继转发的HTTP请求抵达Web服务器。当Web服务器收到经过代理转发的"Connection: Keep-Alive"首部时,会错误地认为中继(对服务器来说,它看起来就和其他客户端一样)要求进行keep-alive的对话!这对Web服务器来说没什么问题——它同意进行keep-alive对话,并回送了一个"Connection: Keep-Alive"响应首部。那么,此时,Web服务器就认为它是在与中继进行keep-alive对话,会遵循keep-alive对话的规则。但中继对keep-alive会话根本就一无所知。
  • 中继将Web服务器的响应报文,以及来自Web服务器的"Connection: Keep-Alive"首部一起发回给客户端。客户端看到这个首部,认为中继同意进行keep-alive对话。此时,客户端和服务器都认为它们是在进行keep-alive对话,但与它们进行对话的中继却根本不知道什么keep-alive对话。
  • 中继对持久对话一无所知,所以它会将收到的所有数据都转发给客户端,等待原始服务器关闭连接。但原始服务器认为中继要求服务器将连接保持在活跃状态,所以是不会关闭连接的!这样,中继就会挂起,等待连接的关闭。
  • 当客户端收到回送的响应报文时,它会直接转向第二条请求,在keep-alive连接上向中继发送另一条请求。简单中继通常不会期待同一条连接上还会有另一条请求到达。浏览器上的圈会不停地转,但没有任何进展。

相关推荐