基于Apache源代码的Web应用服务器的体系结构分析 论文

更新时间:2024-06-25 07:45:01 阅读量: 综合文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

毕 业 设 计 论 文

专业班级: 计算机科学与技术·计本061 学生姓名: 唐宇松 指导教师: 朱克敌

密级:内部

基于Apache源代码的Web应用服

务器的体系结构分析

Analysing the architecture of the Web application server based on

Apache source code

系别名称: 信息工程系

专业班级: 计算机科学与技术·计本061 学生姓名: 唐宇松 学 号: 2006403121 指导教师: 朱克敌

沈阳工程学院毕业论文 摘 要

摘 要

如果说没有Apache就没有Intemet可能有些夸张,但至少可以说没有Apache,互联网就不会发展得这么快。根据互联网研究公司NetCraft的统计,多年来Apache一直稳居Web服务器市场的头把交椅,至今仍占据超过50%的市场份额。就整个互联网来说,Apache仍然是最重要的软件之一。

本论文通过分析Apache服务器体系结构中层次结构和主程序main.c文件源代码,了解Apache服务器运行过程及其运行原理。然后主要分析了Apache服务器中主要的三个重要运行模块及其代码:用于模块加载的三个主要模块,用于指令的指令处理模块,最后是挂钩处理的五个重要组成部分。通过分析它们,为了给未来希望编写第三方模块的人提供借鉴和相关参考。

关键词 Apache服务器,模块加载,指令处理模块,挂钩

- I -

沈阳工程学院毕业论文 Abstract

Abstract

If there is no Apache would not Intemet may be some exaggeration, but at least you can say there is no Apache, the Internet would not have developed so fast. According to Internet research firm NetCraft statistics, over the years has been one of Apache Web server market, the top spot, still holds more than 50% market share. For the whole Internet, Apache still is one of the most important software.

This dissertation analyzes the hierarchy of Apache server and the main program main.c source code file. Learn operating process and principle of the Apache server. Then the main analysis of the main Apache server to run the three major modules and the code: The three main modules of the load module, the command processing module of command, the five important part of the hook. By analyzing them, in order to the people who hope to write the third-party modules in the future provide reference and related reference.

Keywords Apache server, load module, command processing module, hook

- II -

沈阳工程学院毕业论文 目 录

目 录

摘 要 ............................................................................................................................ I Abstract ......................................................................................................................... II 第1章 概 述 ............................................................................................................ 1

1.1 课题研究的意义 ............................................................................................ 1 1.2 Apache功能 .................................................................................................. 1

1.2.1 Apache虚拟主机 ................................................................................ 1 1.2.2 持续连接 .............................................................................................. 1 1.2.3 缓存 ...................................................................................................... 2 1.2.4 访问控制和安全 .................................................................................. 2

第2章 课题研究相关技术 ........................................................................................ 3

2.1 HTTP协议及HTTP服务器的工作原理 ..................................................... 3

2.1.1 HTTP协议简介 ................................................................................... 3 2.1.2 HTTP服务器 ....................................................................................... 3 2.2 MVC三层体系结构以及各个层次之间的依赖关系 ................................. 4

2.2.1 MVC三层体系结构 ........................................................................... 4 2.2.2 MVC三层体系结构之间的依赖关系 ............................................... 4

第3章 Apache体系结构 .......................................................................................... 5

3.1 Apache目录 .................................................................................................. 5

3.2 Apache层次结构 .......................................................................................... 6

3.2.1 操作系统支持层 .................................................................................. 7 3.2.2 可移植运行库层 .................................................................................. 7 3.2.3 核心功能层 .......................................................................................... 7 3.2.4 可选功能层 .......................................................................................... 7 3.3 Apache核心功能层 ...................................................................................... 8

3.3.1 核心与可选模块的关系 ...................................................................... 8 3.3.2 核心组件 .............................................................................................. 8 3.4 Apache运行流程 .......................................................................................... 9

3.4.1 Apache启动过程 ................................................................................ 9 3.4.2 HTTP连接处理 ................................................................................... 9 3.4.3 请求报文读取 ...................................................................................... 9 3.4.4 请求处理 ............................................................................................ 10 3.4.5 内容生成 ............................................................................................ 11 3.5 主程序main ................................................................................................. 11

3.5.1 主程序概要 ........................................................................................ 11 3.5.2 主程序细节 ........................................................................................ 11

第4章 Apache模块化体系结构 ............................................................................ 20

4.1 Apache模块概述 ........................................................................................ 20

- III -

沈阳工程学院毕业论文 目 录

4.2 模块的加载 .................................................................................................. 20

4.2.1 静态模块加载 .................................................................................... 20 4.2.2 动态模块加载 .................................................................................... 23 4.2.3 模块卸载 ............................................................................................ 27 4.3 指令表 .......................................................................................................... 28 4.3.1 指令表概述 ........................................................................................ 28

4.3.2 指令处理函数 .................................................................................... 28 4.4 挂钩(HOOK) ............................................................................................... 29

4.4.1 声明挂钩 ............................................................................................ 29 4.4.2 挂钩数组声明 .................................................................................... 31 4.4.3 挂钩结构 ............................................................................................ 31 4.4.4 挂钩函数注册 .................................................................................... 32 4.4.5 使用挂钩 ............................................................................................ 32

总 结 .......................................................................................................................... 35 致 谢 .......................................................................................................................... 36 参考文献 ...................................................................................................................... 37

- IV -

沈阳工程学院毕业论文 第1章 概 述

第1章 概 述

1.1 课题研究的意义

众所皆知,Apache良好的模块化架构设计已经成为经典的服务器架构——易于扩展、易于维护。几乎每一个程序员都可以从Apache的架构中吸取到丰富的营养。虽然Apache的代码非常优美,架构层次非常清晰,但是在缺乏资料的情况下,开发人员想在短时间内读懂Apache架构和内部细节并不是一件容易的事情。在陷入迷茫和一头雾水之后,他们可能就会选择放弃,从而与Apache的优美架构失之交臂。

Apache是世界使用排名第一的Web服务器软件。它可以运行在几乎所有广泛使用的计算机上。 所以越来越多的人使用Apache服务器。因此,对于Apache使用者来说,理解和掌握Apache运行原理及运行体系是很重要的。

本论文通过分析Apache Web 服务器源代码理解和掌握Apache Web服务器源代码体系结构,使读者能够更加方便快捷的阅读掌握Apache Web服务器源代码体系结构,理解Apache Web服务器的机制体系和运行原理。

1.2 Apache功能

1.2.1 Apache虚拟主机

虚拟主机是指一个机器上运行多个Web站点的机制。虚拟主机的实现包括以下三种方式。

(1) Web服务器中配备多个IP地址,并且每一个逻辑Web服务器使用一个IP地址。

(2) Web服务器只有一个IP地址,不同的Web服务器使用不同的端口进行侦听。

(3) Web服务器只有一个IP地址,同多个域名被映射到该IP地址上。

Apache中支持上面三个方式的虚拟主机,而且通过mod_vhost_alias模块,可以使得类似的虚拟主机配置起来非常容易,减轻了管理者的负担。

1.2.2 持续连接

所谓持续连接,就是某个连接再打开后不立即关闭,而是继续使用,后续的数据传输都基于该连接。因此对于某一个Web界面来说,不管其中包含了视频还是图片等,都是基于该连接传送的。为了使用持续连接,客户端和服务器之间会使用“Connection:keep-alive”请求域;默认情况下连接就是持续连接,除非进行了特殊的指定。如果客户端和服务器端的某一方不愿意使用持续连接,它只需要设置“Connection:close”请求域,另一方面一旦接收到“Connection:close”,

- 1 -

沈阳工程学院毕业论文 第1章 概 述

就会在当前请求处理完毕后关闭当前请求。Apache中提供了配置指令,允许限制同一连接上的处理请求的数目,以及处理超时的时间,一旦超过该处理时间,所有的连接都将关闭。

1.2.3 缓存

HTTP通常位于分布式信息系统中,在这些系统中,可以通过采用缓存应答的方式改善系统的性能。HTTP/1.1协议中包含了大量的元素,尽可能地使缓存产生更好的效果。通过缓存,可以加快客户端的响应速度。

HTTP/1.1协议中缓存的设计目标就是在很多情况下见地发送请求的必要性,以及在很多其他情况下降低发送完整应答的必要性。前者就使你少了很多操作所需要的网络回合的数目——它使用过期机制来实现这一目的。后者减少了网络带宽需求——通过验证机制达到这一目的。

