在Apache系列之1Apache框架介绍中,为大家介绍了Apache的五层结构以及模块化设计,Apache本身只包括第三层核心功能层,核心功能层只提供HTTP服务的基本功能和对模块的扩展,那么如果你需要实现自己的功能就需要开发Apache模块,这一篇我们来介绍Apache模块。

1. 什么是模块?

Apache模块是指一些具有一定相对独立功能的函数。如果要扩展Apache功能,必须自行编写Apache模块或者使用现有的Apache模块。模块既可以在编译的时候被静态链接进去,也可以在Apache编译完成之后动态加载(LoadModule配置指令)。

2. 模块包括什么?

  • 描述模块本身的数据结构
  • 挂钩注册函数
  • 模块配置数据结构:针对各个目录及各个服务器的配置信息
  • 指令表:当前模块能够处理的指令及相应的处理程序
  • 可选函数
  • 过滤器相关处理

说起来比较抽象,我们来看一个具体模块mod_so.c(负责动态加载模块的模块)的结构,如下所示

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 */};

一个模块最重要的包括两个部分:指令表和挂钩函数,是我们的重点,来详细介绍。

3. Apache Module 指令表

Apache在启动阶段会读取配置文件,对于配置文件中的指令进行执行,而这些指令对应的指令函数由模块完成,例如LoadModule这个指令由mod_so模块中下面的函数 执行

static const char *load_module(cmd_parms *cmd, void *dummy,const char *modname, const char *filename)

每个模块都把自己能够处理的指令放在指令表中,供Apache核心去调用,例如mod_offline模块,指令表为:

static const command_rec offline_cmds[] ={    AP_INIT_TAKE1("OfflinePktNum", offline_set_ptk_num, NULL, RSRC_CONF, "pkt num ceiling in a message queue"),    AP_INIT_TAKE1("OfflineMemLimit", offline_set_mem_limit, NULL, RSRC_CONF, "self mem pool ceiling"),    AP_INIT_TAKE1("OfflineMaxRecvLen", offline_set_max_recv_len, NULL, RSRC_CONF, "sum of the pkts size in 1 direction"),    AP_INIT_TAKE1("OfflineLogLevel", offline_set_log_level, NULL, RSRC_CONF, "log level"),    AP_INIT_TAKE1("OfflineLogLimit", offline_set_log_limit, NULL, RSRC_CONF, "log file size ceiling"),    AP_INIT_TAKE1("OfflineRecvTimeOut", offline_set_recv_timeout, NULL, RSRC_CONF, "time to wait for recv response very time"),    AP_INIT_TAKE1("OfflineEthName", offline_set_ethname, NULL, RSRC_CONF, "which eth should be monitored"),    AP_INIT_TAKE1("OfflineLogName", offline_set_log_name, NULL, RSRC_CONF, "where log should be putted"),    AP_INIT_TAKE1("OfflineEngine", offline_set_engine, NULL, RSRC_CONF, "engine on or off"),    {NULL}};

这里能够处理的指令例如OfflinePktNum,调用的操作为offline_set_ptk_num函数,具体AP_INIT_TAKE1这个宏展开是什么样子大家可以自己研究一下,而这个部分我会放在Apache 启动解析部分去讲

在Apache启动阶段加载模块的时候,将创建一个全局的哈希表ap_config_hash,key是指令字符,value是一个链表,表示所有处理这条指令的函数,如下图所示指令哈希表.png

在Apache读取每一条指令进行操作的时候,就会从全局哈希表中获取到ap_mod_list节点构成的一条链,然后遍历这条链表执行指令处理函数。

4. 挂钩

4.1. 挂钩是什么?

Apache对HTTP的请求可以分为连接、处理和断开连接三个阶段;从小的方面而言,每个阶段又可以分为更多的子阶段,比如对HTTP的请求,我们可以进一步划分为客户身份验证、客户全县认证、URL重定向等,每一个阶段调用响应的函数进行处理,这些子阶段称为挂钩。Apache中对请求的处理过程实际上就是一次调用一系列挂钩的过程。

挂钩分布在不同的模块中,每一个模块有可能包含多个挂钩

挂钩函数就是每个挂钩对应的操作函数,对同一个挂钩,各个模块可以自行设计针对他的处理函数

4.2. 挂钩的声明?

首先Apache本身就声明了很多的挂钩,挂钩的声明总是位于模块内部,声明遵循谁第一个使用谁声明的原则。挂钩的声明通过这个宏的方式,参数包括hook名称,参数以及hook返回值类型

#define AP_DECLARE_HOOK(ret,name,args) \ APR_DECLARE_EXTERNAL_HOOK(ap,AP,ret,name,args)

而这个宏展开之后是

DECLARE_HOOK.png

