Update: I wrote a HOWTO guide on how to create an Apache HTTPd module years ago. This is an updated version of the same, that can be found in github.

Write your own httpd module could be interesting for a number of reasons:

  • understand how a webserver works;
  • understand Apache httpd;
  • speed up your current system by optimizing a specific bottleneck;
  • instrumentation;
  • fun?

Warming up

So, let’s jump into it. First step is to clone the HTTPd code and build it.

    $ git clone https://github.com/apache/httpd.git
    $ cd httpd
    $ git clone https://github.com/apache/apr.git srclib/apr
    $ ./buildconf
    $ mkdir mybuild
    $ cd mybuild
    $ CFLAGS="-O0 -ggdb" ../configure --enable-rewrite --enable-so --prefix=/home/ziviani/www
    $ make -j 5
    $ make install
    $ cd /home/ziviani/www
    $ ls
    bin  build  cgi-bin  conf  error  htdocs  icons  include  lib  logs  man  manual  modules

Check whether the server is working (I set it with high port to run it as a normal user):

    $ # still in /home/ziviani/www
    $ vi conf/httpd.conf
    ...
    LISTEN 9898
    ...
    ServerName localhost:9898
    ...
    
    $ bin/apachectl -k start

apache initial works page

    $ bin/apachectl -k stop

My Module

Now we have the httpd source code that compiles and runs, let’s create our first module.

    $ bin/apxs -g -n my_fast_server
    Creating [DIR]  my_fast_server
    Creating [FILE] my_fast_server/Makefile
    Creating [FILE] my_fast_server/modules.mk
    Creating [FILE] my_fast_server/mod_my_fast_server.c
    Creating [FILE] my_fast_server/.deps
    
    $ vim my_fast_server/mod_my_fast_server.c

Easy thing, huh? apxs just created a template (thanks to -g option) for us. The instructions in the comment section explains how to build and configure it.

 1    /*
 2    **  mod_my_fast_server.c -- Apache sample my_fast_server module
 3    **  [Autogenerated via ``apxs -n my_fast_server -g'']
 4    **
 5    **  To play with this sample module first compile it into a
 6    **  DSO file and install it into Apache's modules directory 
 7    **  by running:
 8    **
 9    **    $ apxs -c -i mod_my_fast_server.c
10    **
11    **  Then activate it in Apache's httpd.conf file for instance
12    **  for the URL /my_fast_server in as follows:
13    **
14    **    #   httpd.conf
15    **    LoadModule my_fast_server_module modules/mod_my_fast_server.so
16    **    <Location /my_fast_server>
17    **    SetHandler my_fast_server
18    **    </Location>
19    **
20    **  Then after restarting Apache via
21    **
22    **    $ apachectl restart
23    **
24    **  you immediately can request the URL /my_fast_server and watch for the
25    **  output of this module. This can be achieved for instance via:
26    **
27    **    $ lynx -mime_header http://localhost/my_fast_server 
28    **
29    **  The output should be similar to the following one:
30    **
31    **    HTTP/1.1 200 OK
32    **    Date: Tue, 31 Mar 1998 14:42:22 GMT
33    **    Server: Apache/1.3.4 (Unix)
34    **    Connection: close
35    **    Content-Type: text/html
36    **
37    **    The sample page from mod_my_fast_server.c
38    */
39    #include "httpd.h"
40    #include "http_config.h"
41    #include "http_protocol.h"
42    #include "ap_config.h"
43    
44    /* The sample content handler */
45    static int my_fast_server_handler(request_rec *r)
46    {
47        if (strcmp(r->handler, "my_fast_server")) {
48            return DECLINED;
49        }
50        r->content_type = "text/html";      
51    
52        if (!r->header_only)
53            ap_rputs("The sample page from mod_my_fast_server.c\n", r);
54        return OK;
55    }
56    
57    static void my_fast_server_register_hooks(apr_pool_t *p)
58    {
59        ap_hook_handler(my_fast_server_handler, NULL, NULL, APR_HOOK_MIDDLE);
60    }
61    
62    /* Dispatch list for API hooks */
63    module AP_MODULE_DECLARE_DATA my_fast_server_module = {
64        STANDARD20_MODULE_STUFF, 
65        NULL,                  /* create per-dir    config structures */
66        NULL,                  /* merge  per-dir    config structures */
67        NULL,                  /* create per-server config structures */
68        NULL,                  /* merge  per-server config structures */
69        NULL,                  /* table of config file commands       */
70        my_fast_server_register_hooks  /* register hooks              */
71    };

I made some changes in mod_my_fast_server.c. Nothing big, just make it prints browser’s query string.

 1    #include "httpd.h"
 2    #include "http_config.h"
 3    #include "http_protocol.h"
 4    #include "ap_config.h"
 5    
 6    #define MAX_HANDLER 4
 7    
 8    typedef int (*method_handler)(request_rec *r);
 9    