1.2.4 访问控制和安全

对于Web站点上存在的一些私有文件,我们必须确保这些文件在绝对的受控范围之内,通过认证、授权和访问控制等一系列的安全措施,可以确保受控资料的安全性。

认证(Authentication)、授权(Authorization)及账户确认(Accounting)三者合起来称为AAA模块。

- 2 -

沈阳工程学院毕业论文 第2章 课题研究相关技术

第2章 课题研究相关技术

Apache的所有源代码都是使用C语言开发的。因此,如果你不熟悉C语言,则最好不要阅读本论文。如果你熟悉C++,则会更好。Apache中的很多设计都借鉴了C++的一些思想,了解C++,会让你更容易理解这些设计。

本论文的Apache代码基于2.2.8版本,读者在阅读之前请登陆Apache 下载网址http://httpd.apache.org下载一份代码。对于阅读和分析代码,笔者推荐使用的工具是SourceInsight。

理解代码的最好方式就是跟踪和调试。因此勤于调试能有助于你对Apache源代码和本书内容的理解。

2.1 HTTP协议及HTTP服务器的工作原理

2.1.1 HTTP协议简介

HTTP是一个属于应用层的面向对象的协议,由于其简捷、快速的方式,适用于分布式超媒体信息系统。它于1990年提出,经过几年的使用与发展,得到不断地完善和扩展。目前在WWW中使用的是HTTP/1.0的第六版,HTTP/1.1的规范化工作正在进行之中,而且HTTP-NG(Next Generation of HTTP)的建议已经提出。

HTTP协议定义服务器端和客户端之间文件传输的沟通方式。目前HTTP协议的版本是Http1.1。RFC 2616描述了HTTP协议的具体信息。

这个协议已经成为浏览器和Web站点之间的标准。

当访问者点击一个超链接的时候,将会给浏览器提交一个URL地址。通过这个URL地址,浏览器便知道去链接那个网站并去取得具体的页面文件(也可能是一张图片,一个pdf文件)。

HTTP工作的基础就是,连接一个服务器并开始传输文件到浏览器。 最基本的过程是:

(1)客户端连接一个主机 (2)服务器接收连接

(3)客户端请求一个文件 (4)服务器发送一个应答

2.1.2 HTTP服务器

整个Web可以分为俩个重要的组成部分:客户端和服务器端。当客户端需要请求特定的URI时,它将与服务器建立连接,并通过HTTP协议发送请求至Web服务器。Web服务器接收到客户端的请求之后将生成响应内容,同时将该响应内容通过连接返回给客户端。

- 3 -

沈阳工程学院毕业论文 第2章 课题研究相关技术

正常情况下,一个HTTP服务器会等待浏览器发送的请求,并根据HTTP协议进行响应。客户端总是请求某个特定的文档。服务器将检查该请求,同时将客户端请求的文档映射到服务器本地的文件系统中,或者将该请求转发给一个特别的应用程序,并由该应用程序负责对请求进行处理,生成相应内容。一旦处理完毕,服务器将把处理结果返回个客户端。

2.2 MVC三层体系结构以及各个层次之间的依赖关系

2.2.1 MVC三层体系结构

MVC是设计模式应用的一个经典案例。其中的M指数据模型datamodel,V指视图View,C指控制器Controller。 MVC应用程序总是由三个部分组成。Event(事件)导致Controller改变Model或View,或者同时改变两者。只要Controller 改变了Models的数据或者属性,所有依赖的View都会自动更新。类似的,只要Controller改变了View,View会从潜在的Model中获取数据来刷新自己。

2.2.2 MVC三层体系结构之间的依赖关系

MVC模式是一个复杂的架构模式,其实现也显得非常复杂,但多种设计模式结合在一起,使MVC模式的实现变得相对简单易行。Views可以看作一棵树,显然可以用Composite Pattern来实现。Views和Models之间的关系可用Observer Pattern体现。Controller控制Views的显示,可以用Strategy Pattern实现。

- 4 -

沈阳工程学院毕业论文 第3章 Apache体系结构

第3章 Apache体系结构

本章从整体上介绍了Apache的三层体系结构,并着重介绍了核心功能层,分析了核心功能层中各个重要的组件及它们之间的相互依赖关系。另外本章还讲述了Apache的运行流程及主函数的实现细节。

Apache服务器是一个非常复杂的服务器,一方面,它是为处理Web应用而存在的,因此,它不需要实现所有与Web服务器相关的技术,比如HTTP协议、授权处理等;另一方面,它又是一个服务器,因此,它必须考虑到服务器设计的各种关键技术,比如如何承载高负载,如何方便地进行功能拓展等。因此对于Apache而言,如何设计它的体系结构成为一个头等的大事请。幸运的是,Apache采用了模块化的体系结构,它的大部分功能都被分割为相互独立的模块。这样,一方面,通过增加或删除模块就可以拓展和修改Apache提供的功能;另一方面,对于Apache功能的理解也变得非常容易。

3.1 Apache目录

在了解整个Apache体系结构之前,我们必须先了解Apache的源代码是如何组成的。Apache2.0版本的目录结构如图3-1所示。

support build test include docs srclib apr-util apr pcre os modules ssl proxy ··· Httpd-2.0.45 server mpm 图3-1 Apache2.0的源代码组织结构

- 5 -

沈阳工程学院毕业论文 第3章 Apache体系结构

整个Apache核心功能包括请求处理、协议处理等功能,实现文件全部包含在server目录中,server目录中又包含一个称之为mpm的目录。在2.0以上的版本中,Apache推出了多种进程的一种并发模型,包括线程池,预创建等,这些都被称之为多进程处理模块(MPM)。它们之间既互相独立,又能互相替换。这些模块统统保存在mpm目录中。每一种并发模型对应一个.c文件。

另外一个重要的目录就是modules,顾名思义,其中保存着Apache中的所有模块。每一个模块占有一个目录,比如AAA认证模块目录为aaa。代理模块名称为proxy。

include目录包含了Apache中必须的头文件。

srclib目录中包含了Apache开发和运行所需要的基础库。 os目录中包含了各个操作系统中所特有的内容。

docs目录中包含了Apache提供的文档

test目录中包含了很多APR库使用的测试函数。

3.2 Apache层次结构

整个Apache可以被划分为四个大的层次:可以只运行库层(APR)、Apache核心功能层(Apache CPRE)、Apache可选功能层(Apache Optional Module)及Apache第三方支持库。图3-2描述了Apache中的层次关系。

OpenSSLmod_SSLhttp_core共享内存LDAP5······Apache模块中使用的第三方支持库PerlPHPmod_rewritemod_phpmod_spelingmod_ladpmod_perl4······Apache可选功能层Apache核心核心模块http_protocolhttp_requestmod_MPMmod_coremod_httpmod_so3···Apache核心功能层Libapr库2文网件···络可移植运行库层或者操作系统适配层libldaplibldaplibpcre1操作系统(Operating System)IOIO平台功能层

图3-2 Apache的体系结构

- 6 -

沈阳工程学院毕业论文 第3章 Apache体系结构

3.2.1 操作系统支持层

Apache归根结底是建立在操作系统的普通的营运程序上的,因此,很多时候必须使用操作系统本身提供的底层功能。离开了操作系统,Apache就失去了存在的根基。

3.2.2 可移植运行库层

早期的Apache设计者并没有把跨平台作为Apache的设计目标之一,但是从Apache2.0开始,Apache就专门封装不同操作系统API的任务独立出来形成一个新的项目APR,全称为Apache可移植运行库。APR的任务是屏蔽底层的操作系统API细节,对于所有的操作系统,提供一个完全相同的函数接口。这样,Apache开发者就不必顾虑操作系统细节,而是只要开发商层功能即可。

3.2.3 核心功能层

对于Apache而言,其最核心的功能则位于第三层,我们称之为核心功能层。这层主要包括两大部分:Apache核心程序和Apache核心模块。

Apache的核心程序主要用于实现Apache作为HTTP服务器的基本功能,这些基本功能包括:

(1)启动和停止Apache

(2)处理配置文件(config.c) (3)接受和处理HTTP连接

(4)读取HTTP请求并对该请求进行处理 (5)处理HTTP协议

核心层另一个重要组成部分就是核心模块。Apache中大部分模块都是可选择的,这意味着对于Apache而言是可有可无的。这些模块的缺失至多影响Apache功能完整性,并不影响运行。但是有两个模块是必须的,即mod_core和mod_so。前者负责处理文件中大部分配置指令,并根据这些指令运行Apache,而后者则负责动态加载其余的模块,缺少了该模块,其余的模块就无法使用。这两个模块都必须静态编译。

