The order in which things happen is very important to keep in mind when writing Apache modules, and I find not enough attention paid to this matter in references I can find on the subject. The answer, it turns out, is this: for Apache 1.3.x installations whose modules are all dynamically loaded at runtime using the LoadModule directive, for each request, all of the modules' post read-request handler functions will be called in reverse order of their LoadModule listing in httpd.conf; then, all of the modules' URI-to-filename translation handler functions will be called in reverse order of their LoadModule listing in httpd.conf; then all of the module's header parser handler functions, etc., etc.

The rest of this document is a clarification of the above statement.

Let me expand on this topic by sharing with you my original confusion over the function call order of Apache modules. At the bottom of most any Apache module's source code, you'll see this familiar structure:

module MODULE_VAR_EXPORT manni1_module = {
    STANDARD_MODULE_STUFF,
    handle_module_init,         /* module initializer */
    NULL,                       /* per-directory config creator */
    NULL,                       /* dir config merger */
    NULL,                       /* server config creator */
    NULL,                       /* server config merger */
    NULL,                       /* configuration directive table */
    NULL,                       /* [9]  content handlers */
    handle_uri_to_filename,     /* [2]  URI-to-filename translation */
    NULL,                       /* [5]  check/validate user_id */
    NULL,                       /* [6]  check user_id is valid *here* */
    NULL,                       /* [4]  check access by host address */
    NULL,                       /* [7]  MIME type checker/setter */
    NULL,                       /* [8]  pre-run fixups */
    NULL,                       /* [10] logger */
    NULL,                       /* [3]  header parser */
    NULL,                       /* child process initialisation */
    NULL,                       /* child process exit/cleanup */
    handle_post_read_request    /* [1]  post read-request */
};

As you probably already know, the numbered comments show the order in which your module's functions will be called for each request. What is not made clear in any documentation I have read is what is the order of function calls, per request, between modules?

Well it turns out that all of the [1]s are executed first, and then all of the [2]s, and then all of the [3]s, etc. If you want to know which module's [1] will be executed before which other module's [1] (now that you know they all get executed before any of the [2]s), you have to look at the order of the LoadModule directives in your httpd.conf. Here is a typical listing:

LoadModule env_module         libexec/mod_env.so
LoadModule config_log_module  libexec/mod_log_config.so
LoadModule mime_module        libexec/mod_mime.so
LoadModule negotiation_module libexec/mod_negotiation.so
LoadModule includes_module    libexec/mod_include.so
LoadModule autoindex_module   libexec/mod_autoindex.so
LoadModule dir_module         libexec/mod_dir.so
LoadModule cgi_module         libexec/mod_cgi.so
LoadModule asis_module        libexec/mod_asis.so
LoadModule imap_module        libexec/mod_imap.so
LoadModule action_module      libexec/mod_actions.so
LoadModule alias_module       libexec/mod_alias.so
LoadModule rewrite_module     libexec/mod_rewrite.so
LoadModule access_module      libexec/mod_access.so
LoadModule auth_module        libexec/mod_auth.so
LoadModule setenvif_module    libexec/mod_setenvif.so

As it turns out, handler functions are executed in reverse order of the LoadModule declarations. So, setenvif_module's [1] handler function is executed first, then auth_module's [1] handler, and on down the line. Then, setenvif_module's [2] handler function is executed, followed by auth_module's [2] handler, and on down the line.

However, there is a simplification I made in the previous paragraph: the only reason why Apache calls auth_module's [1] handler after setenvif_module's [1] handler is because setenvif's [1] handler returned DECLINED and not OK. And, the only reason why access_module's [1] handler would get called is if auth_module's [1] handler returned DECLINED and not OK, and on up the line. So for each handler, [1] through [10], Apache calls the handler of each module starting from the last listed module of the LoadModule listing and working towards the first listed module, checking the return code of each handler. Each time it recieves a DECLINED, it goes to the next-highest module in the list and executes its handler. When it recieve an OK, it starts back at the bottom of the list and begins processing all of the [2]s if it was just processing the [1]s, or all of the [5]s if it was just processing the [4]s, etc.

