撰写一个HTTPD Module

(这是一篇旧文,整理到这里)

在这篇文章里,为大家介绍,如何为Apache HTTPD撰写一个module。我使用的是Fedora Linux,因此可以把Fedora Linux里面给提供的httpdhttpd-devel两个包安装好。

安装httpd-devel的原因是因为我们给HTTPD写module时,需要调用相关的header文件。接下来我们写一个简单的模块:

// module_foo.c
#include <stdio.h>
#include "apr_hash.h"
#include "ap_config.h"
#include "ap_provider.h"
#include "httpd.h"
#include "http_core.h"
#include "http_config.h"
#include "http_log.h"
#include "http_protocol.h"
#include "http_request.h"


static int foo_handler(request_rec *r) {
  if (!r->handler || strcmp(r->handler, "foo_handler")) return (DECLINED);

  ap_set_content_type(r, "text/html");
  ap_rprintf(r, "Hello, martian!");

  return OK;
}

static void foo_hooks(apr_pool_t *pool) {
  ap_hook_handler(foo_handler, NULL, NULL, APR_HOOK_MIDDLE);
}

module AP_MODULE_DECLARE_DATA foo_module = {
  STANDARD20_MODULE_STUFF,
  NULL,
  NULL,
  NULL,
  NULL,
  NULL,
  foo_hooks
};

这个模块通过AP_MODULE_DECLARE_DATA来注册一个foo_module

module AP_MODULE_DECLARE_DATA foo_module = {

并会在运行时通过foo_hooks中调用ap_hook_handler将我们的逻辑函数foo_handler注册进httpd:

static void foo_hooks(apr_pool_t *pool) {
  ap_hook_handler(foo_handler, NULL, NULL, APR_HOOK_MIDDLE);
}

我们的foo_handler功能非常简单,并不处理用户请求request_rec,只是先判断在httpd.conf中模块是否设置为{foo_handler}。判断完成后,这个模块会直接返回HTML数据:

  ap_set_content_type(r, "text/html");
  ap_rprintf(r, "Hello, martian!");

理解了这个module的作用以后,接下来是编译这个module。

HTTPD自己提供了一个很方便的module compiler叫做apxs,我们可以用它来编译所写的foo_module

$ apxs -a -c foo_module.c

然后编译过程输出如下:

/usr/lib64/apr-1/build/libtool --silent --mode=compile gcc -prefer-pic -O2 -g -pipe -Wall -Werror=format-security -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -specs=/usr/lib/rpm/redhat/redhat-hardened-cc1 -m64 -mtune=generic  -DLINUX -D_REENTRANT -D_GNU_SOURCE -pthread -I/usr/include/httpd  -I/usr/include/apr-1   -I/usr/include/apr-1   -c -o foo_module.lo foo_module.c && touch foo_module.slo
/usr/lib64/apr-1/build/libtool --silent --mode=link gcc -Wl,-z,relro,-z,now   -o foo_module.la  -rpath /usr/lib64/httpd/modules -module -avoid-version    foo_module.lo

可以看到apxs展开成了复杂的libtool编译命令,编译后生成了很多library文件:

$ ls
foo_module.c  foo_module.la  foo_module.lo  foo_module.o  foo_module.slo

此外还有很多文件在隐藏目录.libs里面:

$ ls -l ./.libs/
total 104
-rw-rw-r--. 1 weli weli 35580 Jan 27 02:55 foo_module.a
lrwxrwxrwx. 1 weli weli    16 Jan 27 02:55 foo_module.la -> ../foo_module.la
-rw-rw-r--. 1 weli weli   938 Jan 27 02:55 foo_module.lai
-rw-rw-r--. 1 weli weli 35432 Jan 27 02:55 foo_module.o
-rwxrwxr-x. 1 weli weli 25560 Jan 27 02:55 foo_module.so

接下来我们可以把这个编译好的module安装进httpd的标准位置,我们可以使用apxs -i命令来完成:

$ sudo apxs -i -a foo_module.la

此外还帮我们在httpd中加载好了这个module:

[weli@localhost httpd]$ grep 'foo' conf/httpd.conf
LoadModule foo_module modules/module_foo.so

加载了这个模块后,我们来使用它。

还记得我们在module_foo.c中写的:

static int foo_handler(request_rec *r) {
  if (!r->handler || strcmp(r->handler, "foo_handler")) return (DECLINED);
...

因此,在httpd.conf的最后添加一行配置:

<Location /foo>
 SetHandler foo_handler
</Location>

这样,当用户请求/foo这个位置时,由foo_handler负责处理请求并返回。可以重启一下httpd服务然后访问这个url来验证:

$ curl http://localhost/foo
Hello, martian!

可以看到我们的module已经工作了。

小结

在本文中,简单介绍了httpd module的开发流程,以及module的加载及配置方式。