3.2.4 可选功能层

可选功能层通常指Apache模块。如前所述,除了mod_core和mod_so模块外,其余的模块都属于可选模块,在必要的时候可以被mod_so模块夹在到Apache中,二在不需要的时候也可以从Apache中卸载。

- 7 -

沈阳工程学院毕业论文 第3章 Apache体系结构

3.3 Apache核心功能层

3.3.1 核心与可选模块的关系

Apache核心与Apache模块之间的关系是调用和被调用的关系,所有的模块都是直接与核心进行交互。

Apache核心模块和Apache可选模块的接口完全相同,并且它们队员爬出核心而言也是完全相同的。在处理过程中,核心并不会去关心谁是核心模块,谁是可选模块,它们都是一视同仁地被调用。核心模块和非核心模块的唯一区别就是加载的时间不同。核心模块通常必须静态加载,而非核心模块则既可以静态加载,也可以动态加载。因此核心模块是Apache中必不可少的一部分。

3.3.2 核心组件

对于Apache而言,Apache核心组件包括下面几大部分。 (1)配置文件组件(HTTP_CONFIG) (2)进程并发处理组件(MPM)

(3)连接处理组件(HTTP_CNNECTION)

(4)HTTP协议处理组件(HTTP_PROTOCOL) (5)HTTP请求处理组件(HTTP_REQUEST)

(6)HTTP核心组件(HTTP_CORE) (7)核心模块组件(MOD_CORE)

Apache核心组件之间的相互关系可以用图2-3进行描述。

MAIN(main.c)MPMHTTP_CONFIG(config.c)MOD_CORE(core.c)HTTP_CORE(http_core.c)HTTP_PROTOCOL(http_protocol.c)MOD_CORE(core.c)HTTPClientHTTP_CONNECTION(connection.c)HTTP_REQUEST(http_request.c)

图3-3 Apache各组件之间的交互关系

- 8 -

沈阳工程学院毕业论文 第3章 Apache体系结构

3.4 Apache运行流程

Apache的运行流程可以细分为三个大的阶段。 (1)Apache启动过程。

(2)接受客户端连接,并处理该连接。

(3)从连接中读取请求数据,处理客户端的第一次请求。

3.4.1 Apache启动过程

Apache的启动包括两个阶段:高权限启动阶段和低权限运行阶段,通常称之为两阶段启动方式。Apache总是以root用户身份启动运行,因此启动Apache时首先必须获得root权限。在启动的过程中,初始化工作包括以下几个方面。

由于Apache中绝大多是的内存都是基于内存池分配的,因此,在Apache启动的时候必须首先初始化内存池资源。另外,初始化的一个重要任务就是对配置文件惊醒读取和解析。在启动的工程中,配置文件会被读取两次。第一次称之为预读取,主要是从配置文件中获取第二次配置文件中需要的一些配置信息;基于第一次读取的结果,第二次才是真正的配置文件读取。

在启动的最后阶段,Apache将通过调用ap_mpm_run函数将控制权交给MPM模块,只有MPM模块执行失败或执行完毕的时候,执行权才会从MPM返回到主程序中。MPM是Apache服务器和操作系统之间的接口,它的目的只有一个,就是充分利用操作系统特性,对服务器的并发进行最大化的优化。

3.4.2 HTTP连接处理

在接收到客户端的HTTP连接之后,客户端的IP地址已经可以获取。通过该IP地址,便可以确定该IP地址所对应的虚拟主机族,因此,连接处理的第一件事情就是更新虚拟主机的相关信息。

对于连接处理,最主要的任务就是调用预先定义好的连接处理挂钩process_connection。任何模块模块如果要处理连接,都可以实现该挂钩。一旦某个模块是相爱挂钩,那么在连接处理的过程中他们将会被调用。

在连接处理过程中,通过调用ap_read_request函数进入请求读取过程,然后调用ap_process_request对该请求进行处理。

3.4.3 请求报文读取

一旦接收到客户端的连接,请求数据将被读取出来,然后HTTP_PROTCCOL模块将开始对该报文进行解析,请求的解析包括三部分:

(1)HTTP请求头 (2)HTTP请求域

- 9 -

沈阳工程学院毕业论文 第3章 Apache体系结构

(3)HTTP请求体

请求保温读取后,所有的请求相关信息都保存到请求数据结构request_rec中。该结构是一个复杂的数据结构,其中包含了HTTP请求的各个方面。该结构在整个请求处理过程中一直存在,直到请求处理结束。

对于读取的报文还有一个重要的任务就是读取的报文进行输入过滤器处理。所有的输入处理过滤器组成输入过滤器表,在请求报文从网络中读取之后,它就直接进入了过滤器链表中,然后每个过滤器对其进行处理,并将其传送给下一个过滤器,直到最后一个过滤器。

在所有的过滤器处理完毕后,我们得到的就是一个最终的处理后的请求报文。此时请求处理模块HTTP_REQUEST将对请求做进一步处理。

3.4.4 请求处理

对于HTTP报文,Apache调用ap_process_request函数对请求进行实质的处理。Apache中的请求处理包括三个大的阶段:请求解析阶段、安全处理阶段和请求准备阶段。

1.请求解析阶段 (1)URL字符转义

该阶段是一个必须的阶段,如果请求是一个代理请求或请求结构的parsed_uri.path没有赋值,那么该阶段将不会被处理。

(2)从URL中剔除/../和/./字符 URL中所有的/../和/./字符串都在这一阶段调用ap_getparents()函数并剔除,该阶段能够确保将URL提交后续阶段处理时候是一个绝对的路径URL。这个阶段是不能省略的,必须被执行。

(3)首次读取URL相关配置信息 一旦第二步处理完毕后,只包含绝对路径的URL就生成完毕了,此时Apache将调用ap_location_walk从配置系统中查找与该URL关联的配置信息。在请求处理的后续阶段中,比如用户授权认证,权限控制等都需要依赖与读取的配置信息。如果当前请求是一个内部重定向请求或子请求,那么该请求的配置信息可能有很大一部分或全部继承自父请求。

URL关联的配置信息需要两次,此处一次读取。在对URL进行转换映射后,它可能被转换为一个完全不同的URL,此时还需要再次读取一次配置信息。完整配置信息是两次读取信息的最终叠加。

(4)URL名称转换(translate_name)

该阶段主要用于对URL尽心转换。比如Alias指令将某个URL映射到另一个特定的URL中;而mod_write模块则用于对URL的完全重写。

(5)map_to_storage

如果在translate_name阶段,请求的URL最终转换本次的磁盘路径,map_ to_ storage将用来确定特定的资源是否在磁盘上存在。

(6)二次URL相关配置文件读取

- 10 -

沈阳工程学院毕业论文 第3章 Apache体系结构

在进行了名称转换之后,我们需要再次读取它新的URL关联的配置信息。 (7)header_parser

该阶段用来检查HTTP请求头,目前该阶段已经很少使用。 2.安全处理阶段 (1)access_checker

该阶段主要是对客户的访问做一些基础性的限制工作。 (2)check_user_id

该阶段主要是检查用户的身份权限,具体就是检查用户是否存在,或者检查用户的密码是否正确。

(3)auth_check

该阶段主要检查用户是否有访问该资源的权限。 3.请求准备阶段 (1)type_checker

在权限检查完毕之后,说明可以响应用户的请求了,这时,Apache开始处理客户请求的资源,并返回给客户。

(2)fixups

这是请求处理的最后一个阶段。该阶段之后就是生成返回给客户端的相应内容。

3.4.5 内容生成

当所有的请求在请求处理阶段处理完毕后,它就调用适当的处理器生成相应内容。如果没有找到对应的处理器,那么默认处理器将被调用。

与请求数据一旦读取就被压入输入过滤器链表一样,生成的这些内容一旦生成,就将被压入到输出过滤器链表进行输出处理。输出过滤器链表中的每一个输出过滤器都会对生成的数据进行处理,直到最后一个输出过滤器——网络输出过滤器,它将数据直接写入网络,然后返回给客户端。

3.5 主程序main

3.5.1 主程序概要

Apache的主函数main()位于${Apache}/server目录下,它的主要功能则着重于预处理,主要包括以下几个方面。

(1)读取Apache的配置文件

(2)检查启动Apache的指令行参数 (3)虚拟主机的设置

3.5.2 主程序细节

- 11 -

沈阳工程学院毕业论文 第3章 Apache体系结构