As an example, let's say Apache got through all of the [1]s because the last [1] it processed retuned OK. Apache is now handling the [2]s, beginning with setenvif_module because it is at the bottom (ironically, therefore, the start) of the LoadModule list. If auth_module's [2] handler returns DECLINED, access_module's [2] handler will get executed, and if access_module's [2] handler returns DECLINED, rewrite_module's [2] handler will get executed. But if rewrite_module's [2] handler returns OK, Apache will go back to setenvif_module, and execute its [3] handler function.

If you want to get an even better feel for this, do what I did and write two simple Apache modules that print to stderr when their handler functions for [1] and [2] are called. Then, place them in different locations of the LoadModule list of your httpd.conf and see what happens. Here is the source code for both modules (pardon the narcissistic names).

mod_manni1.c:

#include "httpd.h"
#include "http_config.h"
#include "http_core.h"
#include "http_log.h"

module MODULE_VAR_EXPORT manni1_module;

static int handle_post_read_request(request_rec *pReq) {
    fprintf(stderr, "manni1_module.handle_post_read_request ===> 1\n");

    return DECLINED;
}

static int handle_uri_to_filename(request_rec *pReq) {
    fprintf(stderr, "manni1_module.handle_uri_to_filename ===> 2\n");

    return DECLINED;
}

module MODULE_VAR_EXPORT manni1_module = {
    STANDARD_MODULE_STUFF,
    NULL,                       /* module initializer */
    NULL,                       /* per-directory config creator */
    NULL,                       /* dir config merger */
    NULL,                       /* server config creator */
    NULL,                       /* server config merger */
    NULL,                       /* configuration directive table */
    NULL,                       /* [9]  content handlers */
    handle_uri_to_filename,     /* [2]  URI-to-filename translation */
    NULL,                       /* [5]  check/validate user_id */
    NULL,                       /* [6]  check user_id is valid *here* */
    NULL,                       /* [4]  check access by host address */
    NULL,                       /* [7]  MIME type checker/setter */
    NULL,                       /* [8]  pre-run fixups */
    NULL,                       /* [10] logger */
    NULL,                       /* [3]  header parser */
    NULL,                       /* child process initialisation */
    NULL,                       /* child process exit/cleanup */
    handle_post_read_request    /* [1]  post read-request */
};

mod_manni2.c:

#include "httpd.h"
#include "http_config.h"
#include "http_core.h"
#include "http_log.h"

module MODULE_VAR_EXPORT manni2_module;

static int handle_post_read_request(request_rec *pReq) {
    fprintf(stderr, "manni2_module.handle_post_read_request ===> 1\n");

    return DECLINED;
}

static int handle_uri_to_filename(request_rec *pReq) {
    fprintf(stderr, "manni2_module.handle_uri_to_filename ===> 2\n");

    return DECLINED;
}

module MODULE_VAR_EXPORT manni2_module = {
    STANDARD_MODULE_STUFF,
    NULL,                       /* module initializer */
    NULL,                       /* per-directory config creator */
    NULL,                       /* dir config merger */
    NULL,                       /* server config creator */
    NULL,                       /* server config merger */
    NULL,                       /* configuration directive table */
    NULL,                       /* [9]  content handlers */
    handle_uri_to_filename,     /* [2]  URI-to-filename translation */
    NULL,                       /* [5]  check/validate user_id */
    NULL,                       /* [6]  check user_id is valid *here* */
    NULL,                       /* [4]  check access by host address */
    NULL,                       /* [7]  MIME type checker/setter */
    NULL,                       /* [8]  pre-run fixups */
    NULL,                       /* [10] logger */
    NULL,                       /* [3]  header parser */
    NULL,                       /* child process initialisation */
    NULL,                       /* child process exit/cleanup */
    handle_post_read_request    /* [1]  post read-request */
};

