阅读 73

HTTP权威指南阅读笔记

HTTP权威指南笔记

一、HTTP概述

Web资源:Web 服务器是 Web 资源(Web resource)的宿主。Web 资源是 Web 内容的源头。

媒体类型:HTTP 仔细地给每种要通过 Web 传输的对象都打上了名为 MIME 类型(MIME type)的数据格式标签。MIME 类型是一种文本标记,表示一种主要的对象类型和一个特定的子类型,中间由一条斜杠来分隔。

  • HTML 格式的文本文档由 text/html 类型来标记。
  • 普通的 ASCII 文本文档由 text/plain 类型来标记。
  • JPEG 格式的图片为 image/jpeg 类型。
  • GIF 格式的图片为 image/gif 类型。

最初设计 MIME (Multipurpose Internet Mail Extension,多用途因特网邮件扩展)是为了解决在不同的电子邮件系统之间搬移报文时存在的问题。

URI:统一资源标识符

URL:统一资源定位符

二、URL与资源

URL 为用户及他们的浏览器提供了找到资源所需的所有条件。URL 定义了用户所需的特定资源,它位于何处以及如何获取它。

URL语法

大多数 URL 方案的 URL 语法都建立在这个由 9 部分构成的通用格式上:

<scheme>://<user>:<password>@<host>:<port>/<path>;<params>?<query>#<frag>
复制代码

URL 最重要的 3 个部分是方案(scheme)、主机(host)和路径(path)。

  • 方案:访问服务器以获取资源时要使用哪种协议
  • 用户:某些方案访问资源时需要用户名
  • 密码:用户名后面可能要包含的密码,中间用冒号 : 分隔开
  • 主机:资源服务器的主机名或点分IP地址
  • 端口:资源服务器正在监听的端口号,很多方案都有默认端口号
  • 路径:服务器上资源的本地名,说明里资源在服务器的什么地方,用 / 将其与前面的内容分隔开
  • 参数:某些方案需要指定输入参数才能提供正常的服务,URL中可以包含多个参数,它们相互之间以及与路径的其余部分之间用分号 ; 分隔开
  • 查询:某些方案会用这个组件来传递参数,很多资源可以通过查询的方式来缩小请求资源的范围,用 ? 来与其他部分分隔开
  • 片段:一小片或一部分资源的名字。

URL快捷方式

相对URL

绝对URL中包含有访问资源所需的全部信息。要从相对URL中获取访问资源的全部信息,就必须相对于另一个,被称为其基础的URL进行解析。

基础URL:

  • 在资源中显式提供,在HTML文档中定义一个基础URL的HTML标记 <base> ,通过它来转换那个HTML文档中的所有相对URL。
  • 封装资源的基础URL

自动扩展URL

主机名扩展:根据一些小提示,浏览器就能将输入的主机名扩展为完整的URL

历史扩展:根据以往的浏览历史来与输入的URL的前缀进行匹配,并提供一些完整的选项供你选择

URL编码

URL的编码格式采用的是 US-ASCII 码,之所有要进行编码,是因为URL中有些字符会引起歧义。

URL编码的原则就是使用安全的字符(没有特殊用途或者特殊意义的可打印字符)去表示那些不安全的字符。

书写URL时要使用US-ASCII字符集中可以打印的字符,如果需要在URL中使用不属于此字符集的字符,就要使用特殊的符号对该字符进行编码。

除了那些无法显示的字符外,还需要在URL中对那些保留(reserved)字符和不安全(unsafe)字符进行编码。

所谓保留字符就是那些在URL中具有特定意义的字符。不安全字符是指那些在URL中没有特殊含义,但在URL所在的上下文中可能具有特殊意义的字符。例如双引号(“”)

Url编码通常也被称为百分号编码,使用 % 百分号加上两个表示字符ASCII码的十六进制数。

部分保留字符和不安全字符及其URL编码