我们用post_config这个挂钩来举例,通过AP_DECLARE_HOOK(int,post_config,args)声明展开之后得到的结果是:

typedef int ap_HOOK_post_config_t(apr_pool_t *pconf, apr_pool_t *plog, apr_pool_t * ptemp, server_rec *s);AP_DECLARE(void) ap_hook_post_config(ap_HOOK_post_config *pf,const char * const * aszPre,const char * const *aszSucc, int nOrder);
AP_DECLARE(ret) ap_run_post_config args;
#define APR_IMPLEMENT_HOOK_GET_PROTO(ap,AP,name)
AP_DECLARE(apr_array_header_t *) ap_hook_get_post_config(void)
typedef struct ap_LINK_post_config_t
{
      ap_HOOK_post_config_t * pFunc;
      const char * szName;
      const char * const * aszPredecessors;
      const char * const * aszSuccessors;
      int nOrder;
}ap_LINK_post_config_t;

可以看出包含了五个部分分别是:

  • 定义挂钩的执行函数原型
  • 定义挂钩注册函数的原型
  • 定义挂钩的调用函数
  • 定义获取挂钩访问函数原型,调用该函数即可得到注册为该挂钩的所有函数
  • 保存挂钩的相关定义信息

4.3. 挂钩数组

对于每一个挂钩,可能在多个模块中都有挂钩处理函数,为了能够保存各个模块中对同一挂钩的实用信息,声明一个数组来保存对应于某个挂钩的所有挂钩函数,link_name数组中每个元素的类型都是ap_LINK_name,关于挂钩的大部分信息都由这个结构提供挂钩数组的声明为这个宏

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

例如config.c文件中声明了如下的挂钩函数

APR_HOOK_STRUCT(           APR_HOOK_LINK(header_parser)           APR_HOOK_LINK(pre_config)           APR_HOOK_LINK(clear_handler)               APR_HOOK_LINK(post_config)           APR_HOOK_LINK(open_logs)           APR_HOOK_LINK(child_init)           APR_HOOK_LINK(handler)           APR_HOOK_LINK(quick_handler)           APR_HOOK_LINK(optional_fn_retrieve)           APR_HOOK_LINK(test_config))

展开之后就形成了

挂钩数组.png

这个结构保留了每个挂钩对应的挂钩函数,Apache中对挂钩数组的访问都必须通过_hook进行

4.4. 挂钩函数注册

每个模块要实现相应的挂钩处理函数,然后通过宏来关联起来,实际上就是通过上面的_hook把自己的挂钩处理函数放入链表中,例如mod_offline模块中的挂钩声明如下

static void offline_register_hooks(apr_pool_t *p){    ap_hook_open_logs(offline_open_logs, NULL, NULL, APR_HOOK_BLOODY_FIRST);    ap_hook_handler(offline_handler, NULL, NULL, APR_HOOK_MIDDLE);}

4.5. 总结

没错,模块的基本内容就讲完了,但是还有很多的细节没有讲,例如挂钩函数的排序问题,挂钩函数执行的终止问题等等,这个在实际开发中都会碰到,但具体就不讲了。

我们总结一下,挂钩就是Apache处理HTTP请求中的一个阶段,而开发的模块为了能够在这个阶段参与处理,就需要完成这个挂钩函数,Apache核心会自动调用完成的挂钩函数,这就是模块开发所做的内容

现有的挂钩都包括什么呢?

阶段挂钩名称挂钩函数功能
服务器启动阶段pre_config修改配置树
post_config用于模块启动分离的进程
open_logs安全打开日志文件的位置
child_init子进程初始化,当子进程被创建,该挂钩对应的处理函数将被立即调用
连接阶段挂钩create_connection完成对连接属性的相关设置
pre_connection接受连接之后,从客户端读取数据之前被调用
process_connection专门为协议模块设计,目的是允许协议模块处理请求,不仅要读请求,还要生成所有的相应数据
Keep-Alive循环中的挂钩create_request从服务器上读取请求的时候调用,如果模块要填充某些会影响服务器读取请求方式的结构字段,或者必须要为各个请求分配内存的时候,就需要使用
post_read_request读取请求的时候,一旦对HTTP请求报文头进行分析后,该挂钩就会立即被调用,让模块在有机会进行实际的操作、处理请求之前修改请求
处理请求阶段translate_name修改URI
map_to_storage让模块确定特定的资源是否可以在磁盘上被找到
header_parser让模块有机会再请求处理的早期查看http请求头
access_checker让模块根据特定的条件限制对资源的访问
fixups处理器阶段之前所调用的最后挂钩,让模块向客户发送响应之前确定响应头
insert_filter过滤器是模块将另外的模块生成的响应发送给客户之前,对其进行修改的方式

登录发表评论 注册

反馈意见