Apache源代码分析——命令表解析
tingya | 18 一月, 2005 22:46该文章主要对Apache中的命令表进行了介绍和分析
/////////////////////////////////////////////////////////////
//Apache源代码分析——命令表解析
//张中庆于西安交通大学软件所
//tingya$stu,xjtu,edu,cn,将$换成@,,换成.防止地址被收集
//转载请保留出处
//最初出自西安交通大学兵马俑Linux版
//////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////
命令行参数处理
如果用户是通过命令行进行Apache启动,那么启动语法如下:
Httpd [-d directory][-D parameter] [-f file] [-C directive] [-c directive] [-L] [-l] [-S] [-V] [-X]
其中,-d命令用来设置ServerRoot,即服务器的根目录。-D用来定义<IiDefine>的参数值,即预先定义一些变量。-f用来设置配置文件的路径,正常情况下,我们使用httpd.conf,不过通过-f选项,我们可以进行修改。-L用来列出当前可用的所有的命令,并退出。-l选项用来列出Apache中编译的模块并退出。-S用来显示虚拟主机的相关信息。-v用来显示Apache的版本号以及Apache编译的时间,将其打印出来,同时退出。-V显示编译设置,并退出。-X用来将Apache设置成为单进程模式。这种模式通常用来进行调试使用。
在这些命令中与配置命令相关的选项有三个:-f,-C和-c。由于命令行的命令和配置文件中命令的设置可能会相互覆盖,因此我们有必要考虑到这种覆盖可能。-C和-c就是处理这种情况。-C列出了各种指令,这些指令必须在读入配置文件之前进行处理,而-c列出的命令则必须在读入配置文件之后进行处理。Apache中给出了两个ap_array_header_t类型的数组ap_server_pre_read_config和ap_server_post_read_config分别记录-C和-c之后所需要处理的命令。为了处理命令行参数,Apache中定义了apr_getopt_t结构来保存 ,apr_getopt_t结构如下:
struct apr_getopt_t {
apr_pool_t *cont;
apr_getopt_err_fn_t *errfn;
void *errarg;
int ind;
int opt;
int reset;
int argc;
const char **argv;
char const* place;
int interleave;
int skip_start;
int skip_end;
};
errfn则是函数在发生错误的时候调用来打印错误消息。如果errfn为NULL,则表明不是输出错误信息。errarg则是用户定义的传给错误消息的第一个参数。
Ind标记该选项在父选项中的索引。如果lnd与argc即参数的总个数相同,则表明已经到了处理的末尾。该选项应该是最后一个选项。
argc和argv与通常的含义相同,分别表示参数的个数和参数字符串。
Place通常用来记录与选项关联的一些参数。
Apache对ap_getopt_t结构的初始化是从apr_getopt_init开始的。一旦初始化完毕,Apache将不断调用apr_getopt对命令行进行解析,同时根据命令进行相应的设置。
对命令行参数进行处理的函数大多数都集中在getopt.c文件中。
///////////////////////////////////////////////////////////////////////////////////////
APR_DECLARE(apr_status_t) apr_getopt_init(apr_getopt_t **os, apr_pool_t *cont,
int argc, const char *const *argv)
该函数主要对apr_getopt()中需要使用的ap_getopt_t结构进行初始化。os是apr_getopt函数中需要使用的ap_getopt_t结构。cont则是初始化过程中需要使用的内存池。argc和argv则是参数个数和参数字符串,其通常来源于main(argc,argv)中的相应变量。
///////////////////////////////////////////////////////////////////////////////////////
APR_DECLARE(apr_status_t) apr_getopt(apr_getopt_t *os, const char *opts,
char *optch, const char **optarg)
apr_getopt是命令行参数解析的核心函数。os则是前面使用apr_getopt_init初始化的结构,一旦进行初始化,main函数中的argc和argv都保存到了os中的argc和argv成员中。opts是应用程序能够接受的可选项的字符串。Optch则是下一个需要解析的字符串,
函数会返回四个值,同时退出。返回值及其含义如下所示:
APR_EOF -- 没有更多的选项进行解析
APR_BADCH -- 发现一个错误的选项字符
APR_BADARG -- 该选项参数后面没有参数
APR_SUCCESS -- 下一个选项被发现
函数流程:
///////////////////////////////////////////////////////////////////////////////////////
Apache配置指令
///////////////////////////////////////////////////////////////////////////////////////
Apache中关于配置指令最重要的数据结构就是指令表了,指令表的结构为command_rec,其定义如下:
typedef struct command_struct command_rec;
struct command_struct {
const char *name;
cmd_func func;
void *cmd_data;
int req_override;
enum cmd_how args_how;
const char *errmsg;
};
command_rec结构描述了与处理命令相关的各个信息,与ap_directive_t不同,其通常位于模块内部,由模块使用。name给出了命令的名称,其值与ap_directive_t中的directive相同;func则是每个模块中用来处理该命令的方法,其是cmd_func类型,Apache中定义为typedef const char *(*cmd_func) ();由于cmd_func不带有任何参数,因此如果需要传入适当的参数的话只能通过cmd_data进行。
为了能够阐述req_override和args_how的具体含义,我们还需要阐述一些相关的概念。
1. 指令类型
Apache中提供了12种类型的指令,这些类型是与实际的配置文件中指令处理相一致的。每种指令都大同小异,唯一的区别就在于其处理的参数的数目以及在将指令传递给指令实现函数之前,服务器如何解释这些参数的方式。由于各个指令的参数不相同,为此也导致了指令的处理函数的格式不相同。
apache中对于指令类型的定义是通过枚举类型cmd_how来实现的,cmd_how定义如下:
enum cmd_how {
RAW_ARGS,
TAKE1,
TAKE2,
ITERATE,
ITERATE2,
FLAG,
NO_ARGS,
TAKE12,
TAKE3,
TAKE23,
TAKE123,
TAKE13
};
对于所有的指令处理函数,其都将返回字符串。如果指令处理函数正确的处理了指令,那么函数返回NULL,否则应该返回错误提示信息。对于各种指令,服务器的处理方法如下:
RAW_ARGS
该指令会通知apache不要对传入的参数做任何的处理,只需要原封不动的传递个指令处理函数即可。使用这种指令会存在一定的风险,因为服务器不做任何的语法检查,因此难免会有错误成为“漏网之鱼”。
这种指令的处理函数通常如下所示:
const char * func(cmd_parms* parms , void* mconfig,char* args);
args只是简单的命令行内容,当然也包括指令在内。
TAKE1
顾名思义,这种类型的指令“Take 1 argument”,其只允许传入一个参数。 这种指令的处理函数通常如下所示:
const char * func(cmd_parms* parms , void* mconfig,const char* first);
ITERATE
该类型指令属于迭代类型。这种指令允许传入多个参数,不过一次只能处理一个,服务器必须遍历处理它们。每次遍历处理的过程又与TAKE1类型指令相同。因此这种指令的处理函数与TAKE1指令相同。
TAKE2,TAKE12,ITERATE2
TAKE2类型必须向指令处理函数传入两个参数;而TAKE12可以接受一个或者两个参数。ITERATE2与ITERATE1类似,都属于参数迭代处理类型,不过ITERATE2要求至少传递两个参数给函数。不过,第二个参数能够使用多次,服务器会遍历它们,直到所有的参数都传递给处理函数。如果只向TAKE12传递一个参数,那么服务器将把第二个参数设置为NULL。这三种类型的指令处理函数原型如下:
const char *two_args_func(cmd_parms* parms , void* mconfig,
const char* first,const char* second);
TAKE3,TAKE23,TAKE13,TAKE123
这组指令最多都可以接受3个参数,如果参数超过三个,则处理函数将会报错。TAKE3意味着参数必须是三个;TAKE23则意味着至少两个参数,也可以为三个,不能少于两个或者多于三个。TAKE13则意味这可以接受一个参数或者三个参数,除此之外都是非法。TAKE123意味着可以接受一个,两个或者三个参数。这四种指令的处理函数原型如下:
const char *three_args_func(cmd_parms* parms , void* mconfig,
const char* first,const cha