10    // HTTP Get method handler
11    static int get_handler(request_rec *r);
12    
13    // HTTP Post method handler
14    static int post_handler(request_rec *r);
15    
16    // HTTP Put method handler
17    static int put_handler(request_rec *r);
18    
19    // HTTP Delete method handler
20    static int delete_handler(request_rec *r);
21    
22    /* The sample content handler */
23    static int my_fast_server_handler(request_rec *r)
24    {
25        if (strcmp(r->handler, "my_fast_server")) {
26            return DECLINED;
27        }
28        r->content_type = "text/html";      
29    
30        // as per httpd.h r->method_number gives a numeric representation of http
31        // method: 0 - get, 1 - put, 2 - post, 3 - delete, etc
32        method_handler methods[MAX_HANDLER] = {&get_handler, &put_handler,
33            &post_handler, &delete_handler};
34    
35        if (r->method_number >= MAX_HANDLER || r->method_number < 0) {
36            return DECLINED;
37        }
38    
39        // call the handler function
40        return methods[r->method_number](r);
41    }
42    
43    static int get_handler(request_rec *r)
44    {
45        apr_status_t rv;
46        int i = 0;
47        int n = 0;
48        char* query = r->args; // query string
49    
50        // mime type send to the called
51        r->content_type = "text/html";
52    
53        // return OK if only header requested or no argument
54        if (r->header_only || r->args == 0) {
55            return OK;
56        }
57    
58        ap_rprintf(r, "<h1>[GET] Your query string: %s</h1>", query);
59    
60        return OK;
61    }
62    
63    // Post http handler
64    static int post_handler(request_rec *r)
65    {
66        return OK;
67    }
68    
69    // Put http handler
70    static int put_handler(request_rec *r)
71    {
72        return OK;
73    }
74    
75    // Delete http handler
76    static int delete_handler(request_rec *r)
77    {
78        return OK;
79    }
80    
81    static void my_fast_server_register_hooks(apr_pool_t *p)
82    {
83        ap_hook_handler(my_fast_server_handler, NULL, NULL, APR_HOOK_MIDDLE);
84    }
85    
86    /* Dispatch list for API hooks */
87    module AP_MODULE_DECLARE_DATA my_fast_server_module = {
88        STANDARD20_MODULE_STUFF, 
89        NULL,                  /* create per-dir    config structures */
90        NULL,                  /* merge  per-dir    config structures */
91        NULL,                  /* create per-server config structures */
92        NULL,                  /* merge  per-server config structures */
93        NULL,                  /* table of config file commands       */
94        my_fast_server_register_hooks  /* register hooks              */
95    };

Now, compile it to build a DSO and install that DSO in the modules directory (apxs does that automatically):

    $ bin/apxs -c -i my_fast_server/mod_my_fast_server.c
    
    /home/ziviani/www/build/libtool --silent --mode=compile gcc ...
    ...
    ----------------------------------------------------------------------
    Libraries have been installed in:
       /home/ziviani/www/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 /home/ziviani/www/modules/mod_my_fast_server.so

And that’s all! You only need to configure httpd.conf, start the server and use it.

    $ vim conf/httpd.conf
    ...
    LoadModule my_fast_server_module modules/mod_my_fast_server.so
    <Location /my_fast_server>
        SetHandler my_fast_server
    </Location>
    ...
    
    $ bin/apachectl -k start

Open your browse and navigate it to http://localhost:9898/my_fast_server?test&query=bla to see the query string printed.

apache initial works page

Extra: static (no apxs) build

Suppose you don’t want to use apxs and want to build your module together with httpd itself.

I’ll reuse the same source file and add my module in the Apache httpd build system. The steps are following:

  • copy the my_fast_server folder into httpd main source folder;
  • add my_fast_server in httpd build system;
  • call buildconf again to re-generate autoconf;
  • build!

Example:

    $ cp -a my_fast_server ~/httpd/modules/
    $ cd ~/httpd
    
    $ vim my_fast_server/config.m4
    
    APACHE_MODPATH_INIT(my_fast_server)
    APACHE_MODULE(my_fast_server, My FAST server!, , , no)
    APACHE_MODPATH_FINISH
    
    $ vim my_fast_server/Makefile.in
    
    include $(top_srcdir)/build/special.mk
    
    $ ./buildconf
    
    $ cd mybuild
    $ ../configure --help
    ...
      --disable-version       determining httpd version in config files
      --enable-remoteip       translate header contents to an apparent client
                              remote_ip
      --enable-my-fast-server My FAST!! server  <===== Yay!!!
      --enable-proxy          Apache proxy module
      --enable-proxy-connect  Apache proxy CONNECT module. Requires
    ...
    
    $ CFLAGS="-O0 -ggdb" ../configure --enable-rewrite --enable-so --enable-my-fast-server --prefix=/home/ziviani/www
    $ make
    $ make install

If the build finished without any errors, you can test your new module. Go to the www folder, edit conf/httpd.conf and start the server.

    $ vim conf/httpd.conf
    ...
    LoadModule my_fast_server_module modules/mod_my_fast_server.so
    <Location /my_fast_server>
        SetHandler my_fast_server
    </Location>
    ...
    
    $ bin/apachectl -k start

Navigate it to http://localhost:9898/my_fast_server?test&query=bla to see the query string printed.

apache initial works page

References