For background, I have the apache 1.3.23 source code unpacked in the src/ dir of my home directory, and I have installed Apache in a directory owned by my user (mwood): /usr/local/mwood/apache_1.3.23. I ran configure with the following options:

./configure \
--enable-module=so \
--enable-module=env --enable-shared=env \
--enable-module=setenvif --enable-shared=setenvif \
--enable-module=mime --enable-shared=mime \
--enable-module=negotiation --enable-shared=negotiation \
--enable-module=alias --enable-shared=alias \
--enable-module=rewrite --enable-shared=rewrite \
--enable-module=dir --enable-shared=dir \
--enable-module=autoindex --enable-shared=autoindex \
--enable-module=access --enable-shared=access \
--enable-module=auth --enable-shared=auth \
--enable-module=asis --enable-shared=asis \
--enable-module=include --enable-shared=include \
--enable-module=cgi --enable-shared=cgi \
--enable-module=actions --enable-shared=actions \
--enable-module=log_config --enable-shared=log_config \
--enable-module=imap --enable-shared=imap \
--enable-rule=SHARED_CORE \
--prefix=/usr/local/mwood/apache_1.3.23 \
--with-layout=Apache

I have put the source code for mod_manni1.c and mod_manni2.c in the src/modules/extra directory of my apache source tree, and then, from that directory, executed the following commands:

