HTTP请求处理
10.1 请求处理模型
10.1.1 请求处理 概述
从 第一卷的 分析中,我们 知道 Apache中对于请求的处理实际只有两个函数: ap_read_request() 和 ap_process_request() ,一旦通过 ap_read_request() 读取了 HTTP 连接 上的某个请求之后,Apache 将调用函数 ap_process_request 对其进行 处理。在整个 Apache 的核心内部,大部分读者最关心的就是 Apache 中是如何处理客户端的请求的。由于 HTTP 是基于 TCP 的应用层的协议,而 TCP 则是面向连接的传输层的协议,因此通信的双方在传输数据之前必须建立起网络连接。因此 , 通信双发将首先建立TCP 连接,然后接受 HTTP 数据,最后再关闭连接。
Apache中的 HTTP 请求是与前面所介绍的挂钩 紧紧 关联在一起的。从大的方面来 看,Apache 对 HTTP 的请求可以分为连接、处理和断开连接三个阶段。 从小的方面而言,每个阶段又可以分为更多的子阶段。比如对 HTTP 的请求,我们可以进一步划分为客户身份验证、客 户权限认证、请求校验、 URL 重定向等阶段,每一个阶段调用相应的函数进行处理。在 Apache 中,这些子阶段可以用术语 “ 挂钩(HOOK) ” 来描述。Apache 中对请求的处理过程实质上就是依次调用一系列挂钩的过程,不过由于 HTTP 请求类型的不同,它们所对应的挂钩数目和类型也不尽相同。对于典型的 HTTP 请求,有一个默认的挂钩调用顺序,你可以按照这个默认的顺序进行调用,也可以不遵 遁 这个顺序, 而 根据自己的情况调整调用顺 序。整个HTTP 的请求可以用图 10-1 来描述:
图10-1 HTTP请求模型
在 图10-1 中,存在 6 种不同的挂钩,对于请求a ,只需要调用 Hook2 、 Hook1 ;对于请求 b ,则需要调用 Hook1 、 Hook6 、 Hook5 ;请求 c 需要依次调用 Hook1 、 Hook4 、 Hook3 、 Hook6 四个挂钩。
在Apache 中,挂钩总是和挂钩函数联系在一起的。挂钩是用来表示在处理 HTTP 请求中一组类似的操作,与之对应,挂钩函数就是操作函数。不过即使挂钩相同,对应的挂钩函数也未必相 同 ,如图10-2 所示 。举个简单的例子,配置文件模块和虚拟主机模块都需要身份验证挂钩来确认 访问者能否访问相应的资源,但两个模块的验证方法则差别很大。因此 , 一个挂钩同时是和一组挂钩函数联系在一起的。 在 请求处理过程中,调用挂钩的时候实际上就是调用挂钩函数组 中的挂钩函数 。调用过程可以用 图 10-2 所示 。Apache 对指定挂钩的挂钩函数调用有两种 方式 ,一种就是将挂钩函数组中的每 个函数都调用一次,比如图 10-2 中的挂钩4 ;另一种就是从前往后调用,一旦找到合适的就停止继续调用,图 10-2 中 的挂钩 1、 挂钩 2、 挂钩 3就是这种情况。
通过挂钩机制,你可以自行修 改服务器的行为,比如修改挂钩函数或增加挂钩函数,甚至增加挂钩。不过增加挂钩只是Apache 2.0才提供的功能。
图10-2 请求挂钩和挂钩函数
由于所有的挂钩函数最终都是定义在模块之中,各个模块能够支持一定数目和一定类型的挂钩,因此,如果我们将挂钩、模块和整个请求进行整体分析,则如 图10-3所示:
从 图10-3 中 我们可以看到整个请求的处理流 程 。
所 有的模块A 、 B 、 C 、 D 、 E 组成模块链表,而每个模块分别支持不同的挂钩。某一个 请求与 4 种类型的挂钩HOOK type1 、 HOOK type2 、 HOOK type3及 HOOK type4 关联,当请求执行的时候,各个模块 中的对应的挂钩函数将依次被调用,调用的顺序很简单,先 调用 挂钩类型,后 调用 模块类型。因此 , 从 图 10-3中可以看出,当一个请求处理完毕, 7 个函数将被调用。
10.1.2 处理阶段划分
当HTTP 头读取完毕后,服务器的状态被修改为 “ busy_write ” ,与此同时,服务器将对该请 求进行响应处理。整个请求阶段可以被划分为多个阶段,各个阶段可以用图10-4 进行描述。 Apache 2.0版本中的请求处理流程与早期的 Apache 1.3版本的处理流程几乎相同。唯一的区别 在于 ,在 过滤器的概念 中 ,Apache 2.0以上的版本中尽管只有一个内容处理器被使用,但是可能由多个模块同时负责生成响应。
图10-4 请求处理中的各个阶段
整 个请求处理可以被划分为多个子阶段,这些子阶段分别是:
n Url 预 处理阶段
通常情况下 , 浏览器会自动地将请求的地址栏中的一些特殊字符 ( 比如 将 空格转换为% ) 和十六进制 进行 组合,比如空格就是 “ %20 ” ,因此对于服务器而言,其需要 将 “ %xx ” 格式的字符串重新还原为原来的 字符串。同时 , Apache有必要剔除 URI 中一些冗余的字符,比如 “ ./xxx ”、“ xxx/../ ” 或 “ .//xxx ” 等。在进行进一步的处理之前, 有必要对这些URI 进行规则处理。具体的处理由 ap_unescape_url () 和 ap_getparents() 完成,函数的细节我们在后面的部分将深入了解。
n 请求配置信息查找
在前面我们曾经描述过Apache 中的配置信息。配置信息可以分为多个种类:针对整个服务器的全局配置信息、针对某个虚拟主机的配置信 息 、 针对某个连接的配置信息 , 以及针对某个请求的配置信息。 在 请求的处理过程中 , 很多的信息需要依赖这些配置信息去处理,比如授权信息。为此在进行继续处理之前,服务器必须能够得到所有的与该请 求关联的配置信息。
Apache中与特定请求关联的配置信息总是用 <Location>…</Location> 配置段进行处理,因此 , 对于请求的URI , Apache 需要查找当前的配置信息中是否有针对该 URI 的配置。配置查找通过 location_walk 进行。 这项工作必须在模块转换 URI 之前进行,因为它可能会影响 Apache 对 URI 的转换。
n URL重写( translate_name )
一些模块 会对 URL 进行重写 , 通过对 URL 重写,可以在请求文件的保存路径发生变化的时候避免对外提供的 URL 被修改。比如 mod_alias 会将一个符合条件的 URL 用另一个别名 URL 进行替换,而 mod_rewrite 则会对给定的 URL 进行更加复杂的替换和修改。
在这个阶段,如果某个模块返回DECLINE ,那么 Apache 将会返回一个 500 的错误码给浏览器,这意味着通知浏览器 “ 当前请求的URI 无法进行转换 ” ,同时该错误被记入日志。
n 再次获取与该请求相关的配置信息
转换前的URI 可能与转换后的 URI 不 相 同,因此一旦URL 经过转换, Apache 将有必要再调用 location_walk 获取转换后的 URI 所对应的配置信息。同时 , 核心将调用挂钩map_to_storage 的处理句柄 core_map_to_storage() 。 一旦 URI 转换之后,该 URI 所对应的 Location 、 目录及文件都 将 确定下来,此时 , 有必要获取每一部分的配置信息。 a p_directory_walk()和 ap_file_walk() 分别用来实现遍历指定的目录和文件的配置,这些配置与获取的 location 配置信息进行合并,从而得到最终的完整的针对当前请求的 URI 的配置信息。
n 资源映射 ( map_to_storag )
该阶段的目的仅仅有一个,那就是让模块确定特定的资源是否可以在磁盘上找到。使用这个挂钩的原因 是, 如果数据要在磁盘上找到,而不 是由模块生成 的 ,那么服务器就必须执行更加 严格的安全检查。在这种情况下,服务器所做的检查越严格,就越有可能避免错误的发生。如果数据位于磁盘上,那么服务器就必须确保可以提供请求的文件。这个 听起来很简单, 其实并非如此 。一些系统支持符号连接,因 此 , 如果在路径中有符号连接,那 么服务器就可能 无法 理解符号。这种情况必须能够 在map_to_storage 中处理。
如果请求的内容不是磁盘上的文件,而是动态生成的,那么这种检查就没有必要了。
n 头信息解析 ( header_parser )
该阶段的主要目的在于检验HTTP 请求的报文头 ,并根据请求头信息做一些必要的处理 。这是一个一般性目的的挂 钩, 在 配置完全有效且进一步特化之 前启用。 在这一阶段,你可以根 据请求头中的信息进行相关的设置,比如 mod_setenvif 就会根据特定的请求头信息设置一些环境变量。
另外,如果你需要实现 Apache 中不支持的一些 HTTP 方法,比如 HTTP PATCH 那么你也可以在该挂钩阶段实现。
n 访问检查( access_checker )
access_checker挂钩被设计用于让 模块根据与模块的特定基础限制对资源 进行 访问。这很重要,因为它可以让系统管理员根据任意数量的特性阻止用户访问它们的站点。例如,mod_access 就可以让管理员根据客户的 IP 地址来限制访问,这就可以让相 同的 Web 服务器同时为公共的 Web 站点和私有的 Web 站点服务。通过 access_checker 模块做一些简单的 配置就可以减少相当 一部分 的无效的攻击。
n 用户检查( check_user_id )
该挂钩可以让模块认证用户的名称和密码。对于大多数的身份验证模块来讲,这意味着如果URI 有所要求,就要确保随请求发送口令信息,还要确保所提供的口令对确定用户有效。服务器会为这个挂钩运 行所有的经过注册的函数,直到有函数返回 DECLINED 之外的内容。大多数实现 check_user_id 的函数 模块都会使用下面的 4 个有效的返回值:
(1)、 OK 用户名和用户密码已经成功的通过认证 ;
(2)、 HTTP_UNAUTHORIED 没有找到用户或该用户提供的密码不正确 ;
(3)、 HTTP_INTERNAL_SERVER_ERROR 在认证这个用户的时候出现了 严重 的问题,服务器不能继续为这个 用户 请求服务器 ;
(4)、 DECLINED 这个模块不能支持用户认证 。
n 用户授权权限检查( auth_checker )
该模块用以确定通过认证的用户是否有权利查看请求的资源。这个挂钩要依赖由核心实现的require 指令。通过使用 require 指令,管理员就能 将 资源的访问权限制到一组已经配 置过的用户组,或者由服务器认可的用户。这个挂钩在处理返回代码的方式上与 其余 的挂钩有所不同,如果针对这个挂钩的所有的模块都返回DECLINED ,并且这个请求已经 启 用了认证,那么服务器就会使用 错误消息终止当前的请求。
access_checker、 check_user_id 和 auth_checker 三个挂钩属于安全管理的内容, 我们在安全管理章节会详细描述它们。
n 资源类型检查( type_checker )
该挂钩会为模块提供一个机会来确定或修改文档类型的特性。例如,mod_mime 会利用该挂钩来设置语言 、 编码及请求类型。在这个阶段, 你还可以修改 被 请求的资源所调用的处理器函 数。
n fixups
请求处理中调用的最后一个挂钩就是fixups 。这是一个一般性的挂钩,没有特定的执行意 义。该挂钩被设计用来让模块在向客户端发送响应之前确定响应头。这是在服务器开始生成数据之前,模块影响响应数据的最后机会。如果在服务器开始处理器阶段 之前能找到任何错误,那么模块都应该利用所有的机会来进行这样的工作。如果服务器开始为无效的请求生成响应,那么你就会在不能发送 请求的页面 上浪费时间和资源。
整个请求的概要流程可以用图10-5 描述:
在 ap_process_request中 ,各个阶段依次按序执行 完成,但是完整的HTTP 请求至少 还要 包括下面三个处理阶段:
n insert_filter
在该阶段,内容过滤器将被插入到请求中, 他们用于对处理器生成的需要返回给客户端的数据进行过滤处理。
n handler
该阶段 被 称为内容生成器,主要的任务就是生成客户端所请求的响应。
n logger
该 阶段用于记录请求中的对话日志 。
所 有的挂钩组合起来可以概括为 4 个不同的阶段:
n post_read_request阶段
该 阶段标志着从协议到请求处理的过 渡 ,在该挂钩中主要的任务就是 设置request_rec 中需要被设置的内容。虽然 request_rec 已经建立起 来,但还是有很多的内容尚未被建立。
n 从translate_name 到 fixups 阶段:
该 阶段通常 被 称为 “ 请求准备阶段 ” ,主要对请求做必要的准备
n 从insert_filter 到 handler 阶段
insert_filter和 handler 组合在一起构成了 Apache 中的内容生成器,该阶段属于内容生成阶段,生成的内容最终将返回给客户端。
n logger阶段
这 是最后的阶段,与请求处理本身的关系不是非常大,主要用于日志记录 。
在下面的部分 , 我们将描述请求处理中的URI 处理,配置数据处理。部分阶段的详细处理我们会在后面的章节中给出,还有一部分属于模块的内容 如 安全处理等 , 我们会 将其 放到第三卷中 进行 更深入的 讨论 。
、本文原始出处为Aapche中国, http://www.cn-apache.com