当前位置

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 的不同之处融合在一起。例子:

  1. #if MODULE_MAGIC_COOKIE == 0x41503230UL || MODULE_MAGIC_COOKIE == 0x41503232UL
  2.  
  3. #include "util_filter.h"
  4. #include "apr_strings.h"
  5. module AP_MODULE_DECLARE_DATA foobar_module;
  6. #define APLOG_FOOBAR APLOG_ERR,0
  7.  
  8. #else
  9.  
  10. module MODULE_VAR_EXPORT foobar_module;
  11. #define apr_pool_t          pool
  12. #define apr_table_t         table
  13. #define apr_pcalloc         ap_pcalloc
  14. #define apr_table_unset     ap_table_unset
  15. #define apr_table_set       ap_table_set
  16. #define apr_table_get       ap_table_get
  17. #define apr_table_make      ap_make_table
  18. #define apr_pstrdup         ap_pstrdup
  19. #define apr_snprintf        ap_snprintf
  20. #define apr_pstrndup        ap_pstrndup
  21. #define APLOG_FOOBAR APLOG_ERR
  22.  
  23. #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 状态下呢?我的方案是:

  1. /* 小技巧,用于帮助 init_module 只执行一次检查 */
  2. #define MAX_FDS 1024
  3. int daemon_flag(int fds[MAX_FDS])
  4. {
  5.     int fd;
  6.     int newfd;
  7.     int i;
  8.     int opt;
  9.     socklen_t optlen = sizeof(int);
  10.     int ret = 0;
  11.  
  12.     memset(fds, 0, sizeof(int) * MAX_FDS);
  13.     newfd = fd = socket(AF_INET,SOCK_STREAM,0);
  14.     while (newfd < MAX_FDS && newfd > 0) {
  15.         fds[newfd] = 1;
  16.         newfd = dup(fd);
  17.     }
  18.     for (i = 0; i < MAX_FDS; i++) {
  19.         if (fds[i] == 1) {
  20.             close(i);
  21.         } else {
  22.             if (0 == getsockopt(i, SOL_SOCKET, SO_REUSEADDR, &opt, &optlen)) {
  23.                 fds[i] = -1; //这样返回以后也知道哪些 fd 被监听
  24.                 ret = 1;
  25.             }
  26.         }
  27.     }
  28.     return ret;
  29. }

因为我知道 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 库从这里下载并安装

评论

不知道你用什么工具做apache的进程debug追踪
我用Emacs的gdb追踪效果不是很好。

不好意思,我写的模块实在是简单,没有经过调试就看起来运行的很好了。

请问你怎么样解决的NO OPENSSL_APPLINK的问题的,可以说详细点吗?或者给我EMAIL: ylemei@21cn.com
不甚感激!!

就是把 PEM_read_RSA_PUBKEY 改成 PEM_read_bio_RSA_PUBKEY 等函数就好了