字符描述用法编码
;分号保留%3B
/斜线保留%2F
?问号保留%3F
:冒号保留%3A
@“at”符号保留%4O
=等号保留%3D
&“和”符号保留%26
<小于号不安全%3C
>大于号不安全%3E
"双引号不安全%22
#井号不安全%23
%百分号不安全%25
{左大括号不安全%7B
}右大括号不安全%7D
|竖线不安全%7C
\反斜线不安全%5C
^加字号不安全%5E
~波浪不安全%7E
[左中括号不安全%5B
]右中括号不安全%5D
`反单引号不安全%60
空格不安全%20

三、HTTP报文

HTTP报文是简单的格式化数据块。每条报文都包含一条来自客户端的请求,或者一条来自服务器的相应。它们由三个部分组成:对报文进行描述的起始行(start line)、包含属性的首部(header)、包含数据的主体(body)。

HTTP报文可以分为两类:请求报文响应报文

请求报文格式:
<methord> <request-URL> <version>
<headers>

<entity-body>

响应报文格式:
<version> <status> <reason-phrase>
<headers>

<entity-body>
复制代码

请求报文和响应报文的格式,只有起始行的语法有所不同

  • 方法(method)

    客户端希望服务器对资源执行的动作。是一个单独的词,如GET、HEAD或POST。

  • 请求URL(request-URL)

    统一资源标识符。

  • 版本(version)

    报文所使用的HTTP版本。其格式如这样:HTTP/<major>.<minor>

  • 状态码(status-code)

    服务器发回的响应状态代码。这三位数字描述了请求过程中所发生的情况。每个状态码的第一位数字都用于描述状态的一般类别(“成功”、“出错”等)。

  • 原因短语(reason-phrase)

    表示状态码的文字描述。

  • 首部(header)

    可以有零个或者多个首部,每个首部都包含一个名字,后面跟着一个冒号:,然后是一个可选的空格,接着是一个值,最后是一个CRLF(回车或者换行)。首部是由一个空行(CRLF)结束的,表示了首部列表的结束和实体主体部分的开始。

  • 实体的主体部分(entity-body)

    实体的主体部分包含一个由任意数据组成的数据块。并不是所有的报文都包含实体的主体部分,有时,报文只是以一个CRLF结束。

报文结构.png

方法

请求的起始行以方法作为开始,方法用来告知服务器要做些什么。

常用的HTTP方法

方法描述是否包含主体
GET请求获取 Request-URL 所标识的资源
POST在 Request-URL 所标识的资源后附加新的数据
HEAD请求获取由 Request-URL 所标识的资源的首部,不返回实体的主体部分
PUT将请求的主体部分存储在服务器,并用 Request-URL 作为其标识
DELETE请求服务器删除 Request-URL 所标识的资源
TRACE请求服务器回送收到的请求信息,主要用于测试或诊断
OPTIONS请求web服务器告知其支持的各种功能,可以询问服务器通常支持哪些方法,或者对某些特殊资源支持哪些方法

状态码

状态代码有三位数字组成,第一个数字定义了响应的类别,且有五种可能取值:

  • 1xx:指示信息 — 表示请求已接收,继续处理
  • 2xx:成功 — 表示请求已被成功接收、理解、接受
  • 3xx:重定向 — 要完成请求必须进行更进一步的操作
  • 4xx:客户端错误 — 请求有语法错误或请求无法实现
  • 5xx:服务器端错误 — 服务器未能实现合法的请求

常见的状态码

状态码状态描述说明
200OK客户端请求成功
400Bad Request客户端请求有语法错误,不能被服务器所理解
401Unauthorized请求未经授权,这个状态代码必须和 WWW-Authenticate 报头域一起使用
403Forbidden服务器收到请求,但是拒绝提供服务
404Not Found请求资源不存在
500Internal Server Error服务器发生不可预期的错误
503Server Unavailable服务器当前不能处理客户端的请求,一段时间后,可能恢复正常

首部

首部和方法配合工作,共同决定了客户端和服务器能做什么事情。

在请求和响应报文中都可以用首部来提供信息,有些首部是某种报文专用的,有些首部则更通用一些,可以将首部分为四个类型:

通用首部

这些是客户端和服务器都可以使用的通用首部。可以在客户端、服务器和其他应用程序之间提供一些非常有用的通用功能。提供了与报文相关的最基本的信息。

首部描述
Connection允许客户端和服务器指定与请求/响应连接有关的选项,例如指定连接是连续,或者指定“close”选项,通知服务器,在响应完成后,断开连接
Date提供日期和时间标志,说明报文是什么时间创建的
Cache-Control用于指定缓存指令,缓存指令是单向的(响应中出现的缓存指令在请求中未必会出现),且是独立的(一个消息的缓存指令不会影响另一个消息处理的缓存机制)
Pragma另一种随报文传送指示的方式,但并不专用于缓存
Transfer-Encoding告知接收端为了保证报文的可靠传输,对报文采用了什么编码方式

请求首部

请求首部是请求报文特有的。他们为服务器提供了一些额外信息,比如客户端希望接收什么类型的数据。用于说明是谁或者什么在发送请求、请求源自何处,或者客户端的喜好及能力。服务器可以根据请求首部给出的客户端信息,试着为客户端提供更好的响应。

请求的信息性首部
首部描述
Client-IP提供了运行客户端的机器的IP地址
From提供了客户端用户的E-mail地址
Host给出了接收请求的服务器的主机名和端口号
User-Agent将发起请求的应用程序名称告知服务器
Accept首部

Accept首部为客户端提供了一种能够将其喜好和能力告知服务器的方式,包括它们想要什么,可以使用什么,以及最重要的,它们不想要什么。这样服务器就可以根据这些额外的信息,对要发送的内容作出更明智的决定。

首部描述
Accept告诉服务器可以发送哪些媒体类型
Accept-Charset告诉服务器可以发送哪些字符集
Accept-Encoding告诉服务器可以发送哪些编码方式
Accept-Language告诉服务器可以发送哪些语言
安全请求首部
首部描述
Authorization包含了客户端提供给服务器,以便对其自身进行认证的数据
Cookie客户端向服务器传送一个令牌——它并不是真正的安全首部,但确实隐含了安全功能

响应首部

响应首部是响应报文特有的。响应首部为客户端提供了一些额外信息,比如谁在发送响应、响应者的功能,甚至与响应相关的一些特殊指令。这些首部有助于客户端处理响应,并在将来发起更好的请求。

首部描述
Age(从最初创建开始)响应持续时间
Location可以将响应接收方(客户端,如浏览器)引导至某个与请求URI位置不同的资源,当请求的资源位置改变时会返回该响应
Server服务器应用程序软件的名称和版本
WWW-Authenticate来自服务器的对客户端的质询列表,该首部必须被包含在状态码为401的响应报文中,客户端收到401响应消息的时候,发送含Authorization首部的请求报文请求服务器对其进行验证,服务器响应报文里就包含本首部

实体首部

实体首部提供了有关实体及其内容的大量信息,请求和响应报文中都可能包含实体部分,所以这两类报文都可能出现这些首部。总之,实体首部可以告知报文的接收者它在对什么进行处理。

首部描述
Content-Base解释主体中的相对 URL 时使用的基础 URL
Content-Encoding对主体执行的任意编码方式
Content-Language主体资源所用的自然语言
Content-Length主体的长度或尺寸
Content-Locaton资源实际所处的位置
Content-Type发送给接收者的实体正文的媒体类型
Content-Range在整个资源中此实体表示的字节范围
Expires响应过期的日期和时间,超过该事件,实体不再有效,要从原始的源端再次获取此实体
Last-Modified实体最后一次被修改的日期和时间

四、连接管理

用TCP套接字编程

套接字API描述
s = socket(<parameters>)创建一个新的、未命名、未关联的套接字
bind(s,<local IP:port>)向套接字赋一个本地端口号和接口
connect(s, <remote IP:port>)创建一条连接本地套接字与远程主机及端口的连接
listen(s, ...)标识一个本地套接字,使其可以合法接受连接
s2 = accept(s)等待某人建立一条到本地端口的连接
n = read(s, buffer, n)尝试从套接字向缓冲区读取 n 个字节
n = write(s, buffer, n)尝试从缓冲区中向套接字写入 n 个字节
close(s)完全关闭TCP连接
shutdown(s,<side>)只关闭TCP 连接的输入或输出端

套接字 API 允许用户创建 TCP 的端点数据结构,将这些端点与远程服务器的 TCP 端点进行连接,并对数据流进行读写。TCP API 隐藏了所有底层网络协议的握手细节,以及 TCP 数据流与 IP 分组之间的分段和重装细节。

TCP 客户端和服务器是如何通过 TCP 套接字接口进行通信的

并行连接

HTTP 允许客户端打开多条连接,并行地执行多个 HTTP 事务。

  • 并行连接可能会提高页面的加载速度,时延重叠

  • 并行连接不一定更快,带宽不足时,多个连接都会竞争这有限的带宽,每个连接都比较慢。

    浏览器确实使用了并行连接,但它们会将并行连接的总数限制为一个较小的值(通常是 4 个)。

  • 并行连接可能让人感觉更快一些,用户能够看到加载的进展

持久链接

在事务处理结束之后仍然保持在打开状态的 TCP 连接被称为持久连接。持久连接会在不同事务之间保持打开状态,直到客户端或服务器决定将其关闭为止。

重用已对目标服务器打开的空闲持久连接,就可以避开缓慢的连接建立阶段。而且,已经打开的连接还可以避免慢启动的拥塞适应阶段,以便更快速地进行数据的传输。

持久以及并行连接

并行连接可以提高复合页面的传输速度。但并行连接也有一些缺点:

  • 每个事务都会打开 / 关闭一条新的连接,会耗费时间和带宽。
  • 由于 TCP 慢启动特性的存在,每条新连接的性能都会有所降低。
  • 可打开的并行连接数量实际上是有限的。

持久连接有一些比并行连接更好的地方。持久连接降低了时延和连接建立的开销,将连接保持在已调谐状态,而且减少了打开连接的潜在数量。但是,管理持久连接时要特别小心,不然就会累积出大量的空闲连接,耗费本地以及远程客户端和服务器上的资源

持久连接与并行连接配合使用可能是最高效的方式。现在,很多 Web 应用程序都会打开少量的并行连接,其中的每一个都是持久连接。

持久连接有两种类型:

  • HTTP/1.0 + keep-alive 连接
  • HTTP/1.1 persistent 连接

keep-alive操作

实现 HTTP/1.0 keep-alive 连接的客户端可以通过包含 Connection: Keep-Alive 首部请求将一条连接保持在打开状态。

如果服务器愿意为下一条请求将连接保持在打开状态,就在响应中包含相同的首部。如果响应中没有 Connection: Keep-Alive 首部,客户端就认为服务器不支持 keep-alive,会在发回响应报文之后关闭连接。

keep-alive选项

keep-Alive 首部只是请求将连接保持在活跃状态。发出 keep-alive 请求之后,客户端和服务器并不一定会同意进行 keep-alive 会话。它们可以在任意时刻关闭空闲的 keep-alive 连接,并可随意限制 keep-alive 连接所处理事务的数量。

可以用 Keep-Alive 通用首部中指定的、由逗号分隔的选项来调节 keep-alive 的行为。

  • time-out,服务器希望将连接保持在活跃状态的时间。
  • max,服务器还希望为多少个事务保持此连接的活跃状态。

keep-alive连接的限制和规则

  • 在 HTTP/1.0 中,keep-alive 并不是默认使用的。客户端必须发送一个 Connection: Keep-Alive 请求首部来激活 keep-alive 连接。
  • Connection: Keep-Alive 首部必须随所有希望保持持久连接的报文一起发送。如果客户端没有发送 Connection: Keep-Alive 首部,服务器就会在那条请求之后关闭连接。
  • 通过检测响应中是否包含 Connection: Keep-Alive 响应首部,客户端可以判断服务器是否会在发出响应之后关闭连接。
  • 只有在无需检测到连接的关闭即可确定报文实体主体部分长度的情况下,才能将连接保持在打开状态——也就是说实体的主体部分必须有正确的 Content-Length,有多部件媒体类型,或者用分块传输编码的方式进行了编码。在一条 keep-alive 信道中回送错误的 Content-Length 是很糟糕的事,这样的话,事务处理的另一端就无法精确地检测出一条报文的结束和另一条报文的开始了。
  • 代理和网关必须执行 Connection 首部的规则。代理或网关必须在将报文转发出去或将其高速缓存之前,删除在 Connection 首部中命名的所有首部字段以及 Connection 首部自身。
  • 严格来说,不应该与无法确定是否支持 Connection 首部的代理服务器建立 keep-alive 连接,以防止出现下面要介绍的哑代理问题。

keep-alive和哑代理

哑代理

问题出在代理上——尤其是那些不理解 Connection 首部,而且不知道在沿着转发链路将其发送出去之前,应该将该首部删除的代理。很多老的或简单的代理都是盲中继(blind relay),它们只是将字节从一个连接转发到另一个连接中去,不对 Connection 首部进行特殊的处理。

上图发生的情况解释如下:

  1. Web 客户端向代理发送了一条报文,其中包含了 Connection: Keep-Alive 首部,如果可能的话请求建立一条 keep-alive 连接。客户端等待响应,以确定对方是否认可它对 keep-alive 信道的请求。
  2. 哑代理收到了这条 HTTP 请求,但它并不理解 Connection 首部(只是将其作为一个扩展首部对待)。代理不知道 keep-alive 是什么意思,因此只是沿着转发链路将报文一字不漏地发送给服务器。但 Connection 首部是个逐跳首部,只适用于单条传输链路,不应该沿着传输链路向下传输。接下来,就要发生一些很糟糕的事情了。
  3. 经过中继的 HTTP 请求抵达了 Web 服务器。当 Web 服务器收到经过代理转发的 Connection: Keep-Alive 首部时,会误以为代理希望进行 keep-alive 对话!对 Web 服务器来说这没什么问题——它同意进行 keep-alive 对话,并回送了一个 Connection: Keep-Alive 响应首部。所以,此时 Web 服务器认为它在与代理进行 keep-alive 对话,会遵循 keep-alive 的规则。但代理却对 keep-alive 一无所知。不妙。
  4. 哑代理将 Web 服务器的响应报文回送给客户端,并将来自 Web 服务器的 Connection: Keep-Alive 首部一起传送过去。客户端看到这个首部,就会认为代理同意进行 keep-alive 对话。所以,此时客户端和服务器都认为它们在进行 keep-alive 对话,但与它们进行对话的代理却对 keep-alive 一无所知。
  5. 由于代理对 keep-alive 一无所知,所以会将收到的所有数据都回送给客户端,然后等待源端服务器关闭连接。但源端服务器会认为代理已经显式地请求它将连接保持在打开状态了,所以不会去关闭连接。这样,代理就会挂在那里等待连接的关闭。
  6. 客户端在收到了回送的响应报文时,会立即转向下一条请求,在 keep-alive 连接上向代理发送另一条请求。而代理并不认为同一条连接上会有其他请求到来,请求被忽略,浏览器就在这里转圈,不会有任何进展了。
  7. 这种错误的通信方式会使浏览器一直处于挂起状态,直到客户端或服务器将连接超时,并将其关闭为止。

为避免此类代理通信问题的发生,现代的代理都绝不能转发 Connection 首部和所有名字出现在 Connection 值中的首部。 不应转发 keep-alive、Proxy-Authenticate、Proxy-Connection、Transfer-Encoding 和 Upgrade 的首部。

HTTP/1.1 persistent

HTTP/1.1 持久连接在默认情况下是激活的。除非特别指明,否则 HTTP/1.1 假定所有连接都是持久的。要在事务处理结束之后将连接关闭,HTTP/1.1 应用程序必须向报文中显式地添加一个 Connection: close 首部。这是与以前的 HTTP 协议版本很重要的区别,在以前的版本中,keep-alive 连接要么是可选的,要么根本就不支持。

HTTP/1.1 客户端假定在收到响应后,除非响应中包含了 Connection: close 首部,不然 HTTP/1.1 连接就仍维持在打开状态。但是,客户端和服务器仍然可以随时关闭空闲的连接。不发送 Connection: close 并不意味着服务器承诺永远将连接保持在打开状态。

持久连接的限制和规则

  • 发送了 Connection: close 请求首部之后,客户端就无法在那条连接上发送更多的请求了。
  • 如果客户端不想在连接上发送其他请求了,就应该在最后一条请求中发送一个 Connection: close 请求首部。
  • 只有当连接上所有的报文都有正确的、自定义报文长度时——也就是说,实体主体部分的长度都和相应的 Content-Length 一致,或者是用分块传输编码方式编码的——连接才能持久保持。
  • HTTP/1.1 的代理必须能够分别管理与客户端和服务器的持久连接——每个持久连接都只适用于一跳传输。

管道化连接

HTTP/1.1 允许在持久连接上可选地使用请求管道。这是相对于 keep-alive 连接的又一性能优化。在响应到达之前,可以将多条请求放入队列。当第一条请求通过网络流向地球另一端的服务器时,第二条和第三条请求也可以开始发送了。在高时延网络条件下,这样做可以降低网络的环回时间,提高性能。

管道化连接

对管道化连接的几条限制:

  • 如果 HTTP 客户端无法确认连接是持久的,就不应该使用管道。
  • 必须按照与请求相同的顺序回送 HTTP 响应。HTTP 报文中没有序列号标签,因此如果收到的响应失序了,就没办法将其与请求匹配起来了。
  • HTTP 客户端必须做好连接会在任意时刻关闭的准备,还要准备好重发所有未完成的管道化请求。

关闭连接

任意解除连接:所有 HTTP 客户端、服务器或代理都可以在任意时刻关闭一条 TCP 传输连接

连接关闭重试

HTTP 应用程序要做好正确处理非预期关闭的准备。如果在客户端执行事务的过程中,传输连接关闭了,那么,除非事务处理会带来一些副作用,否则客户端就应该重新打开连接,并重试一次。对管道化连接来说,这种情况更加严重一些。客户端可以将大量请求放入队列中排队,但源端服务器可以关闭连接,这样就会留下大量未处理的请求,需要重新调度。

副作用是很重要的问题。如果在发送出一些请求数据之后,收到返回结果之前,连接关闭了,客户端就无法百分之百地确定服务器端实际激活了多少事务。有些事务,比如 GET 一个静态的 HTML 页面,可以反复执行多次,也不会有什么变化。而其他一些事务,比如向一个在线书店 POST 一张订单,就不能重复执行,不然会有下多张订单的危险。

如果一个事务,不管是执行一次还是很多次,得到的结果都相同,这个事务就是幂等的。GET、HEAD、PUT、DELETE、TRACE 和 OPTIONS 。

POST方法为非幂等的,要发送一条非幂等请求,就需要等待来自前一条请求的响应状态。

正常关闭

完全关闭:套接字调用 close() 会将 TCP 连接的输入和输出信道都关闭了。

半关闭:套接字调用 shutdown() 单独关闭输入或输出信道。

重置错误:

关闭连接的输出信道总是很安全的。关闭连接的输入信道比较危险,除非你知道另一端不打算再发送其他数据了。

比如你已经在一条持久连接上发送了 10 条管道式请求了,响应也已经收到了,正在操作系统的缓冲区中存着呢(但应用程序还未将其读走)。现在,假设你发送了第 11 条请求,但服务器认为你使用这条连接的时间已经够长了,决定将其关闭。那么你的第 11 条请求就会被发送到一条已关闭的连接上去,并会向你回送一条重置信息。这个重置信息会清空你的输入缓冲区。

当你最终要去读取数据的时候,会得到一个连接被对端重置的错误,已缓存的未读响应数据都丢失了,尽管其中的大部分都已经成功抵达你的机器了。

正常关闭:

实现正常关闭的应用程序首先应该关闭它们的输出信道,然后等待连接另一端的对等实体关闭它的输出信道。当两端都告诉对方它们不会再发送任何数据(比如关闭输出信道)之后,连接就会被完全关闭,而不会有重置的危险。

想要正常关闭连接的应用程序应该先半关闭其输出信道,然后周期性地检查其输入信道的状态(查找数据,或流的末尾)。如果在一定的时间区间内对端没有关闭输入信道,应用程序可以强制关闭连接,以节省资源。

五、Web服务器

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

1.建立连接

处理新连接

客户端请求一条到 Web 服务器的 TCP 连接时,Web 服务器会建立连接,判断连接的另一端是哪个客户端,从 TCP 连接中将 IP 地址解析出来。一旦新连接建立起来并被接受,服务器就会将新连接添加到其现存 Web 服务器连接列表中,做好监视连接上数据传输的准备。

客户端主机名识别

可以用“反向 DNS”对大部分 Web 服务器进行配置,以便将客户端 IP 地址转换成客户端主机名。Web 服务器可以将客户端主机名用于详细的访问控制和日志记录。但要注意的是,主机名查找可能会花费很长时间,这样会降低 Web 事务处理的速度。很多大容量 Web 服务器要么会禁止主机名解析,要么只允许对特定内容进行解析。

通过ident确定客户端用户

有些 Web 服务器还支持 IETF 的 ident 协议。服务器可以通过 ident 协议找到发起 HTTP 连接的用户名。如果客户端支持 ident 协议,就在 TCP 端口 113 上监听 ident 请求。客户端打开了一条 HTTP 连接。然后,服务器打开自己到客户端 ident 服务器端口(113)的连接,发送一条简单的请求,询问与(由客户端和服务器端口号指定的)新连接相对应的用户名,并从客户端解析出包含用户名的响应。

ident 在组织内部可以很好地工作,但出于多种原因,在公共因特网上并不能很好地工作,原因包括:

  • 很多客户端 PC 没有运行 ident 识别协议守护进程软件;
  • ident 协议会使 HTTP 事务处理产生严重的时延;
  • 很多防火墙不允许 ident 流量进入;
  • ident 协议不安全,容易被伪造;
  • ident 协议也不支持虚拟 IP 地址;
  • 暴露客户端的用户名还涉及隐私问题。

2.接收请求报文

解析请求报文

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

解析请求报文时,Web 服务器会不定期地从网络上接收输入数据。网络连接可能随时都会出现延迟。Web 服务器需要从网络中读取数据,将部分报文数据临时存储在内存中,直到收到足以进行解析的数据并理解其意义为止。

因为请求可能会在任意时刻到达,所以 Web 服务器会不停地观察有无新的 Web 请求。不同的 Web 服务器结构会以不同的方式为请求服务:

  • 单线程web服务器
  • 多进程及多线程web服务器
  • 复用 I/O 的服务器
  • 复用的多线程web服务器

3.处理请求

一旦 Web 服务器收到了请求,就可以根据方法、资源、首部和可选的主体部分来对请求进行处理了。有些方法(比如 POST)要求请求报文中必须带有实体主体部分的数据。其他一些方法(比如 OPTIONS)允许有请求的主体部分,也允许没有。少数方法(比如 GET)禁止在请求报文中包含实体的主体数据。

4.对资源的映射及访问

docroot

Web 服务器支持各种不同类型的资源映射,但最简单的资源映射形式就是用请求 URI 作为名字来访问 Web 服务器文件系统中的文件。通常,Web 服务器的文件系统中会有一个特殊的文件夹专门用于存放 Web 内容。这个文件夹被称为文档的根目录(document root,或 docroot)。Web 服务器从请求报文中获取 URI,并将其附加在文档根目录的后面。

目录列表

Web 服务器可以接收对目录 URL 的请求,其路径可以解析为一个目录,而不是文件。我们可以对大多数 Web 服务器进行配置,使其在客户端请求目录 URL 时采取不同的动作。

  • 返回一个错误。
  • 不返回目录,返回一个特殊的默认“索引文件”。
  • 扫描目录,返回一个包含目录内容的 HTML 页面。

大多数 Web 服务器都会去查找目录中一个名为 index.html 的文件来代表此目录。如果用户请求的是一个目录的 URL,而且这个目录中有一个名为 index.html 的文件,服务器就会返回那个文件的内容。

动态内容资源的映射

Web 服务器还可以将 URI 映射为动态资源——也就是说,映射到按需动态生成内容的程序上去。实际上,有一大类名为应用程序服务器的 Web 服务器会将 Web 服务器连接到复杂的后端应用程序上去。Web 服务器要能够分辨出资源什么时候是动态的,动态内容生成程序位于何处,以及如何运行那个程序。

访问控制

Web 服务器还可以为特定资源进行访问控制。有请求到达,要访问受控资源时,Web 服务器可以根据客户端的 IP 地址进行访问控制,也可以要求输入密码来访问资源。

5.构建响应

响应实体

一旦 Web 服务器识别出了资源,就执行请求方法中描述的动作,并返回响应报文。如果有响应主体的话,响应报文中通常包括:

  • HTTP版本、状态码、原因短语

  • 描述了响应主体 MIME 类型的 Content-Type 首部;

  • 描述了响应主体长度的 Content-Length 首部;

  • 实际报文的主体内容。

MIME类型

  • MIME类型:用文件的扩展名来确定 MIME 类型
  • 魔法分类:扫描每个资源的内容,并将其与一个已知模式表(被称为魔法文件)进行匹配,以决定每个文件的 MIME 类型
  • 显式分类:可以对 Web 服务器进行配置,使其不考虑文件的扩展名或内容,强制特定文件或目录内容拥有某个 MIME 类型
  • 类型协商:与用户协商来觉得使用哪种格式(及相关的 MIME 类型)最好。

重定向

Web 服务器有时会返回重定向响应而不是成功的报文。 Web 服务器可以将浏览器重定向到其他地方来执行请求。Location 响应首部包含了内容的新地址或优选地址的 URI。重定向可用于下列情况。

  • 永久搬离的资源
  • 临时搬离的资源
  • URL增强
  • 负载均衡
  • 服务器关联
  • 规范目录名称

6.发送响应

7.记录日志

六、代理

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

HTTP 的代理服务器既是 Web 服务器又是 Web 客户端。HTTP 客户端会向代理发送请求报文,代理服务器必须像 Web 服务器一样,正确地处理请求和连接,然后返回响应。同时,代理自身要向服务器发送请求,这样,其行为就必须像正确的 HTTP 客户端一样,要发送请求并接收响应。

私有和共享代理

公共代理:大多数代理都是公共的共享代理。集中式代理的成本效率更高,更容易管理。某些代理应用,比如高速缓存代理服务器,会利用用户间共同的请求,这样的话,汇入同一个代理服务器的用户越多,它就越有用。

私有代理:专用的私有代理并不常见,但它们确实存在,尤其是直接运行在客户端计算机上的时候。

代理和网关的对比

代理连接的是两个或多个使用相同协议的应用程序,而网关连接的则是两个或多个使用不同协议的端点。

为什么使用代理

代理服务器可以实现各种时髦且有用的功能。它们可以改善安全性,提高性能,节省费用。代理服务器可以看到并接触到所有流过的 HTTP 流量,所以代理可以监视流量并对其进行修改,以实现很多有用的增值 Web 服务。

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

客户端代理设置

  • 手工配置:
  • PAC文件:PAC 文件是一些小型的 JavaScript 程序,可以在运行过程中计算代理设置,因此,是一种更动态的代理配置解决方案。访问每个文档时,JavaScript 函数都会选择恰当的代理服务器。
  • WPAD:WPAD 协议的算法会使用发现机制的逐级上升策略自动地为浏览器查找合适的 PAC 文件。

实现 WPAD 协议的客户端需要:

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

与代理请求有关的问题

代理URI与服务器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
复制代码

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

由于将流量重定向到代理服务器的方式有所不同,通用的代理服务器既应该支持请求报文中的完整 URI,也应该支持部分 URI。如果是显式的代理请求,代理就应该使用完整 URI,如果是 Web 服务器请求,就应该使用部分 URI 和虚拟 Host 首部。

使用完整和部分 URI 的规则如下所示。

  • 如果提供的是完整 URI,代理就应该使用这个完整 URI。
  • 如果提供的是部分 URI,而且有 Host 首部,就应该用 Host 首部来确定原始服务器的名字和端口号。
  • 如果提供的是部分 URI,而且没有 Host 首部,就要用其他方法来确定原始服务器:
    • 如果代理是代表原始服务器的替代物,可以用真实服务器的地址和端口号来配置代理;
    • 如果流量被拦截了,而且拦截者也可以提供原始的 IP 地址和端口,代理就可以使用拦截技术提供的 IP 地址和端口号
    • 如果所有方法都失败了,代理没有足够的信息来确定原始服务器,就必须返回一条错误报文(通常是建议用户升级到支持 Host 首部的现代浏览器)

追踪报文

Via首部

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

Via 首部字段用于记录报文的转发,诊断报文循环,标识请求 / 响应链上所有发送者的协议能力。

Via语法

Via 首部字段包含一个由逗号分隔的路标(waypoint)。每个路标都表示一个独立的代理服务器或网关,且包含与那个中间节点的协议和地址有关的信息。

每个 Via 路标中最多包含 4 个组件:一个可选的协议名(默认为 HTTP)、一个必选的协议版本、一个必选的节点名和一个可选的描述性注释。

Via: 1.1 cache.joes-hardware.com, 1.1 proxy.irenes-isp.net
复制代码
Via与网关

有些代理会为使用非 HTTP 协议的服务器提供网关的功能。Via 首部记录了这些协议转换,这样,HTTP 应用程序就会了解代理链上各点的协议处理能力以及所做的协议转换了。

Via: FTP/1.0 proxy.irenes-isp.net (Traffic-Server/5.0.1-17882 [cMs f ])
复制代码

TRACE方法

代理服务器可以在转发报文时对其进行修改。可以添加、修改或删除首部,也可以将主体部分转换成不同的格式。为了便于对代理网络进行诊断,我们需要有一种便捷的方式来观察在通过 HTTP 代理网络逐跳转发报文的过程中,报文是怎样变化的。

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

当 TRACE 请求到达目的服务器时,4 整条请求报文都会被封装在一条 HTTP 响应 的主体中回送给发送端。当 TRACE 响应到达时,客户端可以检查服务器收到的确切报文,以及它所经过的代理列表(在via首部中)

trace相应

代理认证

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

  • 对受限内容的请求到达一台代理服务器时,代理服务器可以返回一个要求使用访问证书的 407 Proxy Authorization Required 状态码,以及一个用于描述怎样提供这些证书的 Proxy-Authenticate 首部字段
  • 客户端收到 407 响应时,会尝试着从本地数据库中,或者通过提示用户来搜集所需要的证书。
  • 只要获得了证书,客户端就会重新发送请求,在 Proxy-Authorization 首部字段中提供所要求的证书。
  • 如果证书有效,代理就会将原始请求沿着传输链路向下传送;否则,就发送另一条 407 应答。

代理认证

七、缓存

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

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

命中和未命中

缓存无法保存世界上每份文档的副本。在缓存中能找到请求的文档的副本,称为缓存命中,未能在缓存中找到副本而把请求转发给原始服务器,称为缓存未命中

缓存命中率:由缓存提供服务的请求所占的比例。

字节命中率:表示的是缓存提供的字节在传输的所有字节中所占的比例。

再验证

原始服务器的内容可能会发生变化,缓存要不时对其进行检测,看看它们保存的副本是否仍是服务器上最新的副本,这被称为 HTTP 再验证

缓存可以在任意时刻,以任意的频率对副本进行再验证。但由于缓存中通常会包含数百万的文档,而且网络带宽是很珍贵的,所以大部分缓存只有在客户端发起请求,并且副本旧得足以需要检测的时候,才会对副本进行再验证。

缓存对缓存的副本进行再验证时,会向原始服务器发送一个小的再验证请求。如果内容没有变化,服务器会以一个小的 304 Not Modified 进行响应。只要缓存知道副本仍然有效,就会再次将副本标识为暂时新鲜的,并将副本提供给客户端,这被称作再验证命中缓慢命中。这种方式确实要与原始服务器进行核对,所以会比单纯的缓存命中要慢,但它没有从服务器中获取对象数据,所以要比缓存未命中快一些。

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

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

  • 再验证命中:如果服务器对象未被修改,服务器会向客户端发送一个小的 HTTP 304 Not Modified 响应。
  • 再验证未命中:如果服务器内容已被修改,服务器会向客户端发送一条普通的、带有完整内容的 HTTP 200 OK 响应。
  • 对象被删除:如果服务器对象已经被删除了,服务器就回送一个 404 Not Found 响应,缓存也会将其副本删除。

缓存的拓扑结构

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

公有代理缓存:公有缓存是特殊的共享代理服务器,被称为缓存代理服务器。公有缓存会接受来自多个用户的访问,所以通过它可以更好地减少冗余流量。

缓存的层次结构

缓存的层次结构

缓存的处理步骤

  1. 接收——缓存从网络中读取抵达的请求报文。
  2. 解析——缓存对报文进行解析,提取出 URL 和各种首部。
  3. 查询——缓存查看是否有本地副本可用,如果没有,就获取一份副本(并将其保存在本地)。
  4. 新鲜度检测——缓存查看已缓存副本是否足够新鲜,如果不是,就询问服务器是否有任何更新。
  5. 创建响应——缓存会用新的首部和已缓存的主体来构建一条响应报文。
  6. 发送——缓存通过网络将响应发回给客户端。
  7. 日志——缓存可选地创建一个日志文件条目来描述这个事务。

缓存get请求流程图

保持副本的新鲜

过期日期

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

max-age值定义了文档的最大使用期 — 从第一次生成文档到文档不在新鲜、无法使用位置,最大的合法生成时间,以秒为单位。

Cache-Control: max-age=484200
复制代码

Expires 指定一个绝对的过期日期,如果日期已经过了,说明文档不再新鲜。

Expires: Fri, 05 Jul 2002, 05:00:00 GMT
复制代码

服务器再验证

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

  • 如果再验证显示内容发生了变化,缓存会获取一份新的文档副本,并将其存储在旧文档的位置上,然后将文档发送给客户端。
  • 如果再验证显示内容没有发生变化,缓存只需要获取新的首部,包括一个新的过期日期,并对缓存中的首部进行更新就行了。

用条件方法进行再验证

HTTP 允许缓存向原始服务器发送一个“条件 GET”,请求服务器只有在文档与缓存中现有的副本不同时,才回送对象主体。通过这种方式,将新鲜度检测和对象获取结合成了单个条件 GET。向 GET 请求报文中添加一些特殊的条件首部,就可以发起条件 GET。只有条件为真时,Web 服务器才会返回对象。

If-Modified-Since:Data再验证
  • 如果自指定日期后,文档被修改了,If-Modified-Since 条件就为真,通常 GET 就会成功执行。携带新首部的新文档会被返回给缓存,新首部除了其他信息之外,还包含了一个新的过期日期。
  • 如果自指定日期后,文档没被修改过,条件就为假,会向客户端返回一个小的 304 Not Modified 响应报文。一般会发送一个新的过期日期。

If-Modified-Since 首部可以与 Last-Modified 服务器响应首部配合工作。原始服务器会将最后的修改日期附加到所提供的文档上去。当缓存要对已缓存文档进行再验证时,就会包含一个 If-Modified-Since 首部,其中携带有最后修改已缓存副本的日期:

If-Modified-Since: <cached last-modified date>
复制代码

如果在此期间内容被修改了,最后的修改日期就会有所不同,原始服务器就会回送新的文档。否则,服务器会注意到缓存的最后修改日期与服务器文档当前的最后修改日期相符,会返回一个 304 Not Modified 响应。

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

实体标签是附加到文档上的任意标签(引用字符串)。它们可能包含了文档的序列号或版本名,或者是文档内容的校验和及其他指纹信息。

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

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

如果服务器回送了一个实体标签,HTTP/1.1 客户端就必须使用实体标签验证器。如果服务器只回送了一个 Last-Modified 值,客户端就可以使用 If-Modified-Since 验证。如果实体标签和最后修改日期都提供了,客户端就应该使用这两种再验证方案,这样 HTTP/1.0 和 HTTP/1.1 缓存就都可以正确响应了。

控制缓存的能力

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

no-Cache:标识为 no-cache 的响应实际上是可以存储在本地缓存区中的。只是在与原始服务器进行新鲜度再验证之前,缓存不能将其提供给客户端使用。

max-age:文档的最大使用期

Expires:过期日期

must-revalidateCache-Control: must-revalidate 响应首部告诉缓存,在事先没有跟原始服务器进行再验证的情况下,不能提供这个对象的陈旧副本。如果在缓存进行 must-revalidate 新鲜度检查时,原始服务器不可用,缓存就必须返回一条 504 Gateway Timeout 错误。

八、网关、隧道及中继

网关

网关可以作为资源和应用程序之间的粘合剂。网关可以连接两个或多个使用不同协议的应用程序。

服务器端网关:通过 HTTP 与客户端对话,通过其他协议与服务器通信( HTTP/*客户端网关:通过其他协议与客户端对话,通过 HTTP 与服务器通信( */HTTP

协议网关

可以显式地配置浏览器使用网关,对流量进行透明的拦截,或者将网关配置为替代者(反向代理)。

浏览器可以通过配置,让特定的协议使用特定的网关

一般的 HTTP 流量不受影响,会继续流入原始服务器。但对 FTP URL 的 请求则被放在 HTTP 请求中发送给网关 gw1.joes-hardware.com。网关代表客户端执行 FTP 事务,并通过 HTTP 将结果回送给客户端。

常见的网类型:

  • HTTP/* 服务器端Web网关:请求流入原始服务器时,服务器端 Web 网关会将客户端 HTTP 请求转换为其他协议
  • HTTP/HTTPS 服务器安全网关:一个组织可以通过网关对所有的输入 Web 请求加密,以提供额外的隐私和安全性保护。客户端可以用普通的 HTTP 浏览 Web 内容,但网关会自动加密用户的对话
  • HTTPS/HTTP 客户端安全加速器网关:接收安全的 HTTPS 流量,对安全流量进行解密,并向 Web 服务器发送普通的 HTTP 请求。这些网关中通常都包含专用的解密硬件,以比原始服务器有效得多的方式来解密安全流量,以减轻原始服务器的负荷。

资源网关

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

应用程序服务器可以将HTTP客户端连接任意后台应用程序

两个客户端是通过 HTTP 连接到应用程序服务器的。但应用程序服务器并没有回送文件,而是将请求通过一个网关应用编程接口(Application Programming Interface,API)发送给运行在服务器上的应用程序,应用程序将请求处理后,会把处理结果返回给网关,网关会向服务器返回一条响应或响应数据,服务器则会将其转发给客户端。服务器和网关是相互独立的。

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

CGI
  • CGI 是第一个,可能仍然是得到最广泛使用的服务器扩展。
  • CGI 应用程序是独立于服务器的,所以,几乎可以用任意语言来实现,包括 Perl、Tcl、C 和各种 shell 语言。
  • CGI 很简单,几乎所有的 HTTP 服务器都支持它。图 8-9显示了 CGI 模型的基本运行机制。
  • CGI 的处理对用户来说是不可见的。
  • CGI 还能很好地保护服务器,防止一些糟糕的扩展对它造成的破坏。
  • 为每条 CGI 请求引发一个新进程的开销是很高的,会限制那些使用 CGI 的服务器的性能,并且会加重服务端机器资源的负担。为了解决这个问题,人们开发了一种新型 CGI——并将其恰当地称为快速 CGI。这个接口模拟了 CGI,但它是作为持久守护进程运行的,消除了为每个请求建立或拆除新进程所带来的性能损耗。
服务器扩展API

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

Web服务

随着Web应用程序提供的服务类型越来越多,大家逐渐把HTTP作为一种连接应用程序的基础软件来使用,在将应用程序连接起来的过程中,一个更为棘手的问题是在两个应用程序之间进行协议接口的协商,以便这些应用程序可以进行数据的交换。应用程序之间要配合工作,所要交互的信息比 HTTP 首部所能表达的信息要复杂得多。

因此,因特网委员会开发了一组允许 Web 应用程序之间相互通信的标准和协议。

什么是Web服务?

  1. 基于 Web 提供的服务,服务器端提供资源让客户端访问
  2. 一个跨语言、跨平台的标准和协议规范
  3. 多语言、跨平台的应用间通信整合方案

Web 服务可以用 XML 通过 SOAP 来交换信息。XML(Extensible Markup Language,扩展标记语言)提供了一种创建数据对象的定制信息,并对其进行解释的方法。SOAP(Simple Object Access Protocol,简单对象访问协议)是向 HTTP 报文中添加 XML 信息的标准方式。

隧道

Web隧道

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

Web 隧道是用 HTTP 的 CONNECT 方法建立起来的。CONNECT方法可以创建一条到达目的服务器上任意端口的TCP连接,在建立起隧道后,隧道就会对服务器和客户端之间的数据进行盲转发。

当客户端向网关发起 HTTP CONNECT 的时候,就是告诉网关,先在网关和目标服务器之间先建立起TCP连接,在这个TCP连接建立起来之后,目标服务器会返回一个回复给网关,网关将这个回复转发给客户端,这个Response是网关跟目标服务器TCP连接建立的状态回复,而不是请求数据的Response。在此之后,客户端跟目标服务器的所有通信都将使用之前建立起来的TCP连接。这种情况下的HTTP隧道,网关仅仅实现转发,而不会关心转发的数据。

CONNECT请求语法格式如下:

CONNECT home.netscape.com:443 HTTP/1.0
复制代码

其和普通的HTTP方法差不多,只是将后面的URL替换成了一个主机名:端口号格式的数据。

CONNECT的响应也同HTTP普通方法差不多,建立成功则返回200的状态码,只是后面的message一般显示为“Connection Established”。

HTTP/1.0 200 Connection Established
复制代码

一条隧道的建立的整个过程如下图所示:

隧道建立

SSL隧道

SSL协议,其信息是加密的,虽然我们一般可以通过443端口直接进行SSL连接,但是无法通过传统的有HTTP防火墙的代理服务器转发。这个时候可以利用隧道通过一条 HTTP 连接来传输 SSL 流量,以穿过端口 80 的 HTTP 防火墙。

SSL隧道

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

我们之前提到,客户端可以通过 HTTP/HTTPS 网关与服务器进行SSL会话,也可以通过隧道的方式进行SSL会话,那SSL隧道比HTTP/HTTPS网关的优势在哪里呢?

HTTP/HTTPS网关的缺点:

  • 客户端到网关之间的连接是普通的HTTP连接,所以这一段传输是不安全的

  • 尽管代理是已认证主体,但客户端无法对远端服务器执行 SSL 客户端认证

  • 网关需要支持完整的SSL实现

  • 网关有机会对客户端与目标服务器之间的通信数据进行窥探,而且有机会对数据进行串改

SSL隧道的优点:

SSL会话是建立在客户端和服务器端之间的,中间的代理服务器只是将加密数据经过隧道传输,所以中间的代理无需实现SSL,只需要转发数据即可。

隧道认证

可以将代理的认证支持与隧道配合使用,对客户端使用隧道的权利进行认证

隧道认证

隧道的安全性考虑

总的来说,隧道网关无法验证目前使用的协议是否就是它原本打算经过隧道传输的协议。因此,比如说,一些喜欢捣乱的用户可能会通过本打算用于 SSL 的隧道,越过公司防火墙传递因特网游戏流量,而恶意用户可能会用隧道打开 Telnet 会话,或用隧道绕过公司的 E-mail 扫描器来发送 E-mail。为了降低对隧道的滥用,网关应该只为特定的知名端口,比如 HTTPS 的端口 443,打开隧道。

中继

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

中继的优点是实现简单,当我们只是提供一个简单的过滤、诊断或内容转换功能的代理的时候,可以考虑使用中继。但是由于其盲转发的特性,会有一个很常见的问题出现,由于它们无法正确处理 Connection 首部,所以有潜在的挂起 keep-alive 连接的可能。

九、Web机器人

爬虫及爬行方式

Web 爬虫是一种机器人,它们会递归地对各种信息性 Web 站点进行遍历,获取第一个 Web 页面,然后获取那个页面指向的所有 Web 页面,然后是那些页面指向的所有 Web 页面,依此类推。递归地追踪这些 Web 链接的机器人会沿着 HTML 超链创建的网络“爬行”,所以将其称为爬虫(crawler)或蜘蛛(spider)。

根集

爬虫开始访问的 URL 初始集合被称作根集(root set)。挑选根集时,应该从足够多不同的站点中选择 URL,这样,爬遍所有的链接才能最终到达大部分你感兴趣的 Web 页面。通常,一个好的根集会包括一些大的流行 Web 站点、一个新创建页面的列表和一个不经常被链接的无名页面列表。

环路

机器人在 Web 上爬行时,要特别小心不要陷入循环,或环路(cycle)之中。机器人必须知道它们到过何处,以避免环路的出现。环路会造成机器人陷阱,这些陷阱会暂停或减缓机器人的爬行进程。由于URL数量十分巨大,为了快速判定一个链接是否已经访问过,爬虫所使用的数据结构在访问速度和内存使用方面都应该是高效的。下面列出了大规模Web爬虫对其访问过的地址进行管理时使用的一些技术:

  • 树和散列表
  • 位图
  • 检查点:一定要将已访问的URL保存在硬盘上,以防止爬虫崩溃
  • 分类:有些大型 Web 机器人会使用机器人“集群”,每个独立的计算机是一个机器人,以汇接方式工作。为每个机器人分配一个特定的 URL“片”,由其负责爬行。

出现环路的几种情况

  • URL别名导致环路
  • 文件系统连接环路
  • 动态虚拟Web空间

如何避免陷入环路

  • 规范化URL

    • 如果没有指定端口的话,就向主机名中添加“:80”。
    • 将所有转义符 %xx 都转换成等价字符。
    • 删除 # 标签。
  • 广度优先搜索的爬行

  • 节流:限制一段时间内机器人可以从一个 Web 站点获取的页面数量。如果机器人跳进了一个环路,试图不断地访问某个站点的别名,也可以通过节流来限制重复的页面总数和对服务器的访问总数。

  • 限制URL的大小:机器人可能会拒绝爬行超出特定长度(通常是 1KB)的 URL。

  • URL/站点黑名单

  • 模式检测

  • 内容指纹

  • 人工监视

Web机器人的HTTP

请求首部

  • User-Agent:将发起请求的机器人名字告诉服务器

  • From:提供机器人的用户/管理者的Email地址

  • Accept:告知服务器可以发送哪些媒体类型,这有助于确保机器人只接收它感兴趣的内容。

  • Referer:提供包含了当前请求URL的文档的URL

  • Host:明确机器人要访问的主机名。随着虚拟主机的流行,请求中不包含 Host 首部的话,可能会使机器人将错误的内容与一个特定的 URL 关联起来。

对响应的处理

  • 机器人至少应能够处理一些常见的状态码
  • 除了HTTP首部所嵌的信息,机器人还会在实体中查找信息

行为不当的机器人

  • 失控的机器人:机器人发起 HTTP 请求的速度要比在 Web 上冲浪的人类快得多,它们通常都运行在具有快速网络链路的高速计算机上。如果机器人存在编程逻辑错误,或者陷入了环路之中,就可能会向 Web 服务器发出大量的负载——很可能会使服务器过载,并拒绝为任何其他人提供服务。所有的机器人编写者都必须特别小心地设计一些保护措施,以避免失控机器人带来的危害。
  • 失效的URL:有些机器人会去访问 URL 列表。这些列表可能很老了。如果一个 Web 站点对其内容进行了大量的修改,机器人可能会对大量不存在的 URL 发起请求。这会激怒某些 Web 站点的管理员,他们不喜欢他们的错误日志中充满了对不存在文档的访问请求,也不希望提供出错页面的开销降低其 Web 服务器的处理能力。
  • 很长的错误URL:由于环路和编程错误的存在,机器人可能会向 Web 站点请求一些很大的、无意义的 URL。如果 URL 足够长的话,就会降低 Web 服务器的性能,使 Web 服务器的访问日志杂乱不堪,甚至会使一些比较脆弱的 Web 服务器崩溃。
  • 访问敏感数据的机器人
  • 动态网关访问

拒绝机器人访问

robots.txt 的思想很简单。所有 Web 服务器都可以在服务器的文档根目录中提供一个可选的、名为 robots.txt 的文件。这个文件包含的信息说明了机器人可以访问服务器的哪些部分。如果机器人遵循这个自愿约束标准,它会在访问那个站点的所有其他资源之前,从 Web 站点请求 robots.txt 文件。

获取robots.txt文件

机器人会用 HTTP 的 GET 方法来获取 robots.txt 资源,就像获取 Web 服务器上所有其他资源一样。如果有 robots.txt 文件的话,服务器会将其放在一个 text/plain 主体中返回。如果服务器以 404 Not Found HTTP 状态码进行响应,机器人就可以认为这个服务器上没有机器人访问限制,它可以请求任意的文件。

机器人应该在 From 首部和 User-Agent 首部中传输标识信息,以帮助站点管理者对机器人的访问进行跟踪,并在站点管理者要查询,或投诉的机器人事件中提供一些联系信息。

GET /robots.txt HTTP/1.0
Host: www.joes-hardware.com
User-Agent: Slurp/2.0
复制代码

很多 Web 站点都没有 robots.txt 资源,但机器人并不知道这一点。它必须尝试着从每个站点上获取 robots.txt 资源。机器人会根据对 robots.txt 检索的结果采取不同的行动。

  • 如果服务器以一个成功状态(HTTP 状态码 2XX)为响应,机器人就必须对内容进行解析,并使用排斥规则从那个站点上获取内容。
  • 如果服务器响应说明资源并不存在(HTTP 状态码 404),机器人就可以认为服务器没有激活任何排斥规则,对此站点的访问不受 robots.txt 的限制。
  • 如果服务器响应说明有访问限制(HTTP 状态码 401 或 403),机器人就应该认为对此站点的访问是完全受限的。
  • 如果请求尝试的结果是临时故障(HTTP 状态码 503),机器人就应该推迟对此站点的访问,直到可以获取该资源为止。
  • 如果服务器响应说明是重定向(HTTP 状态码 3XX),机器人就应该跟着重定向,知道找到资源为止。

robots.txt文件格式

robots.txt 文件采用了非常简单的,面向行的语法。robots.txt 文件中有三种类型的行:空行注释行规则行。规则行看起来就像 HTTP 首部一样,用于模式匹配。

# this robots.txt file allows Slurp & Webcrawler to crawl
# the public parts of our site, but no other robots...

User-Agent: slurp
User-Agent: webcrawler
Disallow: /private

User-Agent: *
Disallow:
复制代码

robots.txt 文件中的行可以从逻辑上划分成“记录”。每条记录都为一组特定的机器人描述了一组排斥规则。通过这种方式,可以为不同的机器人使用不同的排斥规则。每条记录中都包含了一组规则行,由一个空行或文件结束符终止。记录以一个或多个 User-Agent 行开始,说明哪些机器人会受此记录的影响,后面跟着一些 Disallow 和 Allow 行,用来说明这些机器人可以访问哪些URL。

  1. User-Agent行

每个机器人记录都以一个或多个下列形式的 User-Agent 行开始:User-Agent: <robot-name>User-Agent: * 。 机器人处理 robots.txt 文件时,它所遵循的记录必须符合下列规则之一:

  • 第一个 <robot-name> 是机器人名的大小写无关的子字符串;
  • 第一个 <robot-name>*

如果机器人无法找到记录与之匹配,访问不受限。

  1. Disallow 和 Allow 行

Disallow 和 Allow 行紧跟在机器人排斥记录的 User-Agent 行之后。用来说明显式禁止或显式允许特定机器人使用哪些 URL 路径。

机器人必须将期望访问的 URL 按序与排斥记录中所有的 Disallow 和 Allow 规则进行匹配。使用找到的第一个匹配项。如果没有找到匹配项,就说明允许使用这个 URL。

  1. Disallow/Allow 前缀匹配
  • Disallow 和 Allow 规则要求大小写相关的前缀匹配。
  • 在进行比较之前,要将规则路径或 URL 路径中所有“被转义”的字符(%XX)都反转为字节(除了正斜杠 %2F 之外,它必须严格匹配)。
  • 如果规则路径为空字符串,就与所有内容都匹配。

缓存和robots.txt过期

如果一个机器人在每次访问文件之前都要重新获取 robots.txt 文件,Web 服务器上的负载就会加倍,机器人的效率也会降低。机器人使用的替代方法是,它会周期性地获取 robots.txt 文件,并将得到的文件缓存起来。机器人会使用这个 robots.txt 文件的缓存副本,直到其过期为止。原始服务器和机器人都会使用标准的 HTTP 存控制机制来控制 robots.txt 文件的缓存。

HTML的robot-control元标签

robots.txt 文件允许站点管理员将机器人排除在 Web 站点的部分或全部内容之外。robots.txt 文件的一个缺点就是它是 Web 站点管理员,而不是各部分内容的作者所有的。

HTML 页面的作者有一种更直接的方式可以限制机器人访问那些独立的页面。他们可以直接在 HMTL 文档中添加 robot-control 标签。遵循 robot-control HTML 标签规则的机器人仍然可以获取文档,但如果其中有机器人排斥标签,它们就会忽略这些文档。

机器人排斥标签是以如下形式,通过 HTML 的 META 标签来实现的:

<META NAME="ROBOTS" CONTENT="NOINDEX">
复制代码

常见的robot-control META指令:

  • NOINDEX:告诉机器人不要对页面的内容进行处理,忽略文档
  • NOFOLLOW:告诉机器人不要爬取这个页面的任何外链
  • INDEX:告诉机器人可以对页面内容进行索引
  • FOLLOW:告诉机器人可以爬取这个页面的任何外链
  • NOARCHIVE:告诉机器人不应该缓存这个页面的本地副本
  • ALL:等价于 INDEX、FOLLOW
  • NONE:等价于 NOINDEX、NOFOLLOW

其他META标签指令:

  • DESCRIPTION:允许作者为Web页面定义一个短小的文本摘要来描述其Web页面。
  • KEYWORDS:关联一个由逗号分隔的Web页面描述列表,为关键字搜索提供帮助。

十、HTTP-NG

十一、客户端识别与cookie机制

HTTP 最初是一个匿名、无状态的请求 / 响应协议。服务器处理来自客户端的请求,然后向客户端回送一条响应。Web 服务器几乎没有什么信息可以用来判定是哪个用户发送的请求,也无法记录来访用户的请求序列。现代的 Web 站点希望能够提供个性化的接触。它们希望对连接另一端的用户有更多的了解,并且能在用户浏览页面时对其进行跟踪。

承载用户相关信息的HTTP首部

首部名称首部类型描述
From请求用户的 email 地址
User-Agent请求用户的浏览器软件
Referer请求用户是从这个页面上依照链接跳转过来的
Authorization请求用户名和密码
Client-IP扩展(请求)客户端的 IP 地址
X-Forwarded-For扩展(请求)客户端的 IP 地址
Cookie扩展(请求)服务器产生的 ID 标签

From 首部包含了用户的 E-mail 地址。每个用户都有不同的 E-mail 地址,所以在理想情况下,可以将这个地址作为可行的源端来识别用户。但由于担心那些不讲道德的服务器会搜集这些 E-mail 地址,用于垃圾邮件的散发,所以很少有浏览器会发送 From 首部。实际上,From 首部是由自动化的机器人或蜘蛛发送的,这样在出现问题时,网管还有个地方可以发送愤怒的投诉邮件

User-Agent 首部可以将用户所用浏览器的相关信息告知服务器,包括程序的名称和版本,通常还包含操作系统的相关信息。要实现定制内容与特定的浏览器及其属性间的良好互操作时,这个首部是非常有用的,但它并没有为识别特定的用户提供太多有意义的帮助。

Referer 首部提供了用户来源页面的 URL。Referer 首部自身并不能完全标识用户,但它确实说明了用户之前访问过哪个页面,通过它可以更好地理解用户的浏览行为,以及用户的兴趣所在。

From、User-Agent 和 Referer 首部都不足以实现可靠的识别。

客户端 IP 地址

早期的 Web 先锋曾尝试着将客户端 IP 地址作为一种标识形式使用。但是,使用客户端 IP 地址来识别用户存在着很多缺点,限制了将其作为用户识别技术的效能。

  • 客户端 IP 地址描述的是所用的机器,而不是用户。如果多个用户共享同一台计算机,就无法对其进行区分了。
  • 很多因特网服务提供商都会在用户登录时为其动态分配 IP 地址。用户每次登录时,都会得到一个不同的地址,因此 Web 服务器不能假设 IP 地址可以在各登录会话之间标识用户。
  • 为了提高安全性,并对稀缺的地址资源进行管理,很多用户都是通过网络地址转换(Network Address Translation,NAT)防火墙来浏览网络内容的。这些 NAT 设备隐藏了防火墙后面那些实际客户端的 IP 地址,将实际的客户端 IP 地址转换成了一个共享的防火墙 IP 地址(和不同的端口号)。
  • HTTP 代理和网关通常会打开一些新的、到原始服务器的 TCP 连接。Web 服务器看到的将是代理服务器的 IP 地址,而不是客户端的。有些代理为了绕过这个问题会添加特殊的 Client-IP 或 X-Forwarded-For 扩展首部来保存原始的 IP 地址。但不是所有的代理都支持这种行为。

用户登录

Web 服务器无需被动地根据用户的 IP 地址来猜测他的身份,它可以要求用户通过用户名和密码进行认证(登录)来显式地询问用户是谁。

为了使 Web 站点的登录更加简便,HTTP 中包含了一种内建机制,可以用 WWW-Authenticate 首部和 Authorization 首部向 Web 站点传送用户的相关信息。

但是,登录多个 Web 站点是很繁琐的。从一个站点浏览到另一个站点的时候,需要在每个站点上登录。

Cookie

cookie是当前识别用户,实现持久会话的最好方式。

cookie 中包含一个或多个 name=value 这样的键值对。

会话 cookie: 是一种临时 cookie,它记录了用户访问站点时的设置和偏好。用户退出浏览器时,会话 cookie 就被删除了。

持久 cookie: 的生存时间更长一些;它们存储在硬盘上,浏览器退出,计算机重启时它们仍然存在。

会话 cookie 和持久 cookie 之间唯一的区别就是它们的过期时间。

Cookie是如何工作的

用户首次访问Web站点时,服务器对用户一无所知,服务器希望在该用户再次访问的时候能够识别出来,于是就给用户分配了一个独有的cookie,通过 Set-Cookie 响应首部返回给用户。浏览器会记住从服务器返回的 Set-Cookie 首部中的 cookie 内容,并将 cookie 集存储在浏览器的 cookie 数据库中。将来用户再次访问该站点时,浏览器会从数据库中找出该站点对应的 cookie,将其封装在请求首部中发送给服务器。

cookie 中可以包含任意信息,但它们通常都只包含一个服务器为了进行跟踪而产生的独特的识别码。

cookies版本0(Netscape)

最初的 cookie 规范定义了 Set- Cookie 响应首部、cookie 请求首部以及用于控制 cookie 的字段。版本 0 的 cookie 看起来如下所示:

Set-Cookie: name=value [; expires=date] [; path=path] [; domain=domain] [; secure]

Cookie: name1=value1 [; name2=value2] ...
复制代码
Set-Cookie 首部

Set-Cookie 首部有一个强制性的 cookie 名和 cookie 值。后面跟着可选的 cookie 属性,中间由分号分隔。

属性描述
NAME=VALUE强制的,NAME和VALUE都是字符序列,除非包含在双引号内,否则不包括分号、逗号、等号和空格。
Expires可选的,这个属性会指定一个日期字符串,用来定义 cookie 的实际生存期,一旦过了这个日期,就不再存储或发送这个 cookie 了。
Domain可选的,服务器只向指定域中的服务器主机名发送cookie。
Path可选的,通过这个属性可以为服务器上特定的文档分配 cookie,如果Path属性是一个URL路径前缀,就可以附加一个 cookie。
Secure可选的,如果包含了这一属性,就只有在HTTP使用SSL安全连接时才会发送cookie。
Cookie 首部

客户端发送请求时,会将所有与域、路径和安全过滤器相匹配的未过期 cookie 都发送给这个站点。

cookies版本1(RFC 2965)

RFC 2965 定义了一个 cookie 的扩展版本。这个版本 1 标准引入了 Set-Cookie2 首部和 Cookie2 首部,但它也能与版本0系统进行互操作。主要改动包括以下内容:

  • 为每个 cookie 关联上解释性文本,对其目的进行解释。
  • 允许在浏览器退出时,不考虑过期时间,将 cookie 强制销毁。
  • 用相对秒数,而不是绝对日期来表示 cookie 的 Max-Age。
  • 通过 URL 端口号,而不仅仅是域和路径来控制 cookie 的能力。
  • 通过 Cookie 首部回送域、端口和路径过滤器(如果有的话)。
  • 为实现互操作性使用的版本号。
  • 在 Cookie 首部从名字中区分出附加关键字的 $ 前缀。
Set-Cookie2 新增首部
属性描述
Version强制的。这个属性的值是一个整数,对应于 cookie 规范的版本。RFC 2965 为版本1:
Comment可选。这个属性说明了服务器准备如何使用这个cookie。用户可以通过检查此策略来确定是否允许使用带有这个 cookie 的会话。这个值必须采用 UTF-8 编码
CommentURL可选。这个属性提供了一个 URL 指针,指向详细描述了 cookie 目的及策略的文档。用户可以通过查看此策略来判定是否允许使用带有这个 cookie 的会话
Discard可选。如果提供了这个属性,就会在客户端程序终止时,指示客户端放弃这个 cookie
Max-Agecookie 生存期。客户端应该根据 HTTP/1.1 的使用期计算规则来计算 cookie 的使用期。cookie 的使用期比 Max-Age 大时,客户端就应该将这个 cookie 丢弃。值为零说明应该立即将那个 cookie 丢弃
Part可选。这个属性可以单独作为关键字使用,也可以包含一个由逗号分隔的、可以应用 cookie 的端口列表。如果有端口列表,就只能向端口与列表中的端口相匹配的服务器提供 cookie。如果单独提供关键字 Port 而没有值,就只能向当前响应服务器的端口号提供 cookie
Cookie2 首部

版本 1 的 cookie 会带回与传送的每个 cookie 相关的附加信息,用来描述每个 cookie 途径的过滤器。每个匹配的 cookie 都必须包含来自相应 Set-Cookie2 首部的所有 Domain、Port 或 Path 属性。

Set-Cookie2: ID="29046"; Domain=".joes-hardware.com"
Set-Cookie2: color=blue
Set-Cookie2: support-pref="L2"; Domain="customer-care.joes-hardware.com"
Set-Cookie2: Coupon="hammer027"; Version="1"; Path="/tools"
Set-Cookie2: Coupon="handvac103"; Version="1"; Path="/tools/cordless

Cookie2: $Version="1";
        ID="29046"; $Domain=".joes-hardware.com";
        color="blue";
        Coupon="hammer027"; $Path="/tools";
        Coupon="handvac103"; $Path="/tools/cordless
复制代码
版本1的 Cookie2 首部和版本协商

Cookie2 请求首部负责在能够理解不同 cookie 规范版本的客户端和服务器之间进行互操作性的协商。Cookie2 首部告知服务器,用户 Agent 代理理解新形式的 cookie,并提供了所支持的 cookie 标准版本(将其称为 Cookie-Version 更合适一些):

Cookie2: $Version="1"
复制代码

如果服务器理解新形式的 cookie,就能够识别出 Cookie2 首部,并在响应首部发送 Set-Cookie2(而不是 Set-Cookie)。如果客户端从同一个响应中既获得了 Set-Cookie 首部,又获得了 Set-Cookie2 首部,就会忽略老的 Set-Cookie 首部。

如果客户端既支持版本 0 又支持版本 1 的 cookie,但从服务器获得的是版本 0 的 Set-Cookie 首部,就应该带着版本 0 的 Cookie 首部发送 cookie。但客户端还应该发送 Cookie2: $Version="1" 来告知服务器它是可以升级的。

Cookie与会话跟踪

cookie会话跟踪

Cookie、安全性与隐私

cookie 是可以禁止的,而且可以通过日志分析或其他方式来实现大部分跟踪记录,所以 cookie 自身并不是很大的安全隐患。

但是,潜在的滥用情况总是存在的,所以,在处理隐私和用户跟踪信息时,最好还是要小心一些。第三方 Web 站点使用持久 cookie 来跟踪用户就是一种最大的滥用。将这种做法与 IP 地址和 Referer 首部信息结合在一起,这些营销公司就可以构建起相当精确的用户档案和浏览模式信息。

十二、基本认证机制

认证

HTTP的质询/响应认证框架

Web 应用程序收到一条 HTTP 请求报文时,服务器没有按照请求执行动作,而是以一个“认证质询”进行响应,要求用户提供一些保密信息来说明他是谁,从而对其进行质询。

用户再次发起请求时,要附上保密证书(用户名和密码)。如果证书不匹配,服务器可以再次质询客户端,或产生一条错误信息。如果证书匹配,就可以正常完成请求了。

质询/认证框架

认证协议与首部

步骤首部描述方法/状态
请求第一条请求没有认证信息GET
质询WWW-Authenticate服务器用 401 状态拒绝了请求,说明需要用户提供用户名和密码。服务器上可能会分为不同的区域,每个区域都有自己的密码,所以服务器会在 WWW-Authenticate 首部指明要访问的安全域和认证算法。401 Unauthorized
授权Authorization客户端重新发出请求,但会附加一个 Authorization 首部,用来说明认证算法、用户名和密码GET
成功Authorization-Info如果授权证书是正确的,服务器就会将文档返回。有些授权算法会在可选的 Authorization-Info 首部返回一些与授权会话相关的附加信息200 OK

基本认证

基本认证实例

基本认证实例

Base-64编码

HTTP 基本认证将(由冒号分隔的)用户名和密码打包在一起,并用 Base-64 编码方式对其进行编码。

Base-64 编码可以接受二进制字符串、文本、国际字符表示的数据,将其暂时转换成一个易移植的字母表以便传输。然后,在远端就可以解码出原始字符串,而无需担心传输错误了。

有些用户名和密码中会包含国际字符或其他在 HTTP 首部中非法的字符(比如引号、冒号和回车换行符),对这些用户名和密码来说,Base-64 编码是非常有用的。而且,Base-64 编码扰乱了用户名和密码,这样也可以防止管理员在管理服务器和网络时,不小心看到用户名和密码。

代理认证

中间的代理服务器也可以实现认证功能。有些组织会在用户访问服务器、LAN 或无线网络之前,用代理服务器对其进行认证。可以在代理服务器上对访问策略进行集中管理,因此,通过代理服务器提供对某组织内部资源的统一访问控制是一种很便捷的方式。这个过程的第一步就是通过代理认证(proxy authentication)来识别身份。

Web服务器代理服务器
401 Unauthorized407 Unauthorized
WWW-AuthenticateProxy-Authenticate
AuthorizationProxy-Authorization
Authorization-InfoProxy-Authorization-Info

基本认证的缺陷

  1. 基本认证会通过网络发送用户名和密码,这些用户名和密码都是以一种很容易解码的形式表示的。**实际上,密码是以明文形式传输的,任何人都可以读取并将其捕获。**虽然 Base-64 编码通过隐藏用户名和密码,致使友好的用户不太可能在进行网络观测时无意中看到密码,但 Base-64 编码的用户名和密码可以很轻易地通过反向编码过程进行解码,甚至可以用纸笔在几秒钟内手工对其进行解码!**所以经过 Base-64 编码的密码实际上就是“明文”传送的。**如果有动机的第三方用户有可能会去拦截基本认证发送的用户名和密码,就要通过 SSL 加密信道发送所有的 HTTP 事务,或者使用更安全的认证协议,比如摘要认证。
  2. 即使密码是以更难解码的方式加密的,第三方用户仍然可以捕获被修改过的用户名和密码,并将修改过的用户名和密码一次一次地重放给原始服务器,以获得对服务器的访问权。没有什么措施可用来防止这些重放攻击。
  3. 即使将基本认证用于一些不太重要的应用程序,比如公司内部网络的访问控制或个性化内容的访问,一些不良习惯也会让它变得很危险。很多用户由于受不了大量密码保护的服务,会在这些服务间使用相同的用户名和密码。比如说,某个狡猾的恶徒会从免费的因特网邮件网站捕获明文形式的用户名和密码,然后会发现用同样的用户名和密码还可以访问重要的在线银行网站!
  4. 基本认证没有提供任何针对代理和作为中间人的中间节点的防护措施,它们没有修改认证首部,但却修改了报文的其余部分,这样就严重地改变了事务的本质。
  5. 假冒服务器很容易骗过基本认证。如果在用户实际连接到一台恶意服务器或网关的时候,能够让用户相信他连接的是一个受基本认证保护的合法主机,攻击者就可以请求用户输入密码,将其存储起来以备未来使用,然后捏造一条错误信息传送给用户。

将基本认证与加密数据传输(比如 SSL)配合使用,向恶意用户隐藏用户名和密码,会使基本认证变得更加安全。这是一种常用的技巧。

十三、摘要认证

基本认证便捷灵活,但极不安全。用户名和密码都是以明文形式传送的,也没有采取任何措施防止对报文的篡改。安全使用基本认证的唯一方式就是将其与 SSL 配合使用。摘要认证与基本认证兼容,但却更为安全。

摘要认证的改进

摘要认证是另一种 HTTP 认证协议,它试图修复基本认证协议的严重缺陷。具体来说,摘要认证进行了如下改进。

  • 永远不会以明文方式在网络上发送密码。
  • 可以防止恶意用户捕获并重放认证的握手过程。
  • 可以有选择地防止对报文内容的篡改。
  • 防范其他几种常见的攻击方式。

摘要认证并不是最安全的协议。摘要认证并不能满足安全 HTTP 事务的很多需求。对这些需求来说,使用传输层安全(Transport Layer Security,TLS)和 HTTPS 协议更为合适一些。

用摘要保护密码

摘要认证遵循的箴言是“绝不通过网络发送密码”。客户端不会发送密码,而是会发送一个“指纹”或密码的“摘要”,这是密码的不可逆扰码。客户端和服务器都知道这个密码,因此服务器可以验证所提供的摘要是否与密码相匹配。只拿到摘要的话,除了将所有的密码都拿来试试之外,没有其他方法可以找出摘要是来自哪个密码的!

单向摘要

摘要是“对信息主体的浓缩”。摘要是一种单向函数,主要用于将无限的输入值转换为有限的浓缩输出值。常见的摘要函数 MD5,会将任意长度的字节序列转换为一个 128 位的摘要。

对这些摘要来说,最重要的是如果不知道密码的话,要想正确地猜出发送给服务器的摘要将是非常困难的。同样,如果有摘要,想要判断出它是由无数输入值中的哪一个产生的,也是非常困难的。

MD5 输出的 128 位的摘要通常会被写成 32 个十六进制的字符,每个字符表示 4 位。

有时也将摘要函数称为加密的校验和、单向散列函数或指纹函数。

用随机数防止重放攻击

仅仅隐藏密码并不能避免危险,因为即便不知道密码,别有用心的人也可以截获摘要,并一遍遍地重放给服务器。摘要和密码一样好用。

为防止此类重放攻击的发生,服务器可以向客户端发送一个称为随机数(nonce)的特殊令牌,这个数会经常发生变化(可能是每毫秒,或者是每次认证都变化)。客户端在计算摘要之前要先将这个随机数令牌附加到密码上去。

随机数这个词表示“本次”或“临时的”。在计算机安全的概念中,随机数捕获了一个特定的时间点,将其加入到安全计算之中。

在密码中加入随机数就会使摘要随着随机数的每一次变化而变化。记录下的密码摘要只对特定的随机值有效,而没有密码的话,攻击者就无法计算出正确的摘要,这样就可以防止重放攻击的发生。

摘要认证要求使用随机数,因为这个小小的重放弱点会使未随机化的摘要认证变得和基本认证一样脆弱。随机数是在 WWW-Authenticate 质询中从服务器传送给客户端的。

摘要认证的握手机制

握手机制

简单摘要认证报文

摘要认证报文

参数概览

  • WWW-Authentication:用来定义使用何种方式(Basic、Digest、Bearer等)去进行认证以获取受保护的资源
  • realm:表示Web服务器中受保护文档的安全域(比如公司财务信息域和公司员工信息域),用来指示需要哪个域的用户名和密码
  • qop:保护质量,包含auth(默认的)和auth-int(增加了报文完整性检测)两种策略,(可以为空,但是)不推荐为空值
  • nonce:服务端向客户端发送质询时附带的一个随机数,这个数会经常发生变化。客户端计算密码摘要时将其附加上去,使得多次生成同一用户的密码摘要各不相同,用来防止重放攻击
  • nc:nonce计数器,是一个16进制的数值,表示同一nonce下客户端发送出请求的数量。例如,在响应的第一个请求中,客户端将发送“nc=00000001”。这个指示值的目的是让服务器保持这个计数器的一个副本,以便检测重复的请求
  • cnonce:客户端随机数,这是一个不透明的字符串值,由客户端提供,并且客户端和服务器都会使用,以避免用明文文本。这使得双方都可以查验对方的身份,并对消息的完整性提供一些保护
  • response:这是由用户代理软件计算出的一个字符串,以证明用户知道口令
  • Authorization-Info:用于返回一些与授权会话相关的附加信息
  • nextnonce:下一个服务端随机数,使客户端可以预先发送正确的摘要
  • rspauth:响应摘要,用于客户端对服务端进行认证
  • stale:当密码摘要使用的随机数过期时,服务器可以返回一个附带有新随机数的401响应,并指定stale=true,表示服务器在告知客户端用新的随机数来重试,而不再要求用户重新输入用户名和密码了

摘要的计算

摘要认证的核心就是对公共信息、保密信息和有时限的随机值这个组合的单向摘要。

摘要是根据以下三个组件计算出来的:

  • 由单向散列函数 H(d) 和摘要 KD(s, d) 组成的一对函数,其中 s表示密码,d表示数据。
  • 一个包含了安全信息的数据块,称为 A1,它包含有用户名、密码、保护域和随机数等内容。A1 只涉及安全信息,与底层报文自身无关 。
  • 一个包含了请求报文中非保密属性的数据块,称为 A2,表示的是与报文自身有关的信息,比如 URL、请求方法和报文实体的主体部分。A2 有助于防止方法、资源或报文被篡改。

H 和 KD 处理两块数据 A1 和 A2,产生摘要。

这里说的 KD(s,d) 函数,里面的 s 和 d 并没有什么实际意义,书上说 s 表示密码,d 表示数据,但是在实际的计算过程中,根本一点关系没有。。。实际上 H 和 KD 都是摘要函数,一般来说都是MD5。

RFC 2069 摘要计算方式

H(A1) = MD5(A1) = MD5(username:realm:password)

H(A2) = MD5(A2) = MD5(method:uri)

Response = KD(H(A1):<nonce>:H(A2)) = MD5(MD5(A1):<nonce>:MD5(A2))

RFC 2617 摘要计算方式

H(A1) = MD5(A1) = MD5(username:realm:password)

如果 qop 的值为 auth 或未指定,那么:

H(A2) = MD5(A2) = MD5(method:uri)

如果 qop 的值为 auth-int,那么:

H(A2) = MD5(A2) = MD5(method:uri:MD5(entityBody))

如果 qop 的值为 auth 或 auth-int,那么:

Response = MD5(MD5(A1):<nonce>:<nc>:<cnonce>:<qop>:MD5(A2))

如果 qop 的值为未指定,那么:

Response = KD(H(A1):<nonce>:H(A2)) = MD5(MD5(A1):<nonce>:MD5(A2))

预授权

服务端预先告知客户端下一个随机数是多少,使得客户端可以直接生成正确的Authorization首部,避免了多次“请求/质询”。常用的有一下三种方式:

  • 服务器预先在Authorization-Info成功首部中发送下一个随机数nextnonce。虽然这种机制加快了事务处理的速度,但是它也破坏了对同一台服务器的多次请求进行管道化的功能,可能会造成很大的损失。
  • 服务器允许在一小段时间内使用同一个随机数。在一定时间内使用同一个随机数或限制某个随机数的重用次数,当过期时,声明stale=true。虽然这确实降低了安全性,但是重用的随机数的生存周期是可控的,应该在安全和性能之间找到平衡。
  • 客户端和服务器使用同步的、可预测的随机数生成算法。

随机数选择

RFC 2617建议采用这个假想的随机数公式:

BASE64(timestamp MD5(timestamp ":" ETag ":" private-key))
复制代码

其中,timestamp是服务器产生随机数的时间或其他不重复的值,ETag是与所请求实体有关的HTTP ETag首部的值,private-key是只有服务器知道的私钥。

应该考虑的实际问题

多重质询

服务器可以对某个资源发起多重质询。比如,如果服务器不了解客户端的能力,就可以既提供基本认证质询,又提供摘要认证质询。客户端面对多重质询时,必须以它所支持的最强的质询机制来应答。

差错处理

在摘要认证中,如果某个指令或其值使用不当,或者缺少某个必要指令,就应该使用响应 400 Bad Request。

如果请求的摘要不匹配,就应该记录一次登录失败。某客户端连续多次失败可能说明有攻击者正在猜测密码。

认证服务器一定要确保 URI 指令指定的资源与请求行中指定的资源相同。如果不同,服务器就应该返回 400 Bad Request 错误。(这可能是一种攻击的迹象,因此服务器设计者可能会考虑将此类错误记录下来。)这个字段包含的内容与请求 URL 中的内容是重复的,用来应对中间代理可能对客户端请求进行的修改。这个经过修改(但估计语义是等价的)的请求计算后得到的摘要可能会与客户端计算出的摘要有所不同。

保护空间

域值,与被访问服务器的标准根 URL 结合在一起,定义了保护空间。

通过域可以将服务器上的受保护资源划分为一组保护空间,每个空间都有自己的认证机制和 / 或授权数据库。域值是一个字符串,通常由原始服务器分配,可能会有认证方案特有的附加语义。注意,可能会有多个授权方案相同,而域不同的质询。

保护空间确定了可以自动应用证书的区域。如果前面的某条请求已被授权,在一段时间内,该保护空间中所有其他请求都可以重用同一个证书,时间的长短由认证方案、参数和 / 或用户喜好来决定。除非认证方案进行了其他定义,否则单个保护空间是不能扩展到其服务器范围之外的。

对保护空间的具体计算取决于认证机制。

  • 在基本认证中,客户端会假定请求 URI 中或其下的所有路径都与当前的质询处于同一个保护空间内。客户端可以预先提交对此空间中资源的认证,无需等待来自服务器的另一条质询。
  • 在摘要认证中,质询的 WWW-Authenticate:domain 字段对保护空间作了更精确的定义。domain 字段是一个用引号括起来的、中间由空格分隔的 URI 列表。通常认为,domain 列表中的所有 URI 和逻辑上处于这些前缀之下的所有 URI,都位于同一个保护空间中。如果没有 domain 字段,或者此字段为空,质询服务器上的所有 URI 就都在保护空间内。

重写URI

代理可以通过改变 URI 语法,而不改变所描述的实际资源的方式来重写 URI。比如:

  • 可以对主机名进行标准化,或用 IP 地址来取代;
  • 可以用“%”转义形式来取代嵌入的字符;
  • 如果某类型的一些附加属性不会影响从特定原始服务器上获取资源,就可以将其附加或插入到 URI 中。

代理可修改 URI,而且摘要认证会检查 URI 值的完整性,所以如果进行了任意一种修改,摘要认证就会被破坏。

缓存

共享的缓存收到包含 Authorization 首部的请求和转接那条请求产生的响应时,除非响应中提供了下列两种 Cache-Control 指令之一,否则一定不能将那条响应作为对任何其他请求的应答使用。

  • 如果原始响应中包含有 Cache-Control 指令 must-revalidate,缓存可以在应答后继请求时使用那条响应的实体部分。但它首先要用新请求的请求首部,与原始服务器再次进行验证,这样原始服务器就可以对新请求进行认证了。
  • 如果原始响应中包含有 Cache-Control 指令 public,在对任意后继请求的应答中都可以返回响应的实体部分。

安全性考虑

  • 首部篡改

  • 重放攻击

  • 多重认证机制

  • 词典攻击

  • 恶意代理攻击或中间人攻击

  • 选择明文攻击

十四、安全HTTP

使用 HTTPS 时,所有的 HTTP 请求和响应数据在发送到网络之前,都要进行加密。HTTPS 在 HTTP 下面提供了一个传输级的密码安全层,可以使用 SSL,也可以使用其后继者 TSL。

HTTPS流程示意图

建立安全传输

在 HTTPS 中,客户端首先打开一条到 Web 服务器端口 443 的连接。一旦建立了 TCP 连接,客户端和服务器就会初始化 SSL 层,对加密参数进行沟通,并交换密钥。握手完成之后,SSL 初始化就完成了,客户端就可以将请求报文发送给安全层了。在将这些报文发送给 TCP 之前,要先对其进行加密。

HTTPS事务

SSL握手

在发送已加密的 HTTP 报文之前,客户端和服务器要进行一次 SSL 握手,在这个握手过程中,它们要完成以下工作:

  • 交换协议版本号;
  • 选择一个两端都了解的密码;
  • 对两端的身份进行认证;
  • 生成临时的会话密钥,以便加密信道

简化的SSL握手

服务器证书

SSL 支持双向认证,将服务器证书承载回客户端,再将客户端的证书回送给服务器。而现在,浏览时并不经常使用客户端证书。另一方面,安全 HTTPS 事务总是要求使用服务器证书的。服务器证书是一个显示了组织的名称、地址、服务器 DNS 域名以及其他信息的 X.509 v3 派生证书。

服务器证书

站点证书的有效性

SSL 自身不要求用户检查 Web 服务器证书,但大部分现代浏览器都会对证书进行简单的完整性检查,并为用户提供进行进一步彻查的手段。网景公司提出的一种 Web 服务器证书有效性算法是大部分浏览器有效性验证技术的基础。验证步骤如下所述:

  • 日期检测
  • 签名颁发者可信度检测
  • 签名检测
  • 站点身份检测

十五、实体和编码

Content-Length

Content-Length 首部指示出报文中实体主体的字节大小。这个大小是包含了所有内容编码的,比如,对文本文件进行了 gzip 压缩的话,Content-Length 首部就是压缩后的大小,而不是原始大小。

除非使用了分块编码,否则 Content-Length 首部就是带有实体主体的报文必须使用的。使用 Content-Length 首部是为了能够检测出服务器崩溃而导致的报文截尾,并对共享持久连接的多个报文进行正确分段。

实体摘要

服务器使用 Content-MD5 首部发送对实体主体运行 MD5 算法的结果。只有产生响应的原始服务器可以计算并发送 Content-MD5 首部。中间代理和缓存不应当修改或添加这个首部,否则就会与验证端到端完整性的这个最终目的相冲突。Content-MD5 首部是在对内容做了内容编码之后,还没有做任何传输编码之前,计算出来的。为了验证报文的完整性,客户端必须先进行传输编码的解码,然后计算所得到的未进行传输编码的实体主体的 MD5。

内容编码

内容编码的过程

  • 网站服务器生成原始响应报文,其中有原始的 Content-Type 和 Content- Length 首部。
  • 内容编码服务器(也可能就是原始的服务器或下行的代理)创建编码后的报文。编码后的报文有同样的 Content-Type 但 Content-Length 可能不同(比如主体被压缩了)。内容编码服务器在编码后的报文中增加 Content-Encoding 首部,这样接收的应用程序就可以进行解码了。
  • 接收程序得到编码后的报文,进行解码,获得原始报文。

内容编码的类型

Content-Encoding描述
gzip表明实体采用 GNU zip 编码
compress表明实体采用 Unix 的文件压缩程序
deflate表明实体是用 zlib 的格式压缩的
identity默认,没有进行编码

Accept-Encoding

Accept-Encoding 表明客户端支持的内容编码有哪些。Accept-Encoding 字段包含用逗号分隔的支持编码的列表。

Accept-Encoding: compress, gzip
Accept-Encoding:
Accept-Encoding: *
Accept-Encoding: compress;q=0.5, gzip;q=1.0
Accept-Encoding: gzip;q=1.0, identity; q=0.5, *;q=0
复制代码

客户端可以给每种编码附带 Q(质量)值参数来说明编码的优先级。Q 值的范围从 0.0 到 1.0,0.0 说明客户端不想接受所说明的编码,1.0 则表明最希望使用的编码。

传输编码与分块编码

  • Transfer-Encoding:告知接收方为了可靠地传输报文,已经对其进行了何种编码。
  • TE:用在请求首部中,告知服务器可以使用哪些传输编码扩展。

分块编码

分块编码把报文分割为若干个大小已知的块。块之间是紧挨着发送的,这样就不需要在发送之前知道整个报文的大小了。要注意的是,分块编码是一种传输编码,因此是报文的属性,而不是主体的属性。

分块编码

分块报文的拖挂

如果客户端的 TE 首部中说明它可以接受拖挂的话,就可以在分块的报文最后加上拖挂。产生原始响应的服务器也可以在分块的报文最后加上拖挂。拖挂的内容是可选的元数据,客户端不一定需要理解和使用(客户端可以忽略并丢弃拖挂中的内容)。

拖挂中可以包含附带的首部字段,它们的值在报文开始的时候可能是无法确定的(例如,必须要先生成主体的内容)。Content-MD5 首部就是一个可以在拖挂中发送的首部,因为在文档生成之前,很难算出它的 MD5。

报文首部中包含一个 Trailer 首部,列出了跟在分块报文之后的首部列表。在 Trailer 首部中列出的首部就紧接在最后一个分块之后。

除了 Transfer-Encoding、Trailer 以及 Content-Length 首部之外,其他 HTTP 首部都可以作为拖挂发送。

十六、国际化

十七、内容协商与转码

十八、Web主机托管

十九、发布系统

二十、重定向与负载均衡

二十一、日志记录

文章分类
代码人生
文章标签