Apache Module 开发后记
开发出 apache 2.0 的模块以后,又面对着要将其移植到 apache 其他版本的需求,经过这段时间一点点的修补,现在我的模块已经可以同时在 1.3/2.0/2.2 下编译。甚至在 2.0/Win32 环境下也编译出了 dll,供在个人PC上做开发的同事使用。
我感觉如果项目不复杂的话,可以学习我这样把所有的内容放在一个文件里面的做法。
最重要的就是利用 MODULE_MAGIC_COOKIE 的定义,把 1.3 和 2.0/2.2 的不同之处融合在一起。例子:
#if MODULE_MAGIC_COOKIE == 0x41503230UL || MODULE_MAGIC_COOKIE == 0x41503232UL
#include "util_filter.h"
#include "apr_strings.h"
module AP_MODULE_DECLARE_DATA foobar_module;
#define APLOG_FOOBAR APLOG_ERR,0
#else
module MODULE_VAR_EXPORT foobar_module;
#define apr_pool_t pool
#define apr_table_t table
#define apr_pcalloc ap_pcalloc
#define apr_table_unset ap_table_unset
#define apr_table_set ap_table_set
#define apr_table_get ap_table_get
#define apr_table_make ap_make_table
#define apr_pstrdup ap_pstrdup
#define apr_snprintf ap_snprintf
#define apr_pstrndup ap_pstrndup
#define APLOG_FOOBAR APLOG_ERR
#endif /* MODULE_MAGIC_COOKIE == 0x41503230UL */
同样,模块初始化的部分也这样针对不同版本定义一下。
这样主要的功能函数就可以使用同样的代码模块,并使用 apr_* 系列函数族了。
之所以要对 APLOG_ERR 做定义,是因为在 1.3 和 2.x 版本中,ap_log_error 和 ap_log_rerror 所使用的参数数目不一致,2.x 的参数要多一个,因此针对 2.x自动增加一个参数——",0"
2.0 和 2.2 有些地方也有小差别,我的代码里面就碰到了 apr_socket_create 的参数不一样。同样简单 #if 就可以处理了。
在 1.3 里面 module initializer 如果 2.x 里面的 post_config 会运行两次,而且2.x 上的这个小技巧无法直接使用了。本来是不影响程序运行的,但还是想出了一个变态的办法来解决它。module initializer 两次执行之间的一个重要事件就是 apache 的 daemonize。那样怎么来判断当前进程是否在 daemon 状态下呢?我的方案是:
/* 小技巧,用于帮助 init_module 只执行一次检查 */
#define MAX_FDS 1024
int daemon_flag(int fds[MAX_FDS])
{
int fd;
int newfd;
int i;
int opt;
socklen_t optlen = sizeof(int);
int ret = 0;
memset(fds, 0, sizeof(int) * MAX_FDS);
newfd = fd = socket(AF_INET,SOCK_STREAM,0);
while (newfd < MAX_FDS && newfd > 0) {
fds[newfd] = 1;
newfd = dup(fd);
}
for (i = 0; i < MAX_FDS; i++) {
if (fds[i] == 1) {
close(i);
} else {
if (0 == getsockopt(i, SOL_SOCKET, SO_REUSEADDR, &opt, &optlen)) {
fds[i] = -1; //这样返回以后也知道哪些 fd 被监听
ret = 1;
}
}
}
return ret;
}
因为我知道 apache 必然会对监听套接字设置 SO_REUSEADDR,所以可如此判断。
由于 apache2 有了 apr 的支持,几乎不用修改任何代码模块就可以在 win32 下编译。而且只需要 MS 的免费工具就可以了,包括 MSVC C++ Toolkit 2003 和 Platform SDK。设置好 INCLUDE/LIB 路径后,只需要执行
cl /MD /D "WIN32" /c mod_foobar.c
link /DLL mod_foobar.obj libhttpd.lib libapr.lib
这样就得到了可被 LoadModule 的 mod_foobar.dll
win32 下我碰到的问题稍微麻烦一些。启动 apache 无法成功,报告什么 OPENSSL_Applink 错误。看了 openssl 的 FAQ,说什么要 include 一个 applink.c,但仍然无济于事。不过查看 applink.c 后发现,它似乎和 IO 库相关;最后我把以前使用的 stdio 替换成了 openssl 自己的 bio 函数族,该问题就消失了。
openssl 的 win32 库从这里下载并安装