撰文一个httpd module

撰写一个httpd module
撰写一个httpd module


本文简单介绍如何给httpd 2.4写一个module。首先得保证httpd和httpd的开发库被正确安装:

[weli@localhost work]$ yum list -q httpd httpd-devel
Available Packages
httpd.x86_64                                                 2.4.6-31.el7                                            rhel-7-server-htb-rpms
httpd-devel.x86_64                                           2.4.6-31.el7                                            rhel-7-server-htb-rpms
[weli@localhost work]$ 


安装httpd-devel的原因是因为我们给httpd 2.4写module时,需要调用httpd相关的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`,并会在运行时通过`foo_hooks`中调用`ap_hook_handler`将我们的逻辑函数`foo_handler`注册进httpd。

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

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


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

使用apxs来编译httpd modules


httpd自己提供了一个很方便的模块编译工具叫做apxs,我们可以用它来编译所写的`module_foo`:

httpd/sbin/apxs 
Usage: apxs -g [-S <var>=<val>] -n <modname>
       apxs -q [-v] [-S <var>=<val>] [<query> ...]
       apxs -c [-S <var>=<val>] [-o <dsofile>] [-D <name>[=<value>]]
               [-I <incdir>] [-L <libdir>] [-l <libname>] [-Wc,<flags>]
               [-Wl,<flags>] [-p] <files> ...
       apxs -i [-S <var>=<val>] [-a] [-A] [-n <modname>] <dsofile> ...
       apxs -e [-S <var>=<val>] [-a] [-A] [-n <modname>] <dsofile> ...


以下是使用apxs编译`module_foo.c`的过程:

[weli@localhost work]$ sudo ../sbin/apxs -i -a -c module_foo.c
[sudo] password for weli: 
httpd/lib/build/libtool --silent --mode=compile gcc -std=gnu99 -prefer-pic -O2 -g -pipe -Wall -Wp,-D_FORTIFY_SOURCE=2 -fexceptions -fstack-protector-strong --param=ssp-buffer-size=4 -grecord-gcc-switches -m64 -mtune=generic  -DLINUX -D_REENTRANT -D_GNU_SOURCE -pthread -Ihttpd/include    -c -o module_foo.lo module_foo.c && touch module_foo.slo
httpd/lib/build/libtool --silent --mode=link gcc -std=gnu99 -Wl,-z,relro,-z,now   -o module_foo.la  -rpath httpd/modules -module -avoid-version    module_foo.lo
httpd/lib/build/instdso.sh SH_LIBTOOL='httpd/lib/build/libtool' module_foo.la httpd/modules
httpd/lib/build/libtool --mode=install install module_foo.la httpd/modules/
libtool: install: install .libs/module_foo.so httpd/modules/module_foo.so
libtool: install: install .libs/module_foo.lai httpd/modules/module_foo.la
libtool: install: install .libs/module_foo.a httpd/modules/module_foo.a
libtool: install: chmod 644 httpd/modules/module_foo.a
libtool: install: ranlib httpd/modules/module_foo.a
libtool: finish: PATH="/sbin:/bin:/usr/sbin:/usr/bin:/sbin" ldconfig -n httpd/modules
----------------------------------------------------------------------
Libraries have been installed in:
   httpd/modules

If you ever happen to want to link against installed libraries
in a given directory, LIBDIR, you must either use libtool, and
specify the full pathname of the library, or use the `-LLIBDIR'
flag during linking and do at least one of the following:
   - add LIBDIR to the `LD_LIBRARY_PATH' environment variable
     during execution
   - add LIBDIR to the `LD_RUN_PATH' environment variable
     during linking
   - use the `-Wl,-rpath -Wl,LIBDIR' linker flag
   - have your system administrator add LIBDIR to `/etc/ld.so.conf'

See any operating system documentation about shared libraries for
more information, such as the ld(1) and ld.so(8) manual pages.
----------------------------------------------------------------------
chmod 755 httpd/modules/module_foo.so
[activating module `foo' in httpd/conf/httpd.conf]


可以看到apxs帮我们生成了复杂的libtool编译命令,生成了`module_foo.so`:

[weli@localhost httpd]$ ls modules/*foo*
modules/module_foo.so


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

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


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

使用`module_foo`


还记得我们在`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的加载及配置方式。进一步的学习可以参考httpd的这篇写得很棒的{官方文档}。