Apache主程序实现代码如下所示:

int main(int argc, const char * const argv[])

{

char c;

int configtestonly = 0;

const char *confname = SERVER_CONFIG_FILE; const char *def_server_root = HTTPD_ROOT; const char *temp_error_log = NULL; process_rec *process; server_rec *server_conf; apr_pool_t *pglobal; apr_pool_t *pconf;

apr_pool_t *plog; /* Pool of log streams, reset _after_ each read of conf */ apr_pool_t *ptemp; /* Pool for temporary config stuff, reset often */ apr_pool_t *pcommands; /* Pool for -D, -C and -c switches */ apr_getopt_t *opt; apr_status_t rv; module **mod; const char *optarg;

APR_OPTIONAL_FN_TYPE(ap_signal_server) *signal_server; AP_MONCONTROL(0); /* turn off profiling of startup */ AP_MONCONTROL主要用于打开和关闭代码剖析。代码剖析用于检查代码的运行情况,从而可以找到程序执行的瓶颈,通常用于调试阶段。 apr_app_initialize(&argc, &argv, NULL);

任何一个应用程序如果要使用APR库进行二次开发,那么首先必须为完成的任务是APR库的初始化。apr_app_initialize 负责对APR库进行必要的初始化工作,代码如下:

process = create_process(argc, argv); pglobal = process->pool; pconf = process->pconf;

ap_server_argv0 = process->short_name;

由于Apache通常是通过指令行进行启动的,因此指令行的相关信息(指令数目arge及指令行字符串argv)都非常重要。对Apache而言,指令信息不仅主程序要使用,而且在一些子进程程序中也要使用,因此这就存在指令行信息传递问题。

Apache把所有的指令行相关信息都包装在process_rec结构中,代码如下: struct process_rec { apr_pool_t *pool; apr_pool_t *pconf; int argc;

const char * const *argv;

- 12 -

沈阳工程学院毕业论文 第3章 Apache体系结构

const char *short_name; };

short_name是应用程序的缩略名称。pool和pconf则分别是全局内存池和配置相关内存池。

创建一个process_rec结构体可通过create_process函数完成。 Apache中的指令分为两大类:指令行中的指令及配置文件中的指令。而对于指令中的指令有包括两种:读取配置文件之前必须处理的指令和读取配置文件后必须处理的指令。前者是防止指令行中的指令被覆盖,而后者则相反。一旦命令行被解析完毕后,这两种特殊的指令将被保存起来,以便在合适的时候执行。如果不保存,这些指令将会丢失。保存可以使用如下数组数据结构:

ap_setup_prelinked_modules(process); apr_pool_create(&pcommands, pglobal); apr_pool_tag(pcommands, \

ap_server_pre_read_config = apr_array_make(pcommands, 1, sizeof(char *)); ap_server_post_read_config = apr_array_make(pcommands, 1, sizeof(char *)); ap_server_config_defines = apr_array_make(pcommands, 1, sizeof(char *)); ap_setup_prelinked_modules用于将所有预链接的模块加入到加载模块链接表中。模块只有加入到加载模块链表中才能称之为活动模块,然后才能被Apache核心调用,否则该模块仍处于非活动状态。

error = ap_run_rewrite_args(process); if(error){ ap_log_error(APLOG_MARK, APLOG_STARTUP|APLOG_EMERG, 0,

NULL,”%S :%S”, ap_server_argv0, error);

destroy_and_exit_args(process, 1); }

rewrite_args挂钩主要是为MPM模块设置的,它允许MPM对命令行中的传入参数进行重写。对于一些MPM(如Event MPM、work MPM),它们须将命令行中传入的参数转换为MPM内部的参数。通过调用ap_run_rewrite_args,使得每一个模块都有这种机会进行命令行重写。

当httpd启动的时候,它可能会带有一系列的命令行参数。完整的httpd命令如下:

httpd [-d serverroot] [-f config] [-C directive] [-c directive] [-D parameter] [-e level] [-E file] [-k start|restart|graceful|stop|graceful-stop] [-R directory] [-h] [-l] [-L] [-S] [-t] [-v] [-V] [-X] [-M]

在Windows系统中,还可以使用如下参数: Httpd [-k install|config|uninstall] [-n name] [-w] 各个参数处理如下:

apr_getopt_init(&opt, pcommands, process->argc, process->argv);

while ((rv = apr_getopt(opt, AP_SERVER_BASEARGS, &c, &optarg)) == APR_SUCCESS) { char **new;

- 13 -

沈阳工程学院毕业论文 第3章 Apache体系结构

switch (c) { case 'c':

new = (char **)apr_array_push(ap_server_post_read_config); *new = apr_pstrdup(pcommands, optarg); break; case ?C?:

new = (char **)apr_array_push(ap_server_pre_read_config); *new = apr_pstrdup(pcommands, optarg); break;

-C选项指定了再读取配置文件之前必须先处理directive的配置指令。而-c指令则指定了再读取配置文件之后,才能再处理directive的配置命令。-c中的指令保存到ap_server_post_read_config数组中,而-C中的指令则保存到ap_server_ pre_read_config数组中。

case 'd':

def_server_root = optarg; break;

-d serverroot选项蒋ServerRoot指令设置初始值为serverroot。它可以将配置文件中的ServerRoot指令所覆盖。其默认值为/uer/loca/apache2。

case 'D':

new = (char **)apr_array_push(ap_server_config_defines); *new = apr_pstrdup(pcommands, optarg); break;

-D parameter选项用于设置参数parameter,它配合配置文件中的段,用于服务器启动和重新启动时,有条件地跳过或处理某些命令。

case 'e':

if (strcasecmp(optarg, \

ap_default_loglevel = APLOG_EMERG; } else if (strcasecmp(optarg, \

ap_default_loglevel = APLOG_ALERT;} else if (strcasecmp(optarg, \

ap_default_loglevel = APLOG_CRIT;} else if (strncasecmp(optarg, \ ap_default_loglevel = APLOG_ERR;} else if (strncasecmp(optarg, \ ap_default_loglevel = APLOG_WARNING;}

else if (strcasecmp(optarg, \ ap_default_loglevel = APLOG_NOTICE;} else if (strcasecmp(optarg, \

ap_default_loglevel = APLOG_INFO;}

else if (strcasecmp(optarg, \ ap_default_loglevel = APLOG_DEBUG;} else { usage(process); } break;

-e level选项在服务器启动时,设置日志的LogLevel为level。它用于在启动时,临时提高出错信息的详细程度,以帮助排错。

- 14 -

沈阳工程学院毕业论文 第3章 Apache体系结构

case 'E':

temp_error_log = apr_pstrdup(process->pool, optarg); break; -E file选项用于将服务器的启动过程中的出错信息发送到file。 case 'X':

new = (char **)apr_array_push(ap_server_config_defines);

*new = \ break;

-X选项指定当前Apache以调式模式运行,在此模式下,Apache进启动一个工作进程,并且服务器不与控制台脱离。

case 'f':

confname = optarg; break;

-f config选项在启动中使用config作为配置文件。如果config不以“/”开头,则它是相对于ServerRoot的路径。默认值为conf/httpd.conf。 case 'v':

printf(\ printf(\ %s\\n\ destroy_and_exit_process(process, 0);

-v选项只是简单的显示httpd的版本,然后退出。 case 'V':

show_compile_settings();

destroy_and_exit_process(process, 0);

-V选项用于显示httpd和APR/APR-Util的版本和编译参数,然后退出。 case 'l':

ap_show_modules();

destroy_and_exit_process(process, 0);

-I选项用于输出一个静态编译在服务器中的模块列表。它不会列出使用LoadModule指令动态的模块。

case 'L':

ap_show_directives();

destroy_and_exit_process(process, 0);

-L选项输出一个指令列表,并包含了各指令有效参数和使用区域。 case 't':

configtestonly = 1; break;

-t选项意味着仅对配置文件执行语法检查。 case ?S?:

configtestonly = 1;

new = (char **)apr_array_push(ap_server_config_defines); *new = “DUMP_VHOSTS”; break;

-S显示从配置文件中读取并解析的设置结果。 case ?M?:

configtestonly = 1;

- 15 -

沈阳工程学院毕业论文 第3章 Apache体系结构

new = (char **)apr_array_push(ap_server_config_defines); *new = “DUMP_MODULES”; break;

-M输出一个已经启用模块列表,包括静态编译在服务器中的模块和作为DSO动态加载的模块。

case 'h':

case '?':

usage(process);

-h和-?输出一个可用的命令行选项的简要说明。 } }

主程序对于Windows所需要的-K、-n及-w选项并没有处理,这些选项由于只有MPM才会使用到,因此它们在MPM中被处理,处理由rewrite_args挂钩触法。

/* bad cmdline option? then we die */

if (rv != APR_EOF || opt->ind < opt->argc) { usage(process);}

apr_pool_create(&plog, pglobal); apr_pool_tag(plog, \

apr_pool_create(&ptemp, pconf); apr_pool_tag(ptemp, \

Apache中使用的所有内存资源都是基于内存池的概念分配的,所有的内存池之间形成内存池树的概念。层次越深的内存池它的生存周期就越短,反之,距离根节点越近,它的生存周期就越长。所有节点的根节点是全局内存池pglobal,在启动的时候被分配。除此之外,在启动的时候还需要一个临时内存池——ptemp。

ap_server_root = def_server_root; if (temp_error_log) {

ap_replace_stderr_log(process->pool, temp_error_log); }

一般情况下,如果没有指定日志输出文件,就是用标准的输出设备stderr。。如果在启动Apache的时候通过-E选项指定了日志文件,那么,此时必须使用ap_replace_stderr_log进行输出日志文件替换。

if (ap_run_pre_config(pconf, plog, ptemp) != OK) { destroy_and_exit_process(process, 1);}

rv = ap_process_config_tree(server_conf, ap_conftree, process->pconf,

ptemp);

if(rv = = OK){

ap_fixup_virtual_hosts(pconf, server_conf); ap_fini_vhost_config(pconf, server_conf); apr_hook_sort_all(); if (configtestonly) {

- 16 -

沈阳工程学院毕业论文 第3章 Apache体系结构

ap_run_test_config(pconf, server_conf);

ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, \

destroy_and_exit_process(process, 0);} }

在main.c中,配置文件在Apache启动或重启时的时候总是被读取两次,一次是在主循环执行之前被读取,正如上面代码中所读取的一样,另外一次是在主循环中被读取。之所以要读取两次,主要基于以下两个目的:

(1)预检查配置文件中可能出现的语法错误,确保在真正处理的时候配置文件是完整无误的。

(2)第一次读取文件会生成第二次读取文件时所需要的字段。

代码如下:

signal_server = APR_RETRIEVE_OPTIONAL_FN(ap_signal_server); if (signal_server) { int exit_status;

if (signal_server(&exit_status, pconf) != 0) {

destroy_and_exit_process(process, exit_status); } }

if(rv ! = OK) {

destroy_and_exit_process(process, exit_status); apr_pool_clear(plog);

apr_pool_clear(plog);

if ( ap_run_open_logs(pconf, plog, ptemp, server_conf) != OK) { destroy_and_exit_process(process, 1);}

if ( ap_run_post_config(pconf, plog, ptemp, server_conf) != OK) { destroy_and_exit_process(process, 1);} apr_pool_destroy(ptemp);

整个配置文件的完整读取过程包括三个部分。 (1)读取配置文件的准备工作。 (2)实际的配置文件读取。 (3)读取配置文件后的处理。

如果摸个模块由于某种原因需要启动分离的进程,就应该在这个阶段来完成,代码如下:

for (;;) {

apr_hook_deregister_all(); apr_pool_clear(pconf);

for (mod = ap_prelinked_modules; *mod != NULL; mod++) { ap_register_hooks(*mod, pconf); }

ap_conftree = NULL;

- 17 -

沈阳工程学院毕业论文 第3章 Apache体系结构

apr_pool_create(&ptemp, pconf); apr_pool_tag(ptemp, \

ap_server_root = def_server_root;

server_conf = ap_read_config(process, ptemp, confname,

&ap_conftree);

if (ap_run_pre_config(pconf, plog, ptemp) != OK) { ap_log_error(APLOG_MARK,

APLOG_STARTUPAPLOG_ERR, 0, NULL, \

destroy_and_exit_process(process, 1); }

if (ap_process_config_tree(server_conf, ap_conftree, process->pconf,

ptemp) != OK) {

destroy_and_exit_process(process, 1); } ap_fixup_virtual_hosts(pconf, server_conf); ap_fini_vhost_config(pconf, server_conf); apr_sort_hooks(); apr_pool_clear(plog);

if (ap_run_open_logs(pconf, plog, ptemp, server_conf) != OK) { ap_log_error(APLOG_MARK, APLOG_STARTUP |APLOG_ERR,

0, NULL, \ destroy_and_exit_process(process, 1);}

if (ap_run_post_config(pconf, plog, ptemp, server_conf) != OK) { ap_log_error(APLOG_MARK,

APLOG_STARTUP |APLOG_ERR,

0, NULL, \ destroy_and_exit_process(process, 1); } apr_pool_destroy(ptemp); apr_pool_lock(pconf, 1);

ap_run_optional_fn_retrieve();

if (ap_mpm_run(pconf, plog, server_conf))

break;

apr_pool_lock(pconf, 0); }

apr_pool_lock(pconf, 0);

destroy_and_exit_process(process, 0); return 0; /* Supress compiler warning. */ }

准备就绪后,Apache就进入了主循环for(;;)。循环中主进程所作的事情包括以下几点:

- 18 -

沈阳工程学院毕业论文 第3章 Apache体系结构

(1)挂钩注册

(2)二次配置文件读取

(3)到处注册所有的可选函数 (4)ap_mpm_run调用

如果ap_mpm_run在执行中发生错误,则返回1,否则返回0。 当ap_mpm_run退出的时候,整个主进程就相应的执行结束了。

- 19 -

沈阳工程学院毕业论文 第4章 Apache模块化体系结构

第4章 Apache模块化体系结构

Apache模块化体系结构是本书介绍的重点内容之一,通过模块化,Apache可以很方便地进行第三方扩展及裁剪。本章详细介绍了模块数据结构,并对模块结构中成员的上下文进行介绍,包括指令表、挂钩、配置结构操作指针,可让读者能够透彻理解与模块相关的方方面面,不仅知其然,而且知其所以然。除此之外,本章还介绍了核心模块如何加载的过程,以及Apache模块通信所采用的各种策略,包括简单通信方式、可选函数通信方式及提供者API方式。本章最后对Apache中的一些常用模块进行了简单介绍。

4.1 Apache模块概述

Apache设计的一个最重要的目标就是扩展性能,用户可以根据自己的需要对Apache进行功能扩展。为此引出了模块的概念。Apache模块通常是指一些具有一定相对独立功能函数。送模块的角度讲,Apache有两部分组成:Apache核心和Apache模块。如果要扩展Apache功能,那么你 自行编写Apache模块或使用现有的Apache模块。

Apache的核心对其余模块的管理包括下面几个部分。 (1)非核心模块的注册和卸载。

(2)核心模块负责接收客户端的请求,并与非核心模块进行交互。

(3)挂钩、过滤器及可选函数的注册。

4.2 模块的加载

4.2.1 静态模块加载

Apache中对模块的家在处理是从main.c中的ap_setup_prelink_modules开始的:

error = ap_setup_prelinked_modules(process); if (error) { ap_log_error(APLOG_MARK, APLOG_STARTUP|APLOG_EMERG,

0, NULL, \

destroy_and_exit_process(process, 1);}

它用于将当前Apache中的所有modules.c中预链接模块加载并激活,函数实现如下:

AP_DECLARE(const char*)ap_setup_prelinked_modules(process_rec*process) {

module **m; module **m2;

- 20 -

沈阳工程学院毕业论文 第4章 Apache模块化体系结构

const char *error;

apr_hook_global_pool=process->pconf;

rebuild_conf_hash(process->pconf, 0); total_modules = 0;

for (m = ap_preloaded_modules; *m != NULL; m++)

(*m)->module_index = total_modules++;

加载的前期阶段工作包括创建指令名称的哈希表和初始化各个模块的索引值。

初始化的另一个工作是对ap_preloaded_modules进行索引标记,当初始化时可以看到各个模块的索引都被设置为-1。

ap_loaded_modules = (module **)apr_palloc(process->pool,

sizeof(module *) * (total_modules + DYNAMIC_MODULE_LIMIT + 1)); if (ap_loaded_modules == NULL) {

return \ for (m = ap_preloaded_modules, m2 = ap_loaded_modules; *m != NULL; ) *m2++ = *m++; *m2 = NULL;

for (m = ap_prelinked_modules; *m != NULL; m++) { error = ap_add_module(*m, process->pconf); if (error) { return error;}

} apr_hook_sort_all(); return NULL; }

ap_loaded_modules数组用来保存所有的加载模块,即时模块没有激活,对于ap_preloaded_modules也不例外,因此作为初始化工作,有必要的将ap_preloaded_modules中的数组先添加到ap_loaded_modules数组中。以后如果需要新增加第三方模块,同样也添加到ap_loaded_modules数组中。

对于Apache2.0而言,ap_preloaded_modules数组中的模块都要被激活,为此,Apache还必须调用ap_add_module将它们添加到激活链表中。一旦激活所有的模块,函数便调用apr_hook_sort_all对所有模块内的钩子进行排序,以便于处理。

现在我们来看ap_add_module函数,其定义如下:

AP_DECLARE(const char *) ap_add_module(module *m, apr_pool_t *p){ if (m->version != MODULE_MAGIC_NUMBER_MAJOR) {

return apr_psprintf(p, \ \ \

m->name, m->version, MODULE_MAGIC_NUMBER_MAJOR);} if (m->next == NULL) {

- 21 -

沈阳工程学院毕业论文 第4章 Apache模块化体系结构

m->next = ap_top_module; ap_top_module = m;}

if (m->module_index == -1) {

m->module_index = total_modules++; dynamic_modules++;

if (dynamic_modules > DYNAMIC_MODULE_LIMIT) {

return apr_psprintf(p, \ \ \

\ }

if (ap_strrchr_c(m->name, '/'))

m->name = 1 + ap_strrchr_c(m->name, '/'); if (ap_strrchr_c(m->name, '\\\\'))

m->name = 1 + ap_strrchr_c(m->name, '\\\\'); ap_add_module_commands(m, p); ap_register_hooks(m, p); return NULL; }

ap_add_module用于在服务器中激活制定模块m,通常它用于应对http.conf文件中的AddModule指令。

再将模块加入服务器之前,Apache必须判断模块的版本是否与当前Apache服务器的版本相同。Apache2.0所有模块的版本好统一为MODULE_MAGI- C_NUMBER_MAJOR,min_version则为MODULE_MAGIC_NUMBER_MINOR。

所有的模块最终都被加入到全局模块列表ap_top_module中,所有新增加的模块都被插入到ap_top_module链表的起始位置。一旦插入完成,Apache将对插入的模块进行索引标记。有一点要注意,累积的总模块数不能超过动态载入模块的最大数DYNAMIC_MODULE_LIMIT。

模块还必须将他们所有的指令处理函数加入到指令哈希表中,该过程由函数ap_add_module_commands完成,代码如下:

static void ap_add_module_commands(module *m, apr_pool_t *p){

apr_pool_t *tpool; ap_mod_list *mln;

const command_rec *cmd; char *dir;

cmd = m->cmds;

if (ap_config_hash == NULL) { rebuild_conf_hash(p, 0);}

tpool = apr_hash_pool_get(ap_config_hash); while (cmd && cmd->name) {

- 22 -

沈阳工程学院毕业论文 第4章 Apache模块化体系结构

mln = apr_palloc(tpool, sizeof(ap_mod_list)); mln->cmd = cmd;

mln->m = m;

dir = apr_pstrdup(tpool, cmd->name); ap_str_tolower(dir);

mln->next = apr_hash_get(ap_config_hash, dir,

APR_HASH_ KEY_STRING);

apr_hash_set(ap_config_hash, dir, APR_HASH_KEY_STRING, mln); ++cmd;} }

如果指令哈希表不存在,则首先必须构建生成该指令哈希表,然后逐一遍历模块m中的指令表,并将它们插入到指令哈希表中对应的位置。

4.2.2 动态模块加载

大部分Apache标准模块都可以通过静态编译链接加载执行,不过对很多的第三方Apache模块却很难实现,而这些模块可以通过Apache的动态加载机制实现模块加载。

在分析每个模块之前,首先要分析的是模块的数据结构及该模块能够处理的指令表。对于mod_so模块也不例外,mod_so模块的结构和其中的指令如下所示:

static const command_rec so_cmds[] = {

AP_INIT_TAKE2(\ RSRC_CONF | EXEC_ON_READ,

\ AP_INIT_ITERATE(\

RSRC_CONF | EXEC_ON_READ,

\ { NULL } };

module AP_MODULE_DECLARE_DATA so_module = { STANDARD20_MODULE_STUFF,

NULL, /* create per-dir config */ NULL, /* merge per-dir config */ so_sconf_create, /* server config */

NULL, /* merge server config */ so_cmds, /* command apr_table_t */ register_hooks /* register hooks */};.

模块中能够处理的所有指令都保存在指令表so_cmds中,从中可以看出来,mod_so模块可以处理的指令只有“LoadModule”和“LoadFile”,相应的处理函数分别为load_module 和load_file。下面我们首先来看load_module函数的实现,

- 23 -

沈阳工程学院毕业论文 第4章 Apache模块化体系结构

该函数也是模块装载处理的入口。

static const char *load_module(cmd_parms *cmd, void *dummy,

const char *modname, const char *filename) 该函数用来将共享对象载入到服务器地址空间中。其中,cmd和dummy是所有的指令程序必须有的,由于Apache核心给模块传递相应信息。modname是加载模块的名称,而filename是对应的.so文件的绝对路径名称。

{

apr_dso_handle_t *modhandle; apr_dso_handle_sym_t modsym; module *modp;

const char *szModuleFile = ap_server_root_relative(cmd->pool, filename);

so_server_conf *sconf;

ap_module_symbol_t *modi; ap_module_symbol_t *modie; int i;

const char *error;

*(ap_directive_t **)dummy = NULL; if (!szModuleFile) {

return apr_pstrcat(cmd->pool, \ filename, NULL);} sconf = (so_server_conf *)ap_get_module_config(cmd->server->module_config,

&so_module); modie = (ap_module_symbol_t *)sconf->loaded_modules->elts; for (i = 0; i < sconf->loaded_modules->nelts; i++) { modi = &modie[i];

if (modi->name != NULL && strcmp(modi->name, modname) == 0) { ap_log_perror(APLOG_MARK, APLOG_WARNING, 0,

cmd->pool, \%s is already loaded, skipping\

modname); return NULL;} }

函数首先检查参数szModuleFile制定的so文件是否存在,如果存在,下一步必须在现有的所有模块中查找需要载入的模块是否存在,如果该模块已载入,则无需做任何处理,否则对其进行加载。

for (i = 0; ap_preloaded_modules[i]; i++) { const char *preload_name; apr_size_t preload_len; apr_size_t thismod_len;

- 24 -

沈阳工程学院毕业论文 第4章 Apache模块化体系结构

modp = ap_preloaded_modules[i];

if (memcmp(modp->name, \ continue;}

preload_name = modp->name + strlen(\ preload_len = strlen(preload_name) - 2;

if (strlen(modname) <= strlen(\ continue;}

thismod_len = strlen(modname) - strlen(\ if (strcmp(modname + thismod_len, \ continue;}

if (thismod_len != preload_len) {

continue;}

if (!memcmp(modname, preload_name, preload_len)) { return apr_pstrcat(cmd->pool, \ \ NULL);} }

如果制定的模块没有被加载,那么函数将执行加载。对动态加载模块检查完毕后,Apache将检查静态链接模块数组ap_preloaded_modules。在ap_preloaded_modules数组中查找指定的模块相对简单。对于每一个模块,要阿Apache认定他是一个合法的模块,就必须符合:

(1)Apache必须保证其文件名是以\开始的。 (2)Apache必须保证其文件名是以\结束的。 modi = apr_array_push(sconf->loaded_modules); modi->name = modname; if (apr_dso_load(&modhandle, szModuleFile, cmd->pool) != APR_SUCCESS) {

char my_error[256];

return apr_pstrcat(cmd->pool, \ \

apr_dso_error(modhandle, my_error, sizeof(my_error)),

NULL);}

ap_log_perror(APLOG_MARK, APLOG_DEBUG, 0, cmd->pool, \

if (apr_dso_sym(&modsym, modhandle, modname) != APR_SUCCESS) { char my_error[256];

return apr_pstrcat(cmd->pool, \ modname, \ apr_dso_error(modhandle, my_error,

- 25 -

沈阳工程学院毕业论文 第4章 Apache模块化体系结构

sizeof(my_error)),

NULL);}

modp = (module*) modsym;

modp->dynamic_load_handle = (apr_dso_handle_t *)modhandle; modi->modp = modp;

一切检查完毕后,函数将对模块进行加载。加载分为两步骤。 (1)在module_so模块中保存当前新载入的模块信息。

(2)调用apr_dso_load将文件载入到Apache的地址空间中,同时调用apr_dso_sym获取动态库中的module结构,返回的结构保存在modsym中。

if (modp->magic != MODULE_MAGIC_COOKIE) {

return apr_psprintf(cmd->pool, \ \garbled - expected signature lx but saw \

\- perhaps this is not an Apache module DSO, \

\was compiled for a different Apache version?\

modname, szModuleFile, MODULE_MAGIC_COOKIE, modp->magic);}

error = ap_add_loaded_module(modp, cmd->pool); if (error) {

return error;}

在真正使用即激活加载模块前,Apache必须确保加载的模块确实是Apache没快。为此Apache在模块结构中设定了magic字段,通过检查magic字段,Apache确定加载的模块是否是Apache模块。如果加载的是合法的Apache模块,函数将立即调用ap_add_loaded_module蒋模块激活,所谓的激活无非是将模块放入ap_top_mudoles链表中。

apr_pool_cleanup_register(cmd->pool, modi, unload_module, apr_pool_cleanup_null);

ap_single_module_configure(cmd->pool, cmd->server, modp); return NULL; }

此外,Apache还需要在配置内存池pconf中注册cleanup函数。这样当我们重新启动或关闭服务器时,cleanup函数将自动调用并共享模块卸载。

动态加载模块最后一步是调用模块内部的create_server_config和create_dir_config指针创建模块服务器的相关配置和目录相关配置。它们的创建封装在ap_single_module_configure中。

实际的核心加载由ap_add_loaded_module完成,该函数的实现相对简单,如下:

- 26 -

沈阳工程学院毕业论文 第4章 Apache模块化体系结构

AP_DECLARE(const char *) ap_add_loaded_module(module *mod, apr_pool_t *p)

{

module **m; const char *error;

error = ap_add_module(mod, p); if (error) {

return error;}

for (m = ap_loaded_modules; *m != NULL; m++) ;

*m++ = mod;

*m = NULL; return NULL; }

ap_add_loaded_module函数完成的事情很简单,一方面它调用ap_add_module将模块增加到ap_top_module模块链表中,另一方面还要将模块增加到ap_loaded_modules加载模块链表中。

4.2.3 模块卸载

当一个模块不要再使用时,该模块就可以被卸载。Apache中并没有提供显示式的卸载模块的指令和接口,也就是说,一个模块一旦被加载,就无法再运行过程中被卸载。卸载的唯一办法就是修改配置文件或重新编译。

由于模块数据结构中所占用的内存也是从内存池中分配出来的,因此它遵循内存池的清理原则。当内存被释放时,模块数据结构调用自身的清理数据结构执行模块清理。模块的清理通过函数unload_module完成:

static apr_status_t unload_module(void *data){

ap_module_symbol_t *modi = (ap_module_symbol_t*)data; if (modi->modp == NULL) return APR_SUCCESS;

ap_remove_loaded_module(modi->modp); modi->modp = NULL; modi->name = NULL; return APR_SUCCESS; }

实际的模块卸载由ap_remove_loaded_module函数完成,该函数需要一个参数,即模块的数据描述结构:

AP_DECLARE(void) ap_remove_loaded_module(module *mod){ module **m; module **m2;

- 27 -

沈阳工程学院毕业论文 第4章 Apache模块化体系结构

int done;

ap_remove_module(mod);

for (m = m2 = ap_loaded_modules, done = 0; *m2 != NULL; m2++) { if (*m2 == mod && done == 0) done = 1;

else

*m++ = *m2;} *m = NULL; }

整个移除过程分两步,首先必须从模块链表ap_top_module中将模块结构移除,该移除由ap_remove_module完成。

当模块从ap_top_module链表中被移除时,ap_loaded_modules中指向该模块的指针也要被设置为NULL。

4.3 指令表

4.3.1 指令表概述

模块的另外一项功能就是指令表。所谓指令表就是保存各个指令名称及该指令处理函数的数组,图4-1展示的是一个完整的指令构成。

图4-1 指令表构成

指令名称(name)指令处理函数(func)指令参数(cmd_data)指令覆盖类型(cmd_override)指令处理方式(cmd_how)指令返回提示(errmsg)一个指令包含六部分内容,但与模块最紧密相连的是前两个成员,后面的四个成员都是指令本身的信息。

4.3.2 指令处理函数

关于指令处理函数,它与普通的函数没有太多区别,只有两个特殊的地方:一是参数,另一个是返回值。

- 28 -

沈阳工程学院毕业论文 第4章 Apache模块化体系结构

1.参数

所有的指令处理函数,它的前两个参数都是cmd_parms和void*类型,这可以从指令处理函数的类型看出:

typedef union {

const char *(*no_args) (cmd_parms *parms, void *mconfig);

const char *(*raw_args) (cmd_parms *parms, void *mconfig, const char *args);

const char *(*take_argv) (cmd_parms *parms, void *mconfig, int argc, char *const argv[]);

const char *(*take1) (cmd_parms *parms, void *mconfig, const char *w); const char *(*take2) (cmd_parms *parms, void *mconfig, const char *w, const char *w2);

const char *(*take3) (cmd_parms *parms, void *mconfig, const char *w, const char *w2, const char *w3);

const char *(*flag) (cmd_parms *parms, void *mconfig, int on); } cmd_func;

第一个参数是cmd_parms类型,定义在httpd_config.h中。cmd_parms结果会包含各种信息,所有信息至少会适用于一个指令,它是一个大杂烩,只要处理函数中可能需要的参数都会杯赛导致该结构体中。

除了cmd_parms参数外,指令处理函数的另外一个参数就是mconfig,它是void指针类型。各个指令处理函数所需要的参数不尽相同,可以通过mconfig传入。

2.返回值

指令处理函数的返回值只有三种:NULL、DECLINE_CMD及错误字符串。 如果指令处理函数能正常处理完某个指令,那么函数将返回NILL;如果在处理过程中发生了错误,那么函数将返回一个字符串用于描述发生的错误。

第三种情况比较难理解,就是返回DECLINE_CMD,它被定义为“\\a\\b\\”。该返回值在极少数场合下才会被使用。

4.4 挂钩(HOOK)

4.4.1 声明挂钩

Apache中关于挂钩的实现大部分是通过宏来完成的,而且这些宏大多数非常复杂。挂钩的实现主要定义在文件apr_hook.h和apr_hook.c中,另外在congfig.c中也有部分定义。

Apache中对挂钩的使用总是从定义一个挂钩开始的,在Apache中声明一个挂钩,总是通过如下的宏来实现的。

#define AP_DECLARE_HOOK(ret,name,args) \\

APR_DECLARE_EXTERNAL_HOOK(ap,AP,ret,name,args)

- 29 -

沈阳工程学院毕业论文 第4章 Apache模块化体系结构

在该宏中,,ret是定义挂钩的返回类型,而name则是定义挂钩的名称;args则是挂钩函数需要的额外参数,通常以bracket形势出现。例如下面的语句就声明了一个返回类型为整数,函数名称为do_something,函数参数为(request_rec *r,int n)的挂钩;

AP_DECLARE_HOOK(int, do_something, (request_rec *r,int n)) 不过,AP_DECLARE_HOOK内部则是调用的APR_DECLARE_EXTERNAL _HOOK宏,该宏定义如下:

#define APR_DECLARE_EXTERNAL_HOOK(ns,link,ret,name,args) \\ typedef ret ns##_HOOK_##name##_t args; \\

link##_DECLARE(void) ns##_hook_##name(ns##_HOOK_##name##_t *pf, \\ const char * const *aszPre, \\

const char * const *aszSucc, int nOrder); \\ link##_DECLARE(ret) ns##_run_##name args; \\

APR_IMPLEMENT_HOOK_GET_PROTO(ns,link,name); \\ typedef struct ns##_LINK_##name##_t \\ { \\

ns##_HOOK_##name##_t *pFunc; \\ const char *szName; \\

const char * const *aszPredecessors; \\ const char * const *aszSuccessors; \\ int nOrder; \\

} ns##_LINK_##name##_t;

该宏进一步替换展开后为APR_DECLARE_EXTERNAL_HOOK(ap,AP,int, post_config,(apr_pool_t *pconf,apr_pool_t *plog,apr_pool_t *ptemp,server_rec *s) ),声明一个挂钩宏需要五部分内容。

(1)定义挂钩的执行函数原型:typedef ret ns##_HOOK_##name##_t args; (2)定义挂钩注册函数的原型:

link##_DECLARE(void) ns##_hook_##name(ns##_HOOK_##name##_t *pf, \\ const char * const *aszPre, \\

const char * const *aszSucc, int nOrder); \\ (3)声明挂钩的调用函数:ink##_DECLARE(ret) ns##_run_##name args; (4)APR_IMPLEMENT_HOOK_GET_PROTO(ns,link,name); 该宏展开如下所示:

#define APR_IMPLEMENT_HOOK_GET_PROTO(ns,link,name) \\ link##_DECLARE(apr_array_header_t *) ns##_hook_get_##name(void) 该宏用于返回挂钩访问函数原型,在模块外部可以调用该函数获得注册为该挂钩的所有函数。

(5)typedef struct ns##_LINK_##name##_t \\ { \\

ns##_HOOK_##name##_t *pFunc; \\

- 30 -

沈阳工程学院毕业论文 第4章 Apache模块化体系结构

const char *szName; \\

const char * const *aszPredecessors; \\ const char * const *aszSuccessors; \\ int nOrder; \\

} ns##_LINK_##name##_t;

该宏定义了一个结构类型,用来保存挂钩的相关信息,这些信息与在挂钩定义宏中的信息完全相同。

至此,与挂钩关联的五个方面都已经声明完毕了。

4.4.2 挂钩数组声明

在Apache中,系统定义了一定数量的挂钩,这些挂钩总的来说可以分为两大类:启动挂钩和请求挂钩。启动挂钩是随着服务器启动进行调用的挂钩。而请求挂钩则是服务器处理请求时进行调用的挂钩。对于同一个挂钩,不同模块对应的处理函数各不相同,为了能够保存各个模块中对同一挂钩的实用信息,Apache核心使用apr_array_header_t数组进行保存,该书组通过宏APR_HOOK_LINK声明:

#define APR_HOOK_LINK(name) \\ apr_array_header_t *link_##name;

该宏定义很简单,实际上就是声明了一个apr_array_header_t类型数组,用来保存对应某个挂钩的所有挂钩函数。

4.4.3 挂钩结构

对于每一个挂钩,Apache都会顶一个apr_array_header_t数组来保存它的相关信息。一旦定义了挂钩数组,该数组将在整个Apache中保持唯一。当某个模块想要使用该挂钩的时候,只要访问模块内对应的挂钩数组即可。

不过,Apache2.0中并不支持直接访问挂钩数组,因此,你想直接将数据压入数组是不可能的。Apache2.0中引入了APR_HOOK_STRUCT宏,所有对数组的操作都只能通过该红来实现。该宏定义如下:

#define APR_HOOK_STRUCT(members) \\ static struct { members } _hooks;

该宏展开后实际上定义了一个限于模块内使用的结构_hooks,该模块内部实现的所有挂钩的对应数组都保存为_hook的成员。

Apache中对挂钩数组的访问都必须通过_hook来实现。 不过有几点必须注意的是:

(1)_hook在某个文件中只能定义一次。

(2)_hook的定义为static,这意味着该结构实际上是模块内部的私有结构,外部模块无法直接访问_hook结构。

- 31 -

沈阳工程学院毕业论文 第4章 Apache模块化体系结构

4.4.4 挂钩函数注册

从宏的名字我们就可以大体看出,该宏实际上是实现了具体的挂钩注册函数。该宏定义如下所示:

#define APR_IMPLEMENT_EXTERNAL_HOOK_BASE(ns,link,name) \\

link##_DECLARE(void) ns##_hook_##name(ns##_HOOK_##name##_t *pf,

const char * const *aszPre, \\

const char * const *aszSucc,int nOrder) \\ { \\

ns##_LINK_##name##_t *pHook; \\ if(!_hooks.link_##name) \\

{ \\

_hooks.link_##name=apr_array_make(apr_hook_global_pool,1,sizeof(ns##_LINK_##name##_t)); \\

apr_hook_sort_register(#name,&_hooks.link_##name); \\ } \\

pHook=apr_array_push(_hooks.link_##name); \\ pHook->pFunc=pf; \\

pHook->aszPredecessors=aszPre; \\ pHook->aszSuccessors=aszSucc; \\ pHook->nOrder=nOrder; \\ pHook->szName=apr_hook_debug_current; \\ if(apr_hook_debug_enabled) \\

apr_hook_debug_show(#name,aszPre,aszSucc); \\ } \\

APR_IMPLEMENT_HOOK_GET_PROTO(ns,link,name) \\ { \\

return _hooks.link_##name; \\ }

挂钩注册函数首先检查挂钩数组是否为空,如果为空,则说明是第一次注册该挂钩,然后创建新数组并注册该挂钩类型以供以后排序用;否则,直接加入一条新纪录。

4.4.5 使用挂钩

Apache中挂钩调用函数形式通常如ap_run_HOOKNAME所示,比如ap_run_post_config就是调用post_config挂钩。尽管所有的挂钩对外提供的形式都是一样的,但是内部实现却不相同,差别分别体现于三个宏:AP_IMPLE- MENT_HOOK_VOID、AP_IMPLEMENT_HOOK_RUN_FIRST及AP_IMPLEM- ENT_HOOK_RUN_ALL。

- 32 -

沈阳工程学院毕业论文 第4章 Apache模块化体系结构

1.对于AP_IMPLEMENT_HOOK_VOID,调用函数将遍历挂钩数组,逐个执行针对该挂钩的所有注册过的挂钩函数,直到遍历调用结束。这种类型通常称为VOID,由于它没有任何返回值,其声明如下:

#define

APR_IMPLEMENT_EXTERNAL_HOOK_VOID(ns,link,name,args_decl,args_use) \\

APR_IMPLEMENT_EXTERNAL_HOOK_BASE(ns,link,name) \\ link##_DECLARE(void) ns##_run_##name args_decl \\ { \\

ns##_LINK_##name##_t *pHook; \\ int n; \\ \\

if(!_hooks.link_##name) \\ return; \\ \\

pHook=(ns##_LINK_##name##_t *)_hooks.link_##name->elts; \\ for(n=0 ; n < _hooks.link_##name->nelts ; ++n) \\ pHook[n].pFunc args_use; \\ } 2.AP_IMPLEMENT_HOOK_RUN_ALL简称ALL类型,它与AP_IMPLEME- NT _ HOOK_VOID几乎相同,唯一不同的是ALL类型具有返回值。宏声明如下:

#define APR_IMPLEMENT_EXTERNAL_HOOK_RUN_ALL(ns,link,ret,name,args_decl,

args_use,ok,decline) \\

APR_IMPLEMENT_EXTERNAL_HOOK_BASE(ns,link,name) \\ link##_DECLARE(ret) ns##_run_##name args_decl \\ { \\

ns##_LINK_##name##_t *pHook; \\ int n; \\ ret rv; \\ \\

if(!_hooks.link_##name) \\

return ok; \\ \\

pHook=(ns##_LINK_##name##_t *)_hooks.link_##name->elts; \\ for(n=0 ; n < _hooks.link_##name->nelts ; ++n) \\ { \\

rv=pHook[n].pFunc args_use; \\ \\

if(rv != ok && rv != decline) \\ return rv; \\

- 33 -

沈阳工程学院毕业论文 第4章 Apache模块化体系结构

} \\

return ok; \\

}

3.AP_IMPLEMENT_HOOK_RUN_FIRST简称FIRST类型,对于该类型,Apache核心从头逐一遍历挂钩数组中所注册的挂钩函数,直到遇到一个能够完

成所提交任务的函数或发生错误为止。该宏定义如下:

#define

APR_IMPLEMENT_EXTERNAL_HOOK_RUN_FIRST(ns,link,ret,name,args_decl,

args_use,decline) \\

APR_IMPLEMENT_EXTERNAL_HOOK_BASE(ns,link,name) \\ link##_DECLARE(ret) ns##_run_##name args_decl \\

{ \\

ns##_LINK_##name##_t *pHook; \\ int n; \\ ret rv; \\ \\

if(!_hooks.link_##name) \\ return decline; \\ \\

pHook=(ns##_LINK_##name##_t *)_hooks.link_##name->elts; \\ for(n=0 ; n < _hooks.link_##name->nelts ; ++n) \\

{ \\

rv=pHook[n].pFunc args_use; \\ \\

if(rv != decline) \\ return rv; \\ } \\

return decline; \\ }

任何挂钩都必须且只能是三类型中的一种,任何挂钩在被执行之前都必须调用这三个宏中的一个进行声明。

- 34 -

本文来源:https://www.bwwdw.com/article/7bx3.html

Top