一、大纲
1.Web服务器概述
2.Apache体系结构
3.配置文件管理
4.Apache模块化体系结构
5.多任务并发处理
6.网络连接
7.过滤器
8.存储段和存储段组
9.常用过滤器
二、摘录内容
2.1 一个有用的Web服务器通常会更复杂,它们还会包含以下一些额外功能:
a) 完整地实现HTTP/0.9、HTTP/1.0及HTTP/1.1协议支持。
b) 能够处理并发请求,支持多进程或多线程。
c) 提供开发接口,允许开发人员自行增加额外的功能。
d) 服务器安全机制的实现。
e) 动态内容生成,允许服务器通过CGI、脚本语言(比如perl)和服务器端(包含SSI)生成动态的Web页面。
f) 支持虚拟主机
g) 支持代理功能
h) 允许根据MIME类型协商选择合适的返回资源。
2.2 缓存
2.2.1 Expires
Expires字段声明了一个网页或URL地址不再被浏览器缓存的时间,一旦超过了这个时间,浏览器都应该联系原始服务器。
2.2.2 Cache-Control
2.2.3 Last-Modified
Last-Modified和Etag是条件请求(Conditional Request)相关的两个字段。如果一个缓存收到了针对一个页面的请求,它发送一个验证请求询问服务器页面是否已经更改,在HTTP头里面带上"ETag"和"If Modify Since"头。服务器根据这些信息判断是否更新了信息,如果没有更新,就返回HTTP 304(NotModify);如果更新了,就返回HTTP 200和更新的页面内容,并且携带更新的"ETag"和“LastModified”。
2.3 访问控制和安全
认证(Authentication)、授权(Authorization)及账户确认(Accounting)三者合起来称为AAA模块。
认证包括两种:基本认证和摘要认证。
对资源的访问可能限制在特定的域,浏览器所在机器的网络、地址或特定的用户及用户组。Apache通过解析全局的及局部配置文件来决定用户对特定资源的访问权限。如果管理员允许,Web内容提供者还可以通过本地配置文件.htaccess限制对他们的文档的访问。
一般来说,服务器端的脚本分为两类:嵌入在HTML中的脚本和完全独立生成HTML的脚本。
纯脚本生成HTML的例子最主要的就是CGI程序和Java Servlets两种。CGI程序通常是C、C++或Perl等程序,Java Servlets则是使用Java程序编写的。
2.4 Apache 2.0.45版本的目录结构
整个Apache核心功能包括请求处理、协议处理等功能,实现文件全部包含在server目录中,server目录中又包含一个称之为mpm的目录。在1.3版本中,Apache仅仅支持准备创建进程的一种并发方式,这是一直为人所诟病的地方。在2.0以上的版本中,Apache推出了多种进程并发模型,包括线程池、预创建等,这些都被称之为多进程处理模块(MPM)。它们之间既相互独立,又能相互替换。这些模块统统保存在mpm目录中,每一种并发模型对应一个.c文件。
另外一个重要的目录就是modules,顾名思义,其中保存着Apache中的所有模块。每个模块占有一个目录,比如AAA认证模块为aaa,代理模块名称为proxy。
include目录包含了Apache中必需的头文件,其中包含一些极其重要的头文件,比如http_main.h、http_protocol.h、http_request.h等。
srclib目录中包含了Apache开发和运行所需要的基础库,主要包括apr_util、apr和pcre。apr_util和apr属于APR基础库,pcre则主要是Perl兼容正则表达式库。
os目录中包含了各个操作系统中所特有的内容。
docs目录中包含了Apache提供的文档。
test目录中包含了很多APR库实用的测试函数。
2.5 Apache层次结构
整个Apache可以被划分为四个大的层次:可移植运行库层(APR)、Apache核心功能层(Apache CORE)、Apache可选功能层(Apache Optional Module)及Apache第三方支持库。
如上图所示,加上操作系统层,整个Apache可以被分隔为五层,各层次的功能如下:
2.5.1 操作系统支持层
Apache归根结底是建立在操作系统的普通的应用程序上的,因此,很多时候必须使用操作系统本身提供的底层功能,比如进程和线程、进程和线程间的通信、网络套接字通信、文件操作等。
2.5.2 可移植运行库层
APR的任务就是屏蔽底层的操作系统API细节,对于所有的操作系统,提供一个完全相同的函数接口,这样,Apache开发者就不必顾虑操作系统细节,而只要开发上层功能即可。
2.5.3 核心功能层
核心功能层主要包括两大部分:Apache核心程序和Apache核心模块。
Apache的核心程序主要用于实现Apache作为HTTP服务器的基本功能,包括:
a) 启动和停止Apache b)处理配置文件(config.c)
c)接受和处理HTTP连接 d)读取HTTP请求并对请求进行处理
e)处理HTTP协议
Apache中大部分模块都是可选择的,这些模块的缺失至多影响Apache功能的完整性,并不影响运行,比如mod_ssl、mod_alias等。但是有两个模块则是必需的,即mod_core和mod_so.前者负责处理配置文件中的大部分配置指令,并根据这些指令运行Apache,而后者则负责动态加载其余的模块,缺少了该模块,其余的模块就无法使用。这两个模块都必须静态 编译。
MPM,即多进程处理模块。尽管MPM也是可选择的,但它通常负责处理Apache中的并发模型,或者是Prefork,或者是线程池(ThreadPool),或者是Worker模型等。
Apache核心(第三层)主要有以下两个作用
2.5.4.1 基本的HTTP服务功能
2.5.4.2 Apache Module API
每一个请求都会被划分为多个不同的阶段去处理,Apache中称之为"挂钩"。每个模块如果要对某个阶段进行必要的处理,则它只要实现该挂钩即可。在每一阶段,模块可以通过返回OK或DECLINE来表示是否要完成该步骤的任务。
2.5.4 可选功能层。
2.5.5 第三方支持库
在Apache的一些模块中会使用到第三方的开发库,比如mod_ssl就使用了OpenSSL,mod_perl则使用了Perl开发库。
2.6 Apache核心功能层
2.6.1 核心与可选模块的关系
Apache核心与Apache模块之间的关系是调用和被调用的关系,所有的模块都直接与核心进行交互,如下图:
在处理的过程中,核心并不会去关心谁是核心模块,谁是可选模块,它们都是一视同仁地被调用。核心模块和非核心模块的唯一区别就是加载的时间不同。
2.6.2 核心组件
包括下面几大部分:
a) 配置文件组件(HTTP_CONFIG) b)进程并发处理组件(MPM)
c)连接处理组件(HTTP_CONNECTION) d)HTTP协议处理组件(HTTP_PROTOCOL)
e)HTTP请求处理组件(HTTP_REQUEST) f)HTTP核心组件(HTTP_CORE)
g)核心模块组件(MOD_CORE)
Apache核心组件之间的相互关系如下图描述:
2.6.2.1 main
main.c是整个Apache的入口,它内部包含了main()函数,该函数的功能包含下面几个部分:
a)命令行处理
b) 配置文件处理(读取配置文件时Apache启动后首要任务,函数中配置文件最终处理为配置树结构) c) 构建虚拟主机信息 (main函数将根据虚拟主机配置构建相应的虚拟主机,最终形成一系列的server_rec结构链表)
d)进入主循环(在主循环中,Apache所做的事情就是执行MPM模块。MPM将产生多个进程或多个线程侦听指定端口,并处理该端口上的连接。当MPM执行失败的时候,main函数才会执行下一次循环).
2.6.2.2 HTTP配置文件组件(HTTP_CONFIG)
HTTP_CONFIG组件主要位于http_config.h和config.c中,它的主要作用是对配置文件进行解析、处理和保存。
2.6.2.3 进程并发处理组件(MPM)
MPM组件主要位于mpm目录下的各个文件中,比如Prefork MPM对应的就是prefork.c。MPM负责为Apache系统提供可靠、稳定、高效的进程和线程的并发处理。任何时候,Apache中只能有一个MPM在运行,而且MPM必须在编译的时候指定,不允许动态加载。
2.6.2.4 HTTP连接处理组件(HTTP_CONNECTION)
HTTP_CONNECTION组件主要位于http_connection.h和connection.c中。该组件主要负责处理与HTTP连接相关的事情。
2.6.2.5 HTTP协议处理组件(HTTP_PROTOCOL)
HTTP_PROTOCOL组件主要位于http_protocol.h和http_protocol.c中,主要负责处理HTTP/11.0及HTTP/1.1协议的解析,比如解析http请求头、生成返回给客户端的响应包等。
2.6.2.6 HTTP请求处理组件(HTTP_REQUEST)
HTTP_REQUEST组件主要位于http_request.h、http_request.c及request.c三个文件中。与请求相关的函数全部定义在http_request.h中,函数实现则分散在两个.c中。
a)请求的总入口函数ap_process_request及请求终止函数
b)请求本身属性相关的操作
c)子请求相关的操作
组件中提供的与子请求相关的函数包括:
ap_sub_req_lookup_uri、ap_sub_req_lookup_file、ap_sub_req_lookup_dirent
ap_sub_req_method_uri、ap_run_sub_req、ap_destroy_sub_req
d)重定向请求操作
在Apache处理请求的过程中,任何时候,当前请求都可能被重定向,比如请求处理发生错误,或者请求被rewrite等。组件中提供的与重定向相关的函数包括以下几点:
ap_internal_redirect、ap_internal_redirect_handler、ap_internal_fast_redirect
e)请求挂钩声明
组件中另外一项重要的工作就是声明请求处理阶段所需要使用的各种挂钩,包括create_request、translate_name、map_to_storage、check_user_id等
f)请求相关的配置处理
2.6.2.7 HTTP核心组件(HTTP_CORE)
此HTTP_CORE模块位于文件http_core.h和http_core.c中,主要是将与HTTP协议相关的内容从原来的核心模块中提取出来的。在2.x的设计中,设计者已经开始考虑将Apache设计为一个通用的服务器框架,而不仅仅是一个HTTP Web服务器,以后它也可以支持FTP等协议。因此,有必要将http协议的处理从核心中剥离出来。
2.6.2.8 核心模块组件(MOD_CORE)
核心模块(MOD_CORE)由mod_core.h和core.c组成,该模块的主要任务就是对核心需要的指令进行比较,比如
2.6.2.9 其余模块
除了上面提及的组件之外,还有一些相对重要的组件,包括日志处理组件、虚拟主机处理组件,以及过滤器模块组件。日志处理组件就是进行日志记录,该组件包括http_log.h和log.c。虚拟主机处理组件则为Apache提供虚拟主机支持。过滤器模块组件则是更加重要的组件,它是HTTP数据在核心内部传输的重要机制。
2.7 Apache运行流程
Apache的运行流程可以细分为三个大的阶段:
a) Apache启动过程 b) 接受客户端连接,并处理该连接 c) 从连接中读取请求数据,处理客户端的一次请求
2.7.1 Apache启动过程
Apache的启动包括两个阶段:高权限启动阶段和低权限运行阶段。
2.7.1.1 初始化系统所需要的资源
在启动的最后阶段,Apache将通过调用ap_mpm_run函数将控制权交给MPM模块。只有MPM模块执行失败或执行完毕的时候,执行权才会从MPM返回到主程序中。MPM是Apache服务器和操作系统之间的接口,它的目的只有一个,就是充分利用操作系统的特性,对服务器的并发效率进行最大的优化。
一旦权限从Apache主程序交给MPM,MPM将生成一定数目的进程或线程,侦听指定的端口,并等待接受客户端的连接。一旦接收到客户端请求,Apache将进入连接和请求处理阶段。Apache在接受和处理来自客户端的连接时,会以普通用户的权限去处理,而不会采用系统级别的权限。
2.7.2 HTTP连接处理
对于连接处理,最主要的任务就是调用预先定义好的连接处理挂钩process_connection。任何模块如果要处理连接,都可以实现该挂钩。一旦某个模块实现了该挂钩,那么在连接处理的过程中它们将会被调用。
在连接处理的过程中,通过调用ap_read_request函数进入请求读取过程,然后调用ap_process_request对该请求进行处理。
2.7.3 请求报文读取
一旦接收到客户端的连接,连接数据将被读取出来,然后HTTP_PROTOCOL模块将开始对该报文进行解析,请求的解析包括三部分:
a) HTTP请求头,比如"GET index.html HTTP/1.1";
b) HTTP请求域,比如"Accept:*/*"等;
c) HTTP请求体,对于一些特殊的请求(比如POST方法),它一般会在报文体重保存数据。
所有输入处理过滤器组成输入过滤器链表,在请求报文从网络中读取之后,它就直接进入了过滤器链表中,然后每个过滤器对其进行处理,并将其传递给下一个过滤器,直到最后一个过滤器,如下图所示:
在所有的过滤器处理完毕后,我们得到的就是一个最终的处理后的请求报文。此时请求处理模块HTTP_REQUEST将对该请求做更进一步的处理。
2.7.4 请求处理
对于HTTP报文,Apache调用ap_process_request函数对请求进行实质的处理。Apache中的请求处理包括三个大的阶段:请求解析阶段、安全处理阶段、请求准备阶段。
2.7.4.1 请求解析阶段
a) 通常情况下,浏览器会自动转换请求 地址栏中的一些特殊字符,如空格转换为%和十六进制的组合就是"%20"(比如空格式"%20"),因此对于服务器而言,它就要将"%xx"格式的字符串重新还原为原来的字符串。整个URL的转义由函数ap_unescape_url()完成。
该阶段并不是一个必需的阶段,如果请求是一个代理请求或请求结构的parsed_uri.path没有赋值,那么该阶段将不会被处理。
b)从URL中剔除/../和/./字符
URL中所有的/../和/./字符串都在这一阶段调用ap_getparents()函数并被剔除,该阶段必需被执行。
c)首次读取URL相关配置信息
一旦第二步处理完毕后,只包含绝对路径的URL就生成完毕,此时Apache将调用ap_location_walk从配置系统中查找与该URL关联的配置信息。在请求处理的后续阶段中,比如用户授权验证,权限控制等都需要依赖于读取的配置信息。如果当前请求是一个内部重定向请求或子请求,那么该请求的配置信息可能有很大一部分或全部继承自父请求。
URL关联的配置信息需要读取两次,此处是第一次读取。完整的配置信息时两次读取信息的最终叠加。
d) URL名称转换(translate_name)
该阶段主要用于对URL进行转换。比如Alias指令用于将某个URL映射到另外一个特定的URL中;而mod_writer模块则用于对URL的完全重写。另外,代理请求可能需要在请求的URL之前添加上proxy:://。如果某个模块需要对URL进行修改,那么都可以在这一阶段实现。
e)map_to_storage
使用这个挂钩的原因在于如果所提供的数据不是动态生成的,而是位于磁盘上的文件,那么服务器可能要执行更加严格的安全检查。
f) 二次URL相关配置文件读取
2.8 主程序main
2.8.1 主程序概要
2.8.1.1 读取Apache的配置文件
2.8.1.2 检查启动Apache的指令行参数
2.8.1.3 虚拟主机的设置
main函数的内部函数调用如下图:
AP_MONCONTROL主要用于打开和关闭代码剖析。
apr_app_initialize(&argc, &argv, NULL);
任何一个应用程序如果要使用APR库进行二次开发,那么它首先要完成的任务是对APR库进行必要的初始化。Apache从某个角度而言,它仅仅也是APR库的普通的使用者,因此它也不能例外。apr_app_initialize负责对APR库进行必要的初始化工作,代码如下:
process=create_process(argc, argv);
pglobal=process->pool;
pconf = process->pconf;
ap_server_argv0 = process->short_name;
由于Apache通常是通过指令行进行启动的,因此指令行的相关信息(指令数目argc及指令行字符串argv)都非常重要。对Apache而言,指令信息不仅主程序要使用,而且在一些子进程程序中也要使用,以此这就存在指令行信息传递的问题。一种最简单的方法就是使用全局变量。不过引用全局变量不是一个好的办法,如果用得少还可以忍受,如果使用的地方很多,则在每一个使用的文件头部都需要使用"extern xxxx"。
Apache把所有的指令行相关的信息都包装在process_rec结构中,代码如下:
struct process_rec {
apr_pool_t *pool;
apr_pool_t *pconf;
int argc;
const char *const *argv;
const char * short_name;
};
short_name则是应用程序的缩略名称。pool和pconf则分别是全局内存池和配置相关内存池。一旦定义了该结构,就可以不使用全局变量。任何函数或过程如果需要对应的指令行信息,只需对process_rec进行初始化,然后直接传递给函数即可。
创建一个process_rec结构可通过create_process函数完成。
apr_pool_create(&pcommands, pglobal);
apr_pool_tag(pcommands, "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 *));
Apache中的指令分为两大类:指令行中的指令及配置文件中的指令。而对于指令行中的指令又包括两种:读取配置文件之前必须处理的指令和读取配置文件后必须处理的指令。前者是防止指令行中的指令被覆盖,而后者则相反。一旦命令行被解析完毕后,这两种特殊的指令将被保存起来,以便在合适的时候执行。如果不保存,这些指令将会丢失。保存可使用如下数组数据结构:
error = ap_setup_prelinked_modules(process);
if (error) {
ap_log_error(APLOG_MARK, APLOG_STARTUP|APLOG_EMERG, 0, NULL, "%s: %s", ap_server_argv0, error);
destroy_and_exit_process(process, 1);
}
ap_setup_prelinked_modules用于将所有预链接的模块加入到加载模块链表中。模块只有加入到加载模块链表中才能称之为活动模块,然后才能被Apache核心调用,否则该模块仍然处于非活动状态。
ap_run_rewrite_args(process); //主要是为MPM模块设置的它允许MPM对命令行中的传入参数进行重写。
apr_getopt_init(&opt, pcommands, process->argc, process->argv);
while((rv = apr_getopt(opt, AP_SERVER_BASEARGS, &c, &optarg)) == APR_SUCCESS) {
char **new;
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指令所覆盖。其默认值为/usr/local/apache2.
case 'D':
new = (char **)apr_array_push(ap_server_config_defines);
if (strcmp(optarg, "DUMP_VHOSTS") == 0)
configtestonly = 1;
if (strcmp(optarg, "DUMP_MODULES") == 0)
configtestonly = 1;
break;
-D parameter选项用于设置参数parameter,它配合配置文件中的
case 'e':
if (strcasecmp(optarg, "emerg") == 0) {
ap_default_loglevel=APLOG_EMERG;
}
...
break;
-e level选项咋服务器启动时,设置日志的LogLevel为level。
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 = "DEBUG";
break;
-X 选项指定当前Apache以调试模式运行。
case 'f':
confname = optarg;
break;
-f config选项在启动中使用config作为配置文件。如果config不以"/"开头,则它是相对于ServerRoot的路径。其默认值为conf/httpd.conf。
case 'v':
printf("Server version:%s\n", ap_get_server_version());
printf("Server built: %s\n", ap_get_server_built());
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_directives();
destroy_and_exit_process(process, 0);
-l 选项用于输出一个静态编译在服务器中的模块列表。它不会列出使用LoadModule指令动态加载的模块。
case 'L':
ap_show_directives();
destroy_and_exit_process(process, 0);
-L选项输出一个指令的列表,并包含了各指令的有效参数和使用区域。
case 't':
configtestonly = 1;
break;
-t 选项意味着仅对配置文件执行语法检查。程序在语法解析检查结束后立即退出,或者返回"0"(OK),或者返回非0的值(Error)。如果还指定了"-D DUMP_VHOSTS", 则会显示虚拟主机配置的详细信息。
case 'S':
configtestonly = 1;
new = (char **)apr_array_push(ap_server_config_defines);
*new = "DUMP_VHOSTS";
break;
-S显示从配置文件中读取并解析的设置结果(目前仅显示虚拟主机的设置)。
case 'M':
configtestonly = 1;
new = (char **)apr_array_push(ap_server_config_defines);
*new = "DUMP_MODULES";
break;
-M 输出一个已经启用的模块列表,包括静态编译在服务器中的模块和作为DSO动态加载的模块。
主程序中对于Window所需要的-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, "plog");
apr_pool_create(&ptemp, pconf);
apr_pool_tag(ptemp, "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) {
ap_run_test_config(pconf, server_conf);
ap_log_error(APLOG_MARK, APLOG_STARTUP, 0, NULL, "Syntax OK");
destroy_and_exit_process(process, 0);
}
}
在main.c中,配置文件在Apache启动或重启的时候总是会被读取两次,一次是在主循环执行之前被读取,正如上面代码中所读取的一样,另外以此是在主循环中被读取。之所以要读取两次,主要基于以下两个目的:
a) 预检查配置文件中可能出现的语法问题,确保在真正处理的时候配置文件是完整无误的。
b) 第一次读取文件会生成在第二次读取文件时所需要的字段。比如,当读取配置文件的时候会需要错误日志指令,但是它在存储段配置文件中,所以需要读取一次这个文件,发现错误日志的正确配置,然后就可以在第二次读取配置文件的时候启动服务器,代码如下:
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 our config failed, deal with that here.*/
if (rv != OK) {
destroy_and_exit_process(process, 1);
}
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);
整个配置稳健的完整读取过程分为三个部分:
a) 读取配置文件前的准备工作
b) 实际的配置文件读取
实际文件的读取通过ap_process_config_tree函数完成。
c) 读取配置文件后的处理
与pre_config挂钩对应的是post_config挂钩。该挂钩在每次读取配置文件之后被调用。挂钩是大多数模块执行服务器端初始化的地方。如果某个模块由于某种原因需要启动分离的进程,就应该在这个阶段来完成,代码如下:
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;
apr_pool_create(&ptemp, pconf);
apr_pool_tag(ptemp, "ptemp");
ap_server_root = def_server_root;
server_conf = ap_read_config(process, ptemp, confname, &ap_conftree);
if (!server_conf) {
destroy_and_exit_process(process, 1);
}
if (ap_run_pre_config(pconf, plog, ptemp) != OK) {
ap_log_error(APLOG_MARK, APLOG_STARTUP|APLOG_ERR, 0, NULL, "Pre-configuration failed");
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_hook_sort_all();
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, "Unable to open logs");
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, "Configuration Failed");
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;
}
准备就绪后,Apache就进入了主循环for(;;),循环中主进程所做的事情包括以下几点:
a) 挂钩注册
b) 二次配置文件读取
c) 导出注册所有的可选函数
d) ap_mpm_run
主进程最重要的任务就是对ap_mpm_run的调用。一旦调用ap_mpm_run,主进程将进入多进程并发处理模块。在该模块中将会启动多个进程或多个线程,然后侦听客户端的连接请求,一旦接收到请求,就继而进入请求处理模块。
如果ap_mpm_run在执行中发生错误,则返回1,否则返回0.
当ap_mpm_run退出的时候,整个主进程也就相应地执行结束了。
-f config选项在启动中使用config作为配置文件。如果config不以"/"开头,则它是相对于ServerRoot的路径。其默认值为conf/httpd.conf。
三、第3章
配置文件管理
3.1 Apache配置系统
在Apache中通过MPM模块来实现进程/线程的并发处理。在此过程中,可以通过指令来控制系统中允许同时运行的进程的数目及每个进程允许产生的线程的数目。前者由ServerLimit指令控制,后者则由ThreadsPerChild指令控制。比如:
ThreadsPerChild 25
ServerLimit 16
那么,Apache是什么时候读取这些指令的?它是怎么读取的?读取之后这些指令保存在哪儿?以什么方式保存的?这些指令最终是如何影响Apache的行为的?
从整体上描述Apache配置系统,它应该包含三个主要部分:
a) 配置文件。通常情况下,配置系统会指定一些固定的文件作为配置文件,比如目前最主要的配置文件就是httpd.conf。
b)配置指令。配置系统必须能够决定各个指令的含义,这样,配置系统才能够正确地对其进行解释和处理。配置正确的指令或默认的值,或者由管理员进行修改;而解释配置指令则由Apache的核心及各个模块来处理。
c)配置信息的保存和读取。
Apache在处理了配置信息之后,将这些信息按照一定的数据结构进行保存。在Apache的运行过程中,任何时候需要的配置信息可直接从内存中读取即可。
3.2 配置文件
3.2.1 配置文件类
在Apache 2.0中涉及的配置文件包括以下三种:
3.2.1.1 httpd.conf
通常,当服务器启动的时候,该文件被读取处理一次,同时在每次重新启动的时候又会被处理一次,因此对配置文件的任何修改都要等待到服务器重启后才能生效。
3.2.1.2 .htaccess
3.2.1.3 access.conf和srm.conf
access.conf用于配置服务器的访问权限,控制不同用户和计算机的访问限制。srm.conf是服务器的资源映射文件,告诉服务器各种文件的MIME类型,以及如何支持这些文件。
3.2.2 配置文件处理时机
3.2.2.1 在Apache中,我们将启动的命令行也会进行抽象,视为文件来处理。
3.2.2.2 当Apache启动初始化的时候,httpd.conf将第一次被读取,然后被处理。
3.2.2.3 在Apache的主循环中,每次启动或重启后,Apache都会重新读取配置文件httpd.conf。
3.2.2.4 当处理特定请求的时候,如果允许使用.htaccess,接收到请求后,它将读取.htaccess中的配置信息,然后与httpd.conf中的配置信息进行合并,并生成最终的针对请求的配置信息。
图3-3 演示了整个配置文件在Apache中所处的位置
不管是对配置文件还是命令行参数,Apache最终都一样对待。当Apache启动的时候,这些配置文件将被读取并被解析。Apache的运行将依赖于这些配置信息,不过任何时候配置信息的修改都不会立即生效。由于只有服务器刚启动的时候才会读取配置信息,因此修改后的配置信息只有当服务器重新启动的时候才会生效。经过处理之后,内部配置数据结构将被生成,然后这些数据结构将被保存到配置库中。
3.3 指令相关概念
3.3.1 指令概述
Apache的指令可以分为两种:简单指令及配置段指令。配置段指令都是被包含在"<...>"中的指令,比如
当进行指令处理的时候,Apache将逐行地读取这些配置指令,如果某行不是空行(即不匹配正则表达式"^[\t]*$"),同时也不是一个注释行(不匹配正则表达式"^[\t]*#.*$"),那么Apache将该行的第一个单词视为指令字,后面的单词全部算作参数。如果某行以"\"结尾,则下一行是上一行的继续。
3.3.2 指令参数
3.3.2.1 参数类型
一般指令名称后面可以跟一个或多个用空格分开的参数。如果参数中由空格,则必须用双引号括起来,用方括号"[]"括起来的是可选的参数。如果一个参数可以取多个值,则各个可能的值用"|"分开。使用可变参数个数的指令以"..."结尾,以示最后一个参数可以重复。
a) URL
b)URL-path
c)file-path
d)directory-path
e)filename
f)regex 正则表达式,是对文本匹配模式的描述。
g)extension
一般是指filename中最后一个"."号后面的部分。但是,Apache可以辨认多个文件后缀,如果filename含有多个".",则第一个"."后面由每个"."分隔开的部分都是此文件的后缀。比如filename,file.html.en有两个后缀.html和.en。zai Apache指令中指定extension时,可以有也可以没有前导的".",而且不区分大小写。
h)MIME-type
一种用一个主格式类型和一个副格式类型并用斜杠分隔的描述文件格式的方法,如text/html、img/jpeg等。
i) env-variable
3.3.3 指令上下文
配置文件中的各个指令的作用范围是不一样的,可以分为主配置、虚拟主机配置,局部指令,以及条件指令。默认情况下,配置文件中的指令是作用于整个服务器的,比如ServerRoot和TimeOut指令。有些指令只是针对某个特定的目录,文件或URL地址。还有一些指令我们称之为"条件指令",它们并不是针对某个目录,而是在特定的条件下才会产生效果,比如
3.3.3.1 文件系统容器
Options +Indexes
Order allow,deny
Deny from all
Order allow,deny
Deny from all
3.3.3.2 网络空间容器
下例中的配置会拒绝对任何以"/private"开头的URL路径的访问,比如:
http://yoursite.example.com/private、
http://yoursite.example.com/private123、
http://yoursite.example.com/private/dir/file.html等所有以"/private"开头的URL路径。
Order Allow,Deny
Deny from all
SetHandler server-status
下例使用非正则表达式的通配符来改变所有用户目录的配置:
Options Indexes
下例使用正则表达式一次性拒绝对多种图形文件的访问:
3.3.3.3 条件上下文
Apache中允许设定的某些指令在特定的条件下才产生效果。这三种上下文主要是指
Redirect / http://otherserver.example.com/
下例中,MimeMagicFiles指令仅当mod_mime_magic模块启用时才有效。
MimeMagicFile conf/magic
= 2.1>
#仅在版本高于2.1.0版的时候才生效
3.3.3.4 上下文嵌套关系
如上图,包含范围大小依次为
各种上下文在嵌套中必须遵循下面的嵌套规则。
a)
b)
c)
d)
3.3.3.5 上下文合并和继承
Apache中允许同一个指令在多个地方出现,但是这会导致一个问题,即到底以哪个指令的配置为最终结果呢?Apache中有两种处理方式:完全覆盖和继承合并。对于所有的配置段,各个配置段会按照特定的顺序依次生效,这种生效次序会对配置指令的处理结果产生重大的影响,生效的顺序依次为:
除了
而各个配置段相同指令之间的覆盖继承关系如下:
a)如果两个指令位于同一个层次,则后面的指令将覆盖前面的指令。
b)指令的作用域越小,则它的优先级别越高。
在下面这个更具体的例子中,无论在
Order deny,allow
Allow from all
#Woops! This section wiil have no effect
Order allow,deny
Allow from all
Deny from badguy.example.com
3.3.3.6 指令位置
Apache中提供了指令位置字段的概念来控制一个指令所允许出现的上下文位置。位置字段主要用于控制各个指令在配置文件中允许出现的位置,其中包括三种:顶层位置、目录区和虚拟主机区。如果服务器发现一个指令出现在不允许出现的地方,比如LoadModule只允许出现在顶层位置,但发现其在
对于指令位置字段,Apache中提供了下面几个控制选项:
#define OR_NONE 0
#define OR_LIMIT 1
#define OR_OPTIONS 2
#define OR_FILEINFO 4
#define OR_AUTHCFG 8
#define OR_INDEXES 16
#define OR_UNSET 32
#define ACCESS_CONF 64
#define RSRC_CONF 128
#define EXEC_ON_READ 256
#define OR_ALL (OR_LIMIT|OR_OPTIONS|OR_FILEINFO|OR_AUTHCFG|OR_INDEXES)
a) 普通配置文件位置选项
这三个标志通常在指定指令的时候就必须预先设置好,而不能通过配置文件进行配置。比如
AP_INIT_RAW_ARGS("
而另一个指令AllowOverride则只允许出现在
AP_INIT_RAW_ARGS("AllowOverride", set_override, NULL, ACCESS_CONF, "Controls what groups of directives can be configured by per-directory "
"config files"),
如果一个指令既可以出现在
AP_INIT_TAKE1("HostnameLookups", set_hostname_lookups, NULL, ACCESS_CONF|RSRC_CONF,
"\"on\" to enable, \"off\" to disable reverse DNS lookups, or \"double\" to "
"enable double-reverse DNS lookups"),
b) 目录级别位置选项
除了上面三个用于对配置文件进行控制的选项外,Apache中还允许控制目录及.htaccess文件中出现的指令,这些由AllowOverride控制。该指令仅允许存在于
(1) 如果AllowOverride指令被设置为None,那么.htaccess文件将被完全忽略。事实上,服务器根本不会读取.htaccess文件。
(2) 当此指令设置为All时,所有具有".htaccess"作用域的指令都允许出现在.htaccess文件中。
(3)除此之外,AllowOverride后的参数还允许是下面的指令类型:
AuthConfig、FileInfo、Indexes、Limit(允许使用控制主机访问的指令(Allow、Deny、Order)、Options[=Option,...]、OR_NONE、OR_LIMIT、OR_OPTIONS、OR_FILEINFO、OR_AUTHCFG、OR_INDEXS、OR_ALL、OR_UNSET
指令的位置记录在指令描述数据结构command_rec中,使用req_override进行记录,目前在配置文件中,对于
RSRC_CONF|OR_OPTIONS|OR_FILEINFO|OR_INDEXS
而在
ACCESS_CONF|OR_LIMIT|OR_OPTIONS|OR_FILEINFO|OR_AUTHCFG|OR_INDEXS
c) Options选项
在这些指令中,Options指令是一个特殊的指令,Options指令控制了在特定目录中将使用哪些服务器特性。option可以为None,在这种情况下,将不启用任何额外特性,或者设置为以下选项中的一个或多个。
All、ExecCGI(允许使用mod_cgi执行cgi脚本)、FollowSymLinks、Includes、IncludesNOEXEC、Indexes、MultiViews、SymLinkslfOwnerMatch
Apache中定义了一系列的选项宏与之对应如下:
#define OPT_NONE 0
#define OPT_INDEXES 1
#define OPT_INCLUDES 2
#define OPT_SYM_LINKS 4
#define OPT_EXECCGI 8
#define OPT_UNSET 16
#define OPT_INCNOEXEC 32
#define OPT_SYM_OWNER 64
#define OPT_MULTI 128
#define OPT_ALL (OPT_INDEXES|OPT_INCLUDES|OPT_SYM_LINKS|OPT_EXECCGI)
Options指令的处理由函数set_allow_opts完成,该函数定义在core.c中。
d) 上下文检查
在使用指定的过程中,一个重要的内容就是对指令的上下文进行检测,判断其是否在合法的范围内。指令的上下文信息都保存在数据结构cmd_parms中。不过最有用也是最简单的判断方法是通过函数ap_check_cmd_context,该函数定义在http_config.h中。
为了明确表示指令的作用上下文,Apache在http_config.h中定义了相关的常量来进行描述,代码如下:
#define NOT_IN_VIRTUALHOST 0x01 //当前的指令不允许出现在配置段中
#define NOT_IN_LIMIT 0x02
#define NOT_IN_DIRECTORY 0x04
#define NOT_IN_LOCATION 0x08
#define NOT_IN_FILES 0x10
#define NOT_IN_DIR_LOC_FILE (NOT_IN_DIRECTORY|NOT_IN_LOCATION|NOT_IN_FILES)
#define GLOBAL_ONLY (NOT_IN_VIRUTALHOST|NOT_IN_LIMIT|NOT_IN_DIR_LOC_FILE)
如果我们不允许某个指令出现在
static const char *my_conf(cmd_parms *cmd, void *cfg, ...)
{
const char *err_msg;
errmsg = ap_check_cmd_context(cmd, NOT_IN_VIRTUALHOST);
if (errmsg != NULL)
return errmsg;
/*OK, not in a ;go ahead and process the directive */
return NULL;
}
ap_check_cmd_context函数返回非空字符串,则意味着指令的出现位置发生了错误。我们来看一下Apache是如何检测指令的上下文的,代码如下:
AP_DECLARE(const char *)ap_check_cmd_context(cmd_parms *cmd, unsigned forbidden)
{
const char *gt = (cmd->cmd->name[0] == '<' && cmd->cmd->name[strlen(cmd->cmd->name)-1] != '>') ? ">" : "";
const ap_directive_t *found;
if ((forbiddern & NOT_IN_VIRTUALHOST) && cmd->server->is_virtual) {
return apr_pstrcat(cmd->pool, cmd->cmd->name, gt, "cannot occur within section", NULL);
}
AP_DECLARE(const char *) ap_check_cmd_context(cmd_parms *cmd, unsigned forbidden)
{
const char *gt = (cmd->cmd->name[0] == '<'
&& cmd->cmd->name[strlen(cmd->cmd->name)-1] != '>') ? ">" :"";
const ap_directive_t *found;
if ((forbidden & NOT_IN_VIRTUALHOST) && cmd->server->is_virtual) {
return apr_pstrcat(cmd->pool, cmd->cmd->name, gt, "cannot occur within section", NULL);
}
如果指令出现在
if ((forbidden & NOT_IN_LIMIT) && cmd->limited != -1) {
return apr_pstrcat(cmd->pool, cmd->cmd->name, gt, "cannot occur within section", NULL);
}
判断当前指令是否在
if ((forbidden & NOT_IN_DIR_LOC_FILE) == NOT_IN_DIR_LOC_FILE) {
if (cmd->path != NULL) {
return apr_pstrcat(cmd->pool, cmd->cmd->name, gt, "cannot occur within "
"section", NULL);
}
if (cmd->cmd->req_override & EXEC_ON_READ) {
/* EXEC_ON_READ must be NOT_IN_DIR_LOC_FILE, if not, it will
* (deliberately) segfault below in the individual tests ...
*/
return NULL;
}
}
判断当前指令是否位于
if (((forbidden & NOT_IN_DIRECTORY)
&& ((found = find_parent(cmd->directive, "directive, "directive, "directive, "directive, "pool, cmd->cmd->name, gt, "cannot occur within ", found->directive, "> section", NULL);
}
return NULL;
}
如果当前指令的上下文为NOT_IN_DIRECTORY、NOT_IN_LOCATION或NOT_IN_FILES,此时需要检查当前指令的所有父节点中是否为
3.3.4 指令参数类型
Apache中提供了12中类型的指令,这些类型是与实际的配置文件中指令处理相一致的。每种指令都大同小异,惟一的区别就在于其处理的参数的数目及在将指令传递给指令实现函数之前,服务器如何解释这些参数的方式。
Apache中对于指令类型的定义是通过枚举类型cmd_how来实现的,cmd_how定义如下:
enum cmd_how
{
RAW_ARGS,
TAKE1,
TAKE2,
ITERATE,
ITERATE2,
FLAG,
NO_ARGS,
TAKE12,
TAKE3,
TAKE23,
TAKE123,
TAKE13,
TAKE_ARGV
};
对于各种指令,服务器的处理方法如下:
a) TAKE_ARGV 这种类型的处理函数会将指令参数作为argc/argv的格式进行处理。
const char *(*take_argv) (cmd_parms *parms, void *mconfig, int argc, char *const argv[]);
b)RAW_ARGS
const char *func(cmd_parms *parms, void *mconfig, char *args);
c) TAKE1
const char *func(cmd_parms* parms, void *mconfig, const char *first);
d) ITERATE
该类型指令属于迭代类型。这种指令允许传入多个参数,不过一次只能处理一个,服务器必须遍历处理它们。每次遍历处理的过程又与TAKE1类型执行相同。
const char *func(cmd_parms* parms, void *mconfig, const char *first);
e)TAKE2, TAKE12
const char *two_args_func(cmd_parms *parms, void *mconfig, const char *first, const char *second);
f) ITERATE2
const char *(*take2) (cmd_parms *parms, void *mconfig, const char *w, const char *w2);
g) TAKE3, TAKE23,TAKE13,TAKE123
const char *three_args_func(cmd_parms *parms, void *mconfig, const char *first, const char *second, const char *third);
h) NO_ARGS
const char *no_args_func(cmd_parms *parms, void *mconfig);
i) FLAG
const char *flag_args_func(cmd_parms* parms, void *mconfig, int flag);
不管是什么指令,其对应的处理函数都是以两个参数开始的:cmd_parms *parms和void *mconfig。cmd_parms结构用来存储处理配置指令时所需要的辅助内容。当处理任何配置信息文件的时候,该结构都将被创建。Apache核心通过它将各种需要的参数传递给处理函数。另一个参数void *mconfig表示针对指令位置的配置记录,基于所遇到的指令位置的不同,该配置记录可以是服务器配置记录,也可以是目录配置记录。
----------------------------------------------------------------------------------------------------------
1.command_rec结构定义:
/*
*The command record structure,Each modules can define a table of these
*to define the directives it will implement.
*/
typedef struct command_struct command_rec;
struct command_struct
{
/* Name of this command */
const char *name;
/* The function to be called when this directive is parsed */
cmd_func func;
/* Extra data, for functions which implement multiple commands... */
void *cmd_data;
/* What overrides need to be allowed to enable this command. */
int req_override;
/* What the command expects as arguments*/
enum cmd_how args_how;
/* 'usage' message, in case of syntax errors */
const char *errmsg;
};
2. Apache中定义了12种指令,为此Apache中也定义了相应的配置处理指令。Apache中定义的12个处理宏定义如下:
3.Apache总是从读取配置文件如httpd.conf开始的。所有的指令读取后最终要保存到内存中。在内存中使用结构ap_directive_t记录各个指令,该数据结构定义在文件util_cfgtree.h中:
struct ap_directive_t {
const char *directive; //标识该指令的名称
const char *args; //该指令所对应的参数,如果参数超过一个,它们之间使用空格隔开。
struct ap_directive_t *next;
struct ap_directive_t *first_child;
struct ap_directive_t *parent;
void *data;
const char *filename; //用于记录指令所在的文件。
int line_num;
};
4.整个Apache配置文件处理的概要流程
5.Apache的完整配置文件处理过程
6.配置文件读取
抽象配置文件在Apache中使用ap_configfile_t结构进行描述,该结构定义在httpd_config.h中:
typedef struct ap_configfile_t ap_configfile_t;
struct ap_configfile_t
{
int (*getch)(void *param); //从抽象配置文件中读取一个字符的方法
void *(*getstr)(void *buf, size_t bufsiz, void *param); //从抽象配置文件中读取一个字符串
int (*close)(void *param); //关闭抽象配置文件
void *param; //指针函数需要的参数
const char *name; //对于真正的配置文件就是文件名称
unsigned line_number; //当前正在处理的文件行数
};
7. ap_build_config是整个配置处理的核心,调用ap_cfg_getline不停地读取文件中的每一行,然后调用ap_build_config_sub将其解析为ap_directive_t结构,并将其插入到配置树的合适位置。
ap_build_config_sub成为了配置文件处理的核心。
8.关于模块化体系结构部分,参考文章:https://blog.csdn.net/zhangge3663/article/details/83343838
9.关于多任务并发处理部分,参考文章:https://blog.csdn.net/zhangge3663/article/details/83343937
10.关于网络连接部分,参考文章:https://blog.csdn.net/zhangge3663/article/details/83344029
11.关于过滤器部分,参考文章:https://blog.csdn.net/zhangge3663/article/details/83344078
12.关于存储段和存储段组,参考文章:https://blog.csdn.net/zhangge3663/article/details/83344115
13.关于常用过滤器部分,参考文章:https://blog.csdn.net/zhangge3663/article/details/83344147