prospero:extra$ perl /usr/local/mwood/apache_1.3.23/bin/apxs -c mod_manni2.c
gcc -DLINUX=22 -I/usr/include/db1 -DUSE_HSREGEX -DUSE_EXPAT -I../lib/expat-lite -fpic -DSHARED_CORE -DSHARED_MODULE -I/usr/local/mwood/apache_1.3.23/include  -c mod_manni2.c
gcc -shared -o mod_manni2.so mod_manni2.o 
prospero:extra$ perl /usr/local/mwood/apache_1.3.23/bin/apxs -i -a -n manni2 mod_manni2.so
[activating module `manni2' in /usr/local/mwood/apache_1.3.23/conf/httpd.conf]
cp mod_manni2.so /usr/local/mwood/apache_1.3.23/libexec/mod_manni2.so
chmod 755 /usr/local/mwood/apache_1.3.23/libexec/mod_manni2.so
cp /usr/local/mwood/apache_1.3.23/conf/httpd.conf /usr/local/mwood/apache_1.3.23/conf/httpd.conf.bak
cp /usr/local/mwood/apache_1.3.23/conf/httpd.conf.new /usr/local/mwood/apache_1.3.23/conf/httpd.conf
rm /usr/local/mwood/apache_1.3.23/conf/httpd.conf.new
prospero:extra$

This installed the mod_manni1 and mod_manni2 modules in the correct directory of my Apache installation, and added the LoadModule and AddModule directives to httpd.conf for me.

The original order of the manni modules is so:

LoadModule env_module         libexec/mod_env.so
LoadModule config_log_module  libexec/mod_log_config.so
LoadModule mime_module        libexec/mod_mime.so
LoadModule negotiation_module libexec/mod_negotiation.so
LoadModule includes_module    libexec/mod_include.so
LoadModule autoindex_module   libexec/mod_autoindex.so
LoadModule dir_module         libexec/mod_dir.so
LoadModule cgi_module         libexec/mod_cgi.so
LoadModule asis_module        libexec/mod_asis.so
LoadModule imap_module        libexec/mod_imap.so
LoadModule action_module      libexec/mod_actions.so
LoadModule alias_module       libexec/mod_alias.so
LoadModule rewrite_module     libexec/mod_rewrite.so
LoadModule manni2_module      libexec/mod_manni2.so
LoadModule manni1_module      libexec/mod_manni1.so
LoadModule access_module      libexec/mod_access.so
LoadModule auth_module        libexec/mod_auth.so
LoadModule setenvif_module    libexec/mod_setenvif.so

ClearModuleList
AddModule mod_env.c
AddModule mod_log_config.c
AddModule mod_mime.c
AddModule mod_negotiation.c
AddModule mod_status.c
AddModule mod_include.c
AddModule mod_autoindex.c
AddModule mod_dir.c
AddModule mod_cgi.c
AddModule mod_asis.c
AddModule mod_imap.c
AddModule mod_actions.c
AddModule mod_userdir.c
AddModule mod_alias.c
AddModule mod_rewrite.c
AddModule mod_manni2.c
AddModule mod_manni1.c
AddModule mod_access.c
AddModule mod_auth.c
AddModule mod_so.c
AddModule mod_setenvif.c

(Please note the AddModule directives list the manni modules in the same location as the LoadModule directives.)

If you run Apache and load the following image, http://localhost:8080/manual/images/sub.gif (loading an image is a good way to log one request to the server), you'll see that the manni modules print the following messages in the following order in logs/error_log:

manni1_module.handle_post_read_request ===> [1]
manni2_module.handle_post_read_request ===> [1]
manni1_module.handle_uri_to_filename ===> [2]
manni2_module.handle_uri_to_filename ===> [2]

Note how putting manni1 after manni2 in the module listing allows manni1's handlers to execute before manni2's. Also note how ALL handlers for post-read-request [1] happen before any calls to URI-to-filename translation [2] handlers.

For real fun, put one of the mannis higher up in the list to see what happens when a module that is called first returns OK from its URI-to-filename [2] handler method: the manni module's URI-to-filename handler won't get called.

LoadModule env_module         libexec/mod_env.so
LoadModule config_log_module  libexec/mod_log_config.so
LoadModule mime_module        libexec/mod_mime.so
LoadModule negotiation_module libexec/mod_negotiation.so
LoadModule manni2_module      libexec/mod_manni2.so
LoadModule includes_module    libexec/mod_include.so
LoadModule autoindex_module   libexec/mod_autoindex.so
LoadModule dir_module         libexec/mod_dir.so
LoadModule cgi_module         libexec/mod_cgi.so
LoadModule asis_module        libexec/mod_asis.so
LoadModule imap_module        libexec/mod_imap.so
LoadModule action_module      libexec/mod_actions.so
LoadModule alias_module       libexec/mod_alias.so
LoadModule rewrite_module     libexec/mod_rewrite.so
LoadModule manni1_module      libexec/mod_manni1.so
LoadModule access_module      libexec/mod_access.so
LoadModule auth_module        libexec/mod_auth.so
LoadModule setenvif_module    libexec/mod_setenvif.so

ClearModuleList
AddModule mod_env.c
AddModule mod_log_config.c
AddModule mod_mime.c
AddModule mod_negotiation.c
AddModule mod_manni2.c
AddModule mod_status.c
AddModule mod_include.c
AddModule mod_autoindex.c
AddModule mod_dir.c
AddModule mod_cgi.c
AddModule mod_asis.c
AddModule mod_imap.c
AddModule mod_actions.c
AddModule mod_userdir.c
AddModule mod_alias.c
AddModule mod_rewrite.c
AddModule mod_manni1.c
AddModule mod_access.c
AddModule mod_auth.c
AddModule mod_so.c
AddModule mod_setenvif.c

Now, calling http://localhost:8080/manual/images/sub.gif gives you:

manni1_module.handle_post_read_request ===> [1]
manni2_module.handle_post_read_request ===> [1]
manni1_module.handle_uri_to_filename ===> [2]

Somewhere along the way, a module that works on URI-to-filename translation returns OK before mod_manni2's URI-to-filename translation handler is called ; so, mod_manni2's URI-to-filename translation handler never gets called when it is loaded in the order show above.

I hope this has helped.