In this short introduction to Apache's C API we will create a few basic modules you will be able to extend to fit your need or just give you a taste of what you can do with it.
Basic knowledge of the C programming language is needed to be able to use this introduction, but examples are kept very down to earth and might be understandable if you know another programming language.
The few common tasks we create a module for are:
Before we start to get down to the hot stuff and write our first modules we will take a look at what actually makes up a module in Apache. We will take a look at what is required and which functions the different headerfiles allows you to use.
To be able to write a module for Apache you need to include some headerfiles from the Apache source tree that defines the C API to Apache. For our first simple examples we will only need two headerfiles:
#include "httpd.h" #include "http_config.h"
httpd.h will include defines, structures, and common functions for all operations related to the Apache server. Including, but not limited to, http status codes, table functions, and much more.
http_config.h includes definitions for the module slot of hooks into Apache. Functions for configuration structures are also exported from this module. We will be taking a look at them in a little while.
From http_config.h we get the structure module which we need to use to get our module hooked into the different phases of a request. In the same file a helping definition for the first 6 of the slots is defined, namely STANDARD_MODULE_STUFF, which fills out some important parts of the module struct. Among others it fills in the API major version, API minor version, and the module magic cookie.
To see how the structures look like, you will need to look into apache-1.3.x/src/include/http_config.h. The following code snippet would not hook any of the functions defined within the file into any of the phases, so the module would basically not do anything when loaded.
module MODULE_VAR_EXPORT name_module = {
STANDARD_MODULE_STUFF,
NULL, /* initializer */
NULL, /* dir config creator */
NULL, /* dir config merger */
NULL, /* server config creator */
NULL, /* server config merger */
NULL, /* command table */
NULL, /* handlers */
NULL, /* filename translation */
NULL, /* check_user_id */
NULL, /* check auth */
NULL, /* check access */
NULL, /* type_checker */
NULL, /* fixups */
NULL, /* logger */
NULL, /* header parser */
NULL, /* child_init */
NULL, /* child_exit */
NULL /* post read-request */
};
What all the different comments (phases) means will be explained in the next section, Request phases
The different request phases are described in an order which seems most appropriate, which means it's not in the order that the module struct expects them. For every phase the prototype for the function and the return values are showed.
If you hook a function into this phase it would be run when the server is initialized. Functions that need to be started in this phase is usualy one that sets a part of the outgoing Server header.
static void modulename_init(server_rec *s, pool *p);
Return value: None.
If you're looking for a place to stick something that will be persistant across the life of the child, like putting initial values into variables, this is the place to do so. You cannot get access to any configuration options in this phase, so initializing persistant connections to databases is not something you would do here unless you want to hardcode configuration of the connections.
static void modulename_child_init(server_rec *s, pool *p);
Return value: None.
This function is called when one of the Apache childs exit, so it is perfect to shutdown persistant connections from.
static void modulename_child_exit(server_rec *s, pool *p);
Return value: None.
This phase should initialize a server config structure if any is used.
static void *modulename_create_server_config(pool *p, server_rec *s);
Return value: pointer to the created module-specific structure.
This phase creates a merged server config structure of the two supplied server config structures. The function may not modify any of it's arguments.
static void *modulename_merge_server_config(pool *p, void *server1_conf, void *server2_conf);
Return value: pointer to the created module-specific structure containing the merged values.
This phase creates a directory config structure if any is used. It is used to keep configuration that is specific to a directory.
static void *modulename_create_dir_config(pool *p, char *dirspec);
Return value: pointer to the created directory-specific structure.
This phase creates a merged directory config structure of the two supplied directory config structures. The function may not modify any of it's arguments.
static void *modulename_merge_dir_config(pool *p, void *parent_conf, void *newloc_conf);
Return value: pointer to the created directory-specific structure containing the merged values.
This phase allows you to put a hook in right after where the request has been read from the client but before any other phases have been initiated.
static int modulename_post_read_request(request_rec *r);
Return value: OK, DECLINED, or HTTP_mumble. If OK is returned no other handlers are called for this phase.
If you need to work with any of the headers and influence any of the later phases this is the place to register your calls.
static int modulename_header_parser(request_rec *r);
Return value: OK, DECLINED, or HTTP_mumble. If OK is returned the remaining modules that have handlers registered for this phase will still be called.
This phase is where the translation from an URI to a filename happens.
static int modulename_translate_filename(request_rec *r);
Return value: OK, DECLINED, or HTTP_mumble. Returning OK will stop other handlers from handling this phase.
Check authentication information sent, username, password.
static int modulename_check_user_id(request_rec *r);
Return value: OK, DECLINED, or HTTP_mumble error, typically HTTP_UNAUTHORIZED. If OK is returned no other handlers for this phase are checked.
This phase is used to check wheter the resource the client is trying to access is protected by some kind of authorization.
static int modulename_check_auth(request_rec *r);
Return value: OK, DECLINED, or HTTP_mumble. If OK is returned no other handlers for this phase are checked. If all registered handlers for this phase returns DECLINED the server aborts the request with a server error.
This phase is used to place module-specific restrictions on access to a resource..
static int modulename_check_access(request_rec *r);
Return value: OK, DECLINED, or HTTP_mumble. All handlers will be called regardless if the others return either OK or DECLINED. Any other return value causes the request to be aborted.
This phase checks the mime type of the request and sets the content_type acordingly.
static int modulename_check_type(request_rec *r);
Return value: OK, DECLINED, or HTTP_mumble. If OK is returned no other handlers for this phase are checked.
Last minute fixing of header fields are preformed in this phase, which is invoked just before any content handlers are called.
static int modulename_fixup(request_rec *r);
Return value: OK, DECLINED, or HTTP_mumble. Even if OK is returned the other registered handlers for this phase are called.
If you are interested in logging any of the actions performed in the request you want to hook your handlers into this phase. Return values are OK, DECLINED, HTTP_mumble and if you return OK all other handlers that are registered for this phase will still be called.
static int modulename_logger(request_rec *r);
Return value: OK, DECLINED, or HTTP_mumble. All handlers will be called for this phase regardless of wheter we return OK here.
This is the phase where the module serves any content or does whatever it needs to do. This type of handler doesn't go into the handler slot in the module structure. A wrapper structure called handler_rec is used for storing each content handler. All the content handlers are put into an array of handler_rec structures and then put into the module structure where all the other handlers go.
static int modulename_handler(request_rec *r);
Return value: OK, DECLINED, or HTTP_mumble. OK means everything went okay. DECLINED means we don't want to serve this request. Everything else is reported as an error.
handler_rec structureThe handler_rec structure is defined in http_config.h. It looks like this:
typedef struct {
const char *content_type; /* MUST be all lower case */
int (*handler) (request_rec *);
} handler_rec;
content_type is what you use to hook the content handler into a location from inside httpd.conf. handler is the function you want called from inside your module. So we end up with this for our module:
static const handler_rec modulename_handlers[] = {
{"modulename-handler", modulename_handler},
{NULL}
};
It will now be possible to enabled this with the use of SetHandler modulename-handler in httpd.conf
command_rec structureThe command_rec structure is likewise defined in http_config.h. It looks like this:
typedef struct command_struct {
const char *name; /* Name of this command */
const char *(*func) (); /* Function invoked */
void *cmd_data; /* Extra data, for functions which
* implement multiple commands...
*/
int req_override; /* What overrides need to be allowed to
* enable this command.
*/
enum cmd_how args_how; /* What the command expects as arguments */
const char *errmsg; /* 'usage' message, in case of syntax errors */
} command_rec;
It has 6 slots that needs to be filled out with information about the command you want to create.
So far the different phases have been pretty straight forward to manipulate, but the two remaining phases are a bit trickier.
Command table: TAKE123 etc..
yada
module MODULE_VAR_EXPORT name_module = {
STANDARD_MODULE_STUFF,
modulename_init, /* initializer */
modulename_create_dir_config, /* dir config creator */
modulename_merge_dir_config, /* dir config merger */
modulename_create_server_config, /* server config creator */
modulename_merge_server_config, /* server config merger */
modulename_handlers, /* command table */
modulename_, /* handlers */
modulename_translate_filename, /* filename translation */
modulename_check_user_id, /* check_user_id */
modulename_check_auth, /* check auth */
modulename_check_access, /* check access */
modulename_check_type, /* type_checker */
modulename_fixup, /* fixups */
modulename_logger, /* logger */
modulename_header_parser, /* header parser */
modulename_child_init, /* child_init */
modulename_child_exit, /* child_exit */
modulename_post_read_request /* post read-request */
};
request_rec object
Before you can use the module created it needs to be compiled. There are two ways of doing this. One is compiling it directly into Apache, the other is making a dynamic loadable module. We'll go over both methods here.
Since it's pretty well documented how to compile Apache we wont go into details on this here, but we will just show you what you need to do to get your module recognized and included by Apache:
$ cp /path/to/mod_my.c src/modules/extra/
$ ./configure [your normal options go here] --activate-module=src/modules/extra/mod_my.c \
--enable-module=my
$ make
The task of compiling the module into the server is pretty trivial, so building scripts to do this is not a bad idea.
It's easy compiling your modules with apxs, which by the way means APache eXtenSion tool. You might want to read the man page of apxs to learn all of it's features. To use this feature you will need to have your Apache with the module mod_so compiled in. We will just cover the basics of compiling a simple module:
$ apxs -Wall -c -o mod_my.so mod_my.c
That is all there is to it. Now you just need to load the module in the Apache config like this:
LoadModule my_module path/to/mod_my.so
Again, the task of compiling should be automated to be less error-prone. I created a Makefile for that purpose:
# Generic Makefile for GNU make (gmake) #APXS=$(shell which apxs) # or hardcode to path of apxs APXS=/home/thomas/build/apache-dev/bin/apxs SOURCES:=$(wildcard mod_*.c) OBJS=$(foreach SOURCE,$(SOURCES),$(subst .c,.so,$(SOURCE))) INST_OBJS=$(foreach SOURCE,$(SOURCES),$(subst .c,,$(SOURCE))) CFLAGS:=`$(APXS) -q CFLAGS` modules: $(OBJS) @echo make modules finished, type make install to install modules install: $(INST_OBJS) @echo installed modules clean: rm -rf *.so *.o *~ $(OBJS): $(APXS) -c -S CFLAGS="$(CFLAGS) -g" -Wall -Wc -o $@ $(subst .so,.c,$@) $(INST_OBJS): $(OBJS) $(APXS) -i -n $@ $@.so
This Makefile will try to guess the path to apxs. If apxs is in your path you will be safe, otherwise you will need to edit the APXS line of the Makefile to fith your install. The Makefile will automatically find the module(s) in the directory if you stick to the simple convention of mod_*.c, which it will compile into mod_*.so. It was designed to work with GNU make, so you will have to edit it to make it work with a regular make or install GNU make.
Writing modules for Apache has been somewhat of a mixed affair for me. It's been fun and I've learned a lot, but a lot of times I've spent hours wondering what was wrong with my module. Apache would just segfault and not give a clue to what was wrong. This is where debuggers come to the rescue.
First of all we need to recompile our Apache httpd binary with the added debugging flags. This is done by defining CFLAGS=-g
$ ls
apache_1.3.20.tar.gz
$ tar -zxf apache_1.3.20.tar.gz
$ ls -F
apache_1.3.20/ apache_1.3.20.tar.gz
$ cd apache_1.3.20/
$ CFLAGS="-g" ./configure --prefix=/home/thomas/build/apache-dev \
--enable-module=so
[output of configure]
$ make
$ make install
Now you need to edit the configuration file httpd.conf to fit your needs or just use this one to test out your modules:
ServerType standalone
ServerRoot "/home/thomas/build/apache-dev"
ServerName localhost
PidFile /home/thomas/build/apache-dev/logs/httpd.pid
ScoreBoardFile /home/thomas/build/apache-dev/logs/httpd.scoreboard
Timeout 300
Port 8080
User thomas
Group thomas
ServerAdmin thomas@localhost
DocumentRoot "/home/thomas/build/apache-dev/htdocs"
<Directory />
Options FollowSymLinks
AllowOverride None
</Directory>
<Directory "/home/thomas/build/apache-dev/htdocs">
Options Indexes FollowSymLinks MultiViews
AllowOverride None
Order allow,deny
Allow from all
</Directory>
<IfModule mod_dir.c>
DirectoryIndex index.html
</IfModule>
AccessFileName .htaccess
<Files ~ "^\.ht">
Order allow,deny
Deny from all
</Files>
ErrorLog /home/thomas/build/apache-dev/logs/error_log
LogLevel warn
LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
CustomLog /home/thomas/build/apache-dev/logs/access_log combined
<IfDefine HEADER>
LoadModule my_header_module libexec/mod_my_header.so
</IfDefine>
<IfDefine SERVER>
LoadModule my_server_module libexec/mod_my_server.so
</IfDefine>
<IfDefine ADVANCED_HEADER>
LoadModule my_advanced_header_module libexec/mod_my_advanced_header.so
MyAdvancedHeader On
MyAdvancedHeader_message "this is a test-header"
</IfDefine>
If you use the configuration file supplied here you will be able to start your server in single-process mode like this:
$ /home/thomas/build/apache-dev/bin/httpd -X -DHEADER # This will load mod_my_header.so $ /home/thomas/build/apache-dev/bin/httpd -X -DSERVER # This will load mod_my_server.so $ /home/thomas/build/apache-dev/bin/httpd -X -DADVANCED_HEADER # This will load mod_my_advanced_header.so
As you can see it is pretty easy and it is just as easy to load it up in your debugger, which the next section is all about.
The most widely used debugger under *NIX environment is gdb, also called The GNU Debugger.
I created a .gdbinit file to make the debugging easier. Please note that there might be a bug in the dump_tables define.
define httpd
run -X -f /home/thomas/build/apache-dev/conf/httpd.conf
end
define dump_table
set $t = (table_entry *)((array_header *)$arg0)->elts
set $n = ((array_header *)$arg0)->nelts
set $i = 0
while $i < $n
printf "[%u] '%s'='%s'\n", $i, $t[$i].key, $t[$i].val
set $i = $i + 1
end
end
document dump_table
Print the key/value pairs in a table.
end
define dump_string_array
set $a = (char **)((array_header *)$arg0)->elts
set $n = (int)((array_header *)$arg0)->nelts
set $i = 0
while $i < $n
printf "[%u] '%s'\n", $i, $a[$i]
set $i = $i + 1
end
end
document dump_string_array
Print all of the elements in an array of strings.
end
define dump_request
set $r = $arg0
printf "[request_rec dump]\n"
if $r->the_request != 0x0
printf "r->the_request = '%s'\n", $r->the_request
end
printf "r->proxyreq = %u\n", $r->proxyreq
printf "r->header_only = %u\n", $r->header_only
if $r->protocol != 0x0
printf "r->protocol = '%s'\n", $r->protocol
end
printf "r->proto_num = %u\n", $r->proto_num
if $r->hostname != 0x0
printf "r->hostname = '%s'\n", $r->hostname
end
printf "r->request_time = %u\n", $r->request_time
if $r->status_line != 0x0
printf "r->startus_line = '%s'\n", $r->status_line
end
printf "r->status = %u\n", $r->status
if $r->method != 0x0
printf "r->method = '%s'\n", $r->method
end
printf "r->method_number = %u\n", $r->method_number
printf "r->allowed = %u\n", $r->allowed
printf "r->sent_bodyct = %u\n", $r->sent_bodyct
printf "r->bytes_sent = %u\n", $r->bytes_sent
printf "r->mtime = %u\n", $r->mtime
printf "r->chunked = %u\n", $r->chunked
printf "r->byterange = %u\n", $r->byterange
if $r->boundary != 0x0
printf "r->boundary = '%s'\n", $r->boundary
end
if $r->range != 0x0
printf "r->range = '%s'\n", $r->range
end
printf "r->clength = %u\n", $r->clength
printf "r->remaining = %u\n", $r->remaining
printf "r->read_length = %u\n", $r->read_length
printf "r->read_body = %u\n", $r->read_body
printf "r->read_chunked = %u\n", $r->read_chunked
printf "r->expecting_100 = %u\n", $r->expecting_100
if $r->content_type != 0x0
printf "r->content_type = '%s'\n", $r->content_type
end
if $r->handler != 0x0
printf "r->handler = '%s'\n", $r->handler
end
if $r->content_encoding != 0x0
printf "r->content_encoding = '%s'\n", $r->content_encoding
end
if $r->content_language != 0x0
printf "r->content_language = '%s'\n", $r->content_language
end
if $r->vlist_validator != 0x0
printf "r->vlist_validator = '%s'\n", $r->vlist_validator
end
printf "r->no_cache = %u\n", $r->no_cache
printf "r->no_local_copy = %u\n", $r->no_local_copy
if $r->unparsed_uri != 0x0
printf "r->unparsed_uri = '%s'\n", $r->unparsed_uri
end
if $r->uri != 0x0
printf "r->uri = '%s'\n", $r->uri
end
if $r->filename != 0x0
printf "r->filename = '%s'\n", $r->filename
end
if $r->path_info != 0x0
printf "r->path_info = '%s'\n", $r->path_info
end
if $r->args != 0x0
printf "r->args = '%s'\n", $r->args
end
end
define dump_tables
printf "[r->headers_in]\n"
dump_table r->headers_in
printf "[r->headers_out]\n"
dump_table r->headers_out
printf "[r->err_headers_out]\n"
dump_table r->err_headers_out
printf "[r->subprocess_env]\n"
dump_table r->subprocess_env
printf "[r->notes]\n"
dump_table r->notes
end
I really didn't feel that comfortable using gdb so I tried on of the graphical frontends to gdb; DDD.
DDD soon became a tool I really liked to use. I like it because it's much easier to see what is going on inside the code. With your cursor you can inspect values of variables and create breakpoints. DDD is downloadable from http://www.gnu.org/software/ddd/ddd.html. Starting DDD with your httpd binary is straightforward:
ddd ./httpd
After digesting all this information we're going to put some of the theory to work. The first example will be adding a custom header to the outgoing response.
CREATE DESCRIPTION OF EACH SINGLE FUNCTION INSIDE EACH MODULE
yada yada yada about the module
#include "httpd.h"
#include "http_config.h"
module MODULE_VAR_EXPORT my_header_module;
static int my_header_fixup(request_rec *r) {
ap_table_set(r->headers_out, "X-My-Header", "this is my header");
return OK;
}
module MODULE_VAR_EXPORT my_header_module = {
STANDARD_MODULE_STUFF,
NULL, /* initializer */
NULL, /* dir config creator */
NULL, /* dir config merger */
NULL, /* server config */
NULL, /* merge server config */
NULL, /* command table */
NULL, /* handlers */
NULL, /* filename translation */
NULL, /* check_user_id */
NULL, /* check auth */
NULL, /* check access */
NULL, /* type_checker */
my_header_fixup, /* fixups */
NULL, /* logger */
NULL, /* header parser */
NULL, /* child_init */
NULL, /* child_exit */
NULL /* post read-request */
};
output from module:
$ lwp-request -m HEAD http://localhost/ 200 OK Connection: close Date: Mon, 30 Jul 2001 20:00:43 GMT Accept-Ranges: bytes Server: Apache/1.3.20 (Unix) Debian/GNU Content-Length: 4100 Content-Type: text/html; charset=iso-8859-1 ETag: "10a979-1004-3b26716d" Last-Modified: Tue, 12 Jun 2001 19:45:49 GMT Client-Date: Mon, 30 Jul 2001 20:00:43 GMT Client-Peer: 127.0.0.1:80 X-My-Header: this is my header
As you see, we have created a module which outputs a header with every response. Now wasn't that simple?
A lot of people have asked this question; How do I change the outgoing server header? There are two ways to do this. One is directly changing the initial value in the sources, which would be not following the guidelines set out by the Apache group. Since we only want to add our own module to the outgoing Server header we are going to use the function ap_add_version_component exported by httpd.h. We use this function in the server_init phase to achieve our goal.
#include "httpd.h"
#include "http_config.h"
#define MY_SERVER_VERSION "1.0"
module MODULE_VAR_EXPORT my_server_module;
static void my_server_init(server_rec *c, pool *p) {
ap_add_version_component("mod_my_server/" MY_SERVER_VERSION);
}
module MODULE_VAR_EXPORT my_server_module = {
STANDARD_MODULE_STUFF,
my_server_init, /* initializer */
NULL, /* dir config creator */
NULL, /* dir config merger */
NULL, /* server config */
NULL, /* merge server config */
NULL, /* command table */
NULL, /* handlers */
NULL, /* filename translation */
NULL, /* check_user_id */
NULL, /* check auth */
NULL, /* check access */
NULL, /* type_checker */
NULL, /* fixups */
NULL, /* logger */
NULL, /* header parser */
NULL, /* child_init */
NULL, /* child_exit */
NULL /* post read-request */
};
output from module:
$ lwp-request -m HEAD http://localhost/ 200 OK Connection: close Date: Mon, 30 Jul 2001 20:00:43 GMT Accept-Ranges: bytes Server: Apache/1.3.20 (Unix) Debian/GNU mod_my_server/1.0 Content-Length: 4100 Content-Type: text/html; charset=iso-8859-1 ETag: "10a979-1004-3b26716d" Last-Modified: Tue, 12 Jun 2001 19:45:49 GMT Client-Date: Mon, 30 Jul 2001 20:00:43 GMT Client-Peer: 127.0.0.1:80
Again, it is very easy to create these "effects"
Now we want to create a more advanced module. We are going to be doing the same as in our first module; display a header with every response. To make it more advanced we want to be able to turn it On and Off from inside httpd.conf. Additionally we want to be able to customize the header message from inside the configuration file.
#include "httpd.h"
#include "http_config.h"
typedef struct {
char *header_message;
int header_on;
} my_advanced_header_server_cfg;
module MODULE_VAR_EXPORT my_advanced_header_module;
static void *my_advanced_header_server_config(pool *p, server_rec *c) {
my_advanced_header_server_cfg *cfg = ap_pcalloc(p, sizeof(my_advanced_header_server_cfg));
if (!cfg)
return NULL;
cfg->header_message = "fill me out";
cfg->header_on = 0;
return (void *)cfg;
}
static const char *my_advanced_header_set_header(cmd_parms *cmd, void *dummy, char *header) {
server_rec *s = cmd->server;
my_advanced_header_server_cfg *cfg = (my_advanced_header_server_cfg *)ap_get_module_config(s->module_config, &my_advanced_header_module);
cfg->header_message = header;
return NULL;
}
static const char *my_advanced_header_set_header_on(cmd_parms *cmd, void *dummy, int flag) {
server_rec *s = cmd->server;
my_advanced_header_server_cfg *cfg = (my_advanced_header_server_cfg *)ap_get_module_config(s->module_config, &my_advanced_header_module);
cfg->header_on = flag;
return NULL;
}
static command_rec my_advanced_header_cmds[] = {
{ "MyAdvancedHeader", my_advanced_header_set_header_on, NULL,
RSRC_CONF, FLAG, "turn on my_advanced_header's output" },
{ "MyAdvancedHeader_message", my_advanced_header_set_header, NULL,
RSRC_CONF, TAKE1, "the message we want to output in our header" },
{ NULL }
};
static int my_header_fixup(request_rec *r) {
my_advanced_header_server_cfg *cfg = (my_advanced_header_server_cfg *)ap_get_module_config(r->server->module_config, &my_advanced_header_module);
if (!cfg->header_on)
return DECLINED;
ap_table_set(r->headers_out, "X-My-Header", cfg->header_message);
return OK;
}
module MODULE_VAR_EXPORT my_advanced_header_module = {
STANDARD_MODULE_STUFF,
NULL, /* initializer */
NULL, /* dir config creator */
NULL, /* dir config merger */
my_advanced_header_server_config, /* server config */
NULL, /* merge server config */
my_advanced_header_cmds, /* command table */
NULL, /* handlers */
NULL, /* filename translation */
NULL, /* check_user_id */
NULL, /* check auth */
NULL, /* check access */
NULL, /* type_checker */
my_header_fixup, /* fixups */
NULL, /* logger */
NULL, /* header parser */
NULL, /* child_init */
NULL, /* child_exit */
NULL /* post read-request */
};
output from module:
$ yada
yada
Explore the source code of Apache and accompanying modules, especially mod_example.c
Writing Apache modules with Perl and C (http://www.modperl.com/) Published by O'Reilly. ISBN: 156592567X
How to write a Makefile
apache-modules mailinglist hosted by Covalent.