namalzi

← All posts

Digging into Apache httpd's Source 6: Modules and Hooks — How a Module Plugs into the Server

Apache httpd · Published

Nearly every feature of httpd is a module, and a module plugs into the server by attaching functions to fixed points called hooks. Using the source at a pinned commit and three small modules written for this post, we look at the module struct, the two ways a module gets registered (static modules and LoadModule), what the hook macros really expand to, how RUN_FIRST differs from RUN_ALL, and how the call order is decided from order values and the names of other modules.

What this post covers

This is the sixth post in the "Digging into Apache httpd's Source" series. In Part 5 we watched main() read the configuration and call stages such as pre_config and post_config one after another. Something was skipped there with a quick "it calls the stage": when ap_run_post_config() is called, whose functions run, and in what order? This post answers that.

There are four questions.

Item Details
Target Apache httpd
Version 2.5.1-dev (trunk)
Commit e4af30fe6d (2026-10-24)
Library read alongside APR-util 1.6.3 (tag 1.6.3, commit 28327eeca2) — hook macros and sorting
Files read most include/http_config.h, server/config.c, include/ap_hooks.h, modules/core/mod_so.c, APR-util include/apr_hooks.h and hooks/apr_hooks.c

Every line number and detail in this post refers to that commit (and, for APR-util, that tag). Part 1 explains how to get the source at the same commit and build the Docker image used for the examples.

This post keeps only the main thread that someone opening the source for the first time can follow. What was left out is named, with its location, under "If you want to go deeper" at the end.

Words to know first

A module is a bundle of C code that adds one feature to httpd. mod_alias (Alias, Redirect), mod_ssl (HTTPS) and mod_proxy (reverse proxy) — the names you see in config files — are all modules, and as we saw in Part 3, so are the core (core.c) and HTTP handling (http_core.c).

A hook is a spot opened up in the middle of the server's work that says "other functions may be called here". For example, the handler hook means "time to produce the response body", and the fixups hook means "last touches just before the handler runs". Modules attach their functions to hooks (registration), and when the server reaches that spot it calls the attached functions in turn (running).

A function pointer is a variable that holds "the address of a function". You can keep it in a variable and later call the function through that variable. "Attaching a function to a hook" ends up meaning putting a function pointer into an array. A macro is a name that is replaced, letter for letter, by other code before compilation (#define). Both are explained in detail in Part 2.

A DSO (Dynamic Shared Object) is a library file that a running program can load later. On Linux it is a .so file, and in httpd the LoadModule directive loads it. A module built into the server executable from the start is called a static module; one loaded from a .so is a shared module.

OK, DECLINED and DONE are the agreed values that a function on a hook returns. They are defined in include/httpd.h.

#define OK           0  /**< Module has handled this stage. */
#define DECLINED    -1  /**< Module declines to handle */
#define DONE        -2  /**< Module has served the response completely
                         *   - it's safe to die() with no more output
                         */
#define SUSPENDED   -3  /**< Module will handle the remainder of the request.
                         *   The core will never invoke the request again */

include/httpd.h lines 461–467

OK means "I handled this stage", DECLINED means "nothing for me here, pass it on", and DONE means "the response is completely finished". Returning an HTTP status number such as 404 or 500 instead means "end this with an error". How these values change the flow is the subject of section 4.

1. A module is one struct

C has no classes. httpd defined "what a module is" as a single struct: module in include/http_config.h. (Every field's explanatory comment has been removed; the code lines are exactly as in the source.)

typedef struct module_struct module;
struct module_struct {
    int version;
    int minor_version;
    int module_index;

    const char *name;
    void *dynamic_load_handle;

    struct module_struct *next;

    unsigned long magic;

    void (*rewrite_args) (process_rec *process);
    void *(*create_dir_config) (apr_pool_t *p, char *dir);
    void *(*merge_dir_config) (apr_pool_t *p, void *base_conf, void *new_conf);
    void *(*create_server_config) (apr_pool_t *p, server_rec *s);
    void *(*merge_server_config) (apr_pool_t *p, void *base_conf,
                                  void *new_conf);

    const command_rec *cmds;

    void (*register_hooks) (apr_pool_t *p);

    int flags;
};

include/http_config.h lines 347–424

Lines like void *(*create_dir_config) (apr_pool_t *p, char *dir);, with (* before the name, are function pointer fields. The struct has four main parts.

  1. ID card — version and minor_version (the server API version the module was compiled against), module_index (its slot number in the config vector, explained below), name (the source file name), magic (a mark proving it really is a module struct), and next (a linked list joining the modules).
  2. Creating and merging config — four functions such as create_dir_config. They create and merge this module's config struct for every <Directory> and every virtual host. We look at them in Part 7.
  3. cmds — the table of directives this module understands. For mod_alias it says which function reads lines like Alias and Redirect. Also in Part 7.
  4. register_hooks — the star of this post. The server calls this function once, and inside it the module registers "I'll attach this function to this hook and that one to that hook".

A real module creates this struct as a single variable. Here is the end of mod_alias.

static void register_hooks(apr_pool_t *p)
{
    static const char * const aszSucc[]={ "mod_userdir.c",
                                          "mod_vhost_alias.c",NULL };

    ap_hook_translate_name(translate_alias_redir,NULL,aszSucc,APR_HOOK_MIDDLE);
    ap_hook_fixups(fixup_redir,NULL,NULL,APR_HOOK_MIDDLE);
}

AP_DECLARE_MODULE(alias) =
{
    STANDARD20_MODULE_STUFF,
    create_alias_dir_config,       /* dir config creater */
    merge_alias_dir_config,        /* dir merger --- default is to override */
    create_alias_config,           /* server config */
    merge_alias_config,            /* merge server configs */
    alias_cmds,                    /* command apr_table_t */
    register_hooks                 /* register hooks */
};

modules/mappers/mod_alias.c lines 783–801

register_hooks() is two lines. It attaches translate_alias_redir to the translate_name hook (the stage that turns a URL into a file path, Part 10) and fixup_redir to the fixups hook. The second and third arguments (NULL,aszSucc) and the last one (APR_HOOK_MIDDLE) decide the order; we get to them in section 5.

AP_DECLARE_MODULE(alias) and STANDARD20_MODULE_STUFF are macros.

#define AP_DECLARE_MODULE(foo) \
    APLOG_USE_MODULE(foo);                         \
    module AP_MODULE_DECLARE_DATA foo##_module

include/http_config.h lines 467–469

#define STANDARD20_MODULE_STUFF MODULE_MAGIC_NUMBER_MAJOR, \
                                MODULE_MAGIC_NUMBER_MINOR, \
                                -1, \
                                __FILE__, \
                                NULL, \
                                NULL, \
                                MODULE_MAGIC_COOKIE, \
                                NULL      /* rewrite args spot */

include/http_config.h lines 489–496

## is the operator that glues text together inside a macro. So AP_DECLARE_MODULE(alias) = { ... } becomes module alias_module = { ... }, the definition of a global variable (the APLOG_USE_MODULE in front is for per-module LogLevel, covered in Part 16). STANDARD20_MODULE_STUFF fills the first eight slots of the struct in order — version, minor_version, module_index, name, dynamic_load_handle, next, magic, rewrite_args. After that one word, a module author only has to write the remaining six slots — five functions and one directive table (the final flags is 0 if left out).

The four parts of the module struct: the ID-card slots filled by STANDARD20_MODULE_STUFF, four functions that create and merge config, the directive table cmds, and register_hooks, which registers hooks. module_index and the config functions lead to a slot number in the config vector, and register_hooks leads to the function array kept for each hook. The yellow box is the config vector, where each module uses one slot; the blue boxes are the two channels into the server's flow (directives and hooks).

module_index — each module's slot number

module_index starts as -1, and when the module is registered with the server it gets 0, 1, 2 … in turn (section 2). This number is a slot number in the config vector. A config vector is a void * array with one slot per module, and each slot holds the address of that module's config struct. The macro a module uses to fetch its own config shows exactly this.

#define ap_get_module_config(v,m)       \
    (((void **)(v))[(m)->module_index])

include/http_config.h lines 553–554

It just takes the module_index-th slot of the array. The server has no idea what the modules' config structs look like; each module interprets its own slot as its own type. The pros and cons of this approach are discussed in Part 2.

Easy to get wrong: a module has three names

The same module goes by different names depending on where you look.

Where Name Comes from
First argument of LoadModule, <IfModule>, httpd -M alias_module The variable name created by AP_DECLARE_MODULE(alias)
httpd -l, before/after lists in hook ordering, SHOW_HOOKS output mod_alias.c The struct's name slot, i.e. __FILE__ (the name of the compiled source file)
Second argument of LoadModule modules/mod_alias.so The file name the build produced

Some compilers put the full path into __FILE__, so ap_add_module() trims it to whatever follows the last / (or \ for a Windows path) (server/config.c lines 620–629). The name in the second row becomes important again in section 5.

2. How a module gets registered with the server

Having a module struct does not mean the server knows about it. There are two routes to registration: a static module, built into the executable, is registered right at startup; a shared module, in a .so, is registered when reading httpd.conf reaches its LoadModule line.

The two routes of module registration. main() first calls ap_setup_prelinked_modules(), which gives the static modules core, so and http the numbers 0, 1, 2 and calls ap_add_module(). Then, when ap_read_config() meets a LoadModule line, mod_so opens the .so, finds the module struct and, through ap_add_loaded_module(), calls the same ap_add_module(), where this module (still numbered -1) gets the next number. ap_add_module() calls the module's register_hooks(), and the ap_hook_*() calls inside it add one entry each to a hook's function array. apr_hook_sort_all() sorts the arrays, and when a request comes ap_run_handler() calls the sorted array in turn. The two routes meet in ap_add_module(). The yellow box — a hook's function array — fills up in registration order; sorting happens separately, at ③.

Static modules: modules.c and ap_setup_prelinked_modules()

Which modules are built into the executable is decided at ./configure time, and the build generates a file called modules.c in the top-level source folder. It is not in the repository (things like os/win32/modules.c in the repository are hand-written versions for other operating systems). Here it is, taken from the build folder in the example container (the image for this series was built with --enable-mods-shared=most, so there are only three static modules).

### 0. /usr/src/httpd/modules.c (generated at build time), lines 21-26
AP_DECLARE_DATA module *ap_prelinked_modules[] = {
  &core_module,
  &so_module,
  &http_module,
  NULL
};

It is an array of module struct addresses. The same file also has ap_preloaded_modules[] (the modules available for loading), whose contents are identical in this build. Early in startup main() calls ap_setup_prelinked_modules() (server/main.c line 574), which walks the array, hands out numbers, and registers the modules one by one.

AP_DECLARE(const char *) ap_setup_prelinked_modules(process_rec *process)
{
    /* ... */
    apr_hook_global_pool=process->pconf;
    /* ... */
    total_modules = 0;
    for (m = ap_preloaded_modules; *m != NULL; m++)
        (*m)->module_index = total_modules++;

    max_modules = total_modules + DYNAMIC_MODULE_LIMIT + 1;
    conf_vector_length = max_modules;
    /* ... */
    for (m = ap_prelinked_modules; *m != NULL; m++) {
        error = ap_add_module(*m, process->pconf, NULL);
        if (error) {
            return error;
        }
    }

    apr_hook_sort_all();

    return NULL;
}

server/config.c lines 763–816

Notice that the config vector length (conf_vector_length) is set up front to "number of static modules + DYNAMIC_MODULE_LIMIT + 1". It leaves room for the shared modules that will be loaded later. The default DYNAMIC_MODULE_LIMIT is 256 (include/httpd.h lines 119–120).

The door every module walks through: ap_add_module()

Static or shared, registration always ends up in ap_add_module().

AP_DECLARE(const char *) ap_add_module(module *m, apr_pool_t *p,
                                       const char *sym_name)
{
    /* ... */
    if (m->version != MODULE_MAGIC_NUMBER_MAJOR) {
        return apr_psprintf(p, "Module \"%s\" is not compatible with this "
                            "version of Apache (found %d, need %d). Please "
                            "contact the vendor for the correct version.",
                            m->name, m->version, MODULE_MAGIC_NUMBER_MAJOR);
    }

    if (m->module_index == -1) {
        /* ... */
        m->module_index = total_modules++;
        dynamic_modules++;

    }
    /* ... */
    if (m->next == NULL) {
        m->next = ap_top_module;
        ap_top_module = m;
    }
    /* ... */
    ap_add_module_commands(m, p);
    /* ... */
    ap_register_hooks(m, p);

    return NULL;
}

server/config.c lines 558–652

  1. Check the version. If the version baked in when the module was compiled (the MODULE_MAGIC_NUMBER_MAJOR of that time) differs from the current server's, the module is refused.
  2. Hand out a number. If it is still -1 (a shared module), it gets the next free number. Static modules already got theirs in the previous function.
  3. Put it at the head of the list. It is linked in at the front of the list that ap_top_module points to.
  4. Register its directives (ap_add_module_commands()) and its hooks (ap_register_hooks()).

ap_register_hooks() is short.

AP_DECLARE(void) ap_register_hooks(module *m, apr_pool_t *p)
{
    if (m->register_hooks) {
        if (getenv("SHOW_HOOKS")) {
            printf("Registering hooks for %s\n", m->name);
            apr_hook_debug_enabled = 1;
        }

        apr_hook_debug_current = m->name;
        m->register_hooks(p);
    }
}

server/config.c lines 489–500

Right before calling the module's register_hooks function pointer, it writes the module's name (mod_alias.c) into the global variable apr_hook_debug_current. The ap_hook_*() calls inside the module take no module name as an argument; this is the trick that still labels each registered function with "whose it is" (we confirm it in the expanded code in section 3). Setting the environment variable SHOW_HOOKS prints registration and sorting to the screen; we use it in the example in section 6.

Shared modules: mod_so and LoadModule

The LoadModule directive belongs not to the core but to the static module mod_so.

static const command_rec so_cmds[] = {
    AP_INIT_TAKE2("LoadModule", load_module, NULL, RSRC_CONF | EXEC_ON_READ,
      "a module name and the name of a shared object file to load it from"),

modules/core/mod_so.c lines 448–450

TAKE2 means a directive with two arguments, and EXEC_ON_READ means "run it right where it is read in the config file". The module has to be loaded before its directives can be understood, so this one cannot be put off and handled later like other directives (the order in which directives are processed is in Part 7). The real work is done by load_module(). The opening part that checks whether the module is already loaded, and the parts that build error messages, are cut.

    error = dso_load(cmd, &modhandle, filename, &module_file);
    if (error)
        return error;
    /* ... */
    if (apr_dso_sym(&modsym, modhandle, modname) != APR_SUCCESS) {
        /* ... */
    }
    modp = (module*) modsym;
    modp->dynamic_load_handle = (apr_dso_handle_t *)modhandle;
    modi->modp = modp;
    /* ... */
    if (modp->magic != MODULE_MAGIC_COOKIE) {
        return apr_psprintf(cmd->pool, "API module structure '%s' in file %s "
                            "is garbled - expected signature %08lx but saw "
                            /* ... */);
    }
    /* ... */
    error = ap_add_loaded_module(modp, cmd->pool, modname);
    if (error) {
        return error;
    }
    /* ... */
    apr_pool_cleanup_register(cmd->pool, modi, unload_module, apr_pool_cleanup_null);
    /* ... */
    ap_single_module_configure(cmd->pool, cmd->server, modp);

    return NULL;
}

modules/core/mod_so.c lines 265–322

  1. Open the file. apr_dso_load(), inside dso_load(), loads the .so file into the process (on Linux, dlopen() sits underneath).
  2. Find the struct by name. apr_dso_sym() looks up the symbol with the same name as the first LoadModule argument — alias_module — that is, the global variable created by AP_DECLARE_MODULE(alias), and returns its address. This is where the first of the "three names" from section 1 is used.
  3. Check it is genuine. It checks that the magic slot equals MODULE_MAGIC_COOKIE. At this commit the value is 0x41503235UL /* "AP25" */ (include/ap_mmn.h line 749).
  4. Register it. ap_add_loaded_module() calls the ap_add_module() we just saw (server/config.c lines 703–731). That is where register_hooks() gets called.
  5. Prepare for unloading. A cleanup is registered on the config pool (pconf), so when that pool is cleared by a restart or shutdown, the module is taken off the list and the .so is unloaded. Pools and cleanups were covered in Part 4.
  6. Create default config. ap_single_module_configure() fills in the server config slot and the default directory config slot.

Checking with an example: httpd -l, -M, -L

This example was run in a Docker container (Ubuntu 24.04) using httpd 2.5.1-dev built unchanged from the pinned commit (e4af30fe6d). It is a minimal config that compiles the three modules used in section 6 (mod_alpha, mod_beta, mod_gamma) with apxs -c and loads them with LoadModule. The whole example is in ex/u06-hooks/run.sh.

### 1. httpd -l
Compiled in modules:
  core.c
  mod_so.c
  http_core.c

### 2. httpd -M
Loaded Modules:
 core_module (static)
 so_module (static)
 http_module (static)
 mpm_event_module (shared)
 unixd_module (shared)
 authz_core_module (shared)
 alpha_module (shared)
 beta_module (shared)
 gamma_module (shared)

### 3. httpd -L | grep -A1 ^LoadModule
LoadModule (mod_so.c)
	a module name and the name of a shared object file to load it from

Easy to get wrong

3. A hook is built from three macros

Now let's see where functions like ap_hook_translate_name() and ap_hook_fixups() come from. If you grep the source, there is no definition with that name anywhere. Macros produce them. We'll follow one hook, handler, as the example.

① Declaration: AP_DECLARE_HOOK

A header file has this one line.

AP_DECLARE_HOOK(int,handler,(request_rec *r))

include/http_config.h line 1399

It says: "there is a hook called handler, and a function attached to it takes a request_rec *r and returns an int". The macro belongs to httpd, but all it does is add name prefixes and hand over to an APR-util macro.

#define AP_DECLARE_HOOK(ret,name,args) \
        APR_DECLARE_EXTERNAL_HOOK(ap,AP,ret,name,args)

include/ap_hooks.h lines 74–75

ap is the prefix of the function names to be generated, and AP is the prefix of export-marking macros like AP_DECLARE. The receiving end is the real thing in APR-util.

#define APR_DECLARE_EXTERNAL_HOOK(ns,link,ret,name,args) \
typedef ret ns##_HOOK_##name##_t args; \
link##_DECLARE(void) ns##_hook_##name(ns##_HOOK_##name##_t *pf, \
                                      const char * const *aszPre, \
                                      const char * const *aszSucc, int nOrder); \
link##_DECLARE(ret) ns##_run_##name args; \
APR_IMPLEMENT_HOOK_GET_PROTO(ns,link,name); \
typedef struct ns##_LINK_##name##_t \
    { \
    ns##_HOOK_##name##_t *pFunc; \
    const char *szName; \
    const char * const *aszPredecessors; \
    const char * const *aszSuccessors; \
    int nOrder; \
    } ns##_LINK_##name##_t;

APR-util include/apr_hooks.h lines 118–132

Put ap into ns and handler into name, glue with ##, and five things appear.

Expanded name What it is
ap_HOOK_handler_t The shape (type) of a function attached to the hook: int (request_rec *r)
ap_hook_handler() Declaration of the registration function a module calls. Takes a function pointer, the modules that must come before, the modules that must come after, and an order value
ap_run_handler() Declaration of the run function the core calls. Same arguments as the hook: (request_rec *r)
ap_hook_get_handler() Declaration of a function that returns the registered array
ap_LINK_handler_t A struct holding one registration: function pointer + name tag + before/after lists + order value

The header produces declarations only. There are no bodies yet. Because the shape of an attachable function is fixed by a typedef, the compiler notices if a module passes a function of a different shape to ap_hook_handler(). I deliberately attached a function with two arguments and no return value and compiled it in the example container (gcc 13.3) (ex/u06-hooks/mod_badtype.c).

### 10. a function of the wrong shape passed to ap_hook_handler()
mod_badtype.c:10:21: warning: passing argument 1 of 'ap_hook_handler' from incompatible pointer type [-Wincompatible-pointer-types]

It is a warning rather than an error, so the build goes on and you must not let such warnings slide — but for a plugin system written in C, the types are tied down fairly firmly.

② Storage: APR_HOOK_STRUCT

The bodies live in the one source file that "owns" the hook. The handler hook is owned by server/config.c. At the top of that file, storage for the hooks it owns is created.

APR_HOOK_STRUCT(
           APR_HOOK_LINK(header_parser)
           /* ... */
           APR_HOOK_LINK(handler)
           /* ... */
)

server/config.c lines 71–83

#define APR_HOOK_STRUCT(members) \
static struct { members } _hooks;

/** macro to link the hook structure */
#define APR_HOOK_LINK(name) \
    apr_array_header_t *link_##name;

APR-util include/apr_hooks.h lines 135–140

Expanded, this is static struct { apr_array_header_t *link_handler; ... } _hooks; — a struct variable with one slot per hook, each slot a pointer to an array. A global variable marked static is visible only inside its own .c file, so every file can use the same name _hooks without clashing. server/request.c has its own _hooks too, holding request-stage hooks such as fixups and translate_name (server/request.c lines 61–77).

③ Implementation: AP_IMPLEMENT_HOOK_RUN_FIRST

In the same file, this line creates the bodies.

AP_IMPLEMENT_HOOK_RUN_FIRST(int, handler, (request_rec *r),
                            (r), DECLINED)

server/config.c lines 169–170

The fourth argument (r) says "this is how to write the received arguments when passing them to the next function", and the fifth, DECLINED, says "move on to the next one if you get this value". This macro also goes through ap_hooks.h to APR-util's APR_IMPLEMENT_EXTERNAL_HOOK_RUN_FIRST (include/ap_hooks.h lines 137–139), which joins APR_IMPLEMENT_EXTERNAL_HOOK_BASE — the part that builds the registration function (APR-util include/apr_hooks.h lines 143–165) — to the body of the run function.

Rather than reading macros as macros, it is quicker to look at what the compiler expands them to. gcc -E stops after preprocessing (macro expansion) without compiling. I expanded a tiny file containing just the two macros above (ex/u06-hooks/expand.c) in the example container. The preprocessor output comes out as one long line, so the sed and awk in run.sh only add line breaks and indentation; no characters were changed (a few empty ; statements were removed — leftovers from debugging probe macros that are switched off).

### 9. gcc -E: what AP_IMPLEMENT_HOOK_RUN_FIRST(handler) becomes
static struct {
    apr_array_header_t *link_handler;
}
_hooks;
void ap_hook_handler(ap_HOOK_handler_t *pf,const char * const *aszPre, const char * const *aszSucc,int nOrder) {
    ap_LINK_handler_t *pHook;
    if(!_hooks.link_handler) {
        _hooks.link_handler=apr_array_make(apr_hook_global_pool,1,sizeof(ap_LINK_handler_t));
        apr_hook_sort_register("handler",&_hooks.link_handler);
    }
    pHook=apr_array_push(_hooks.link_handler);
    pHook->pFunc=pf;
    pHook->aszPredecessors=aszPre;
    pHook->aszSuccessors=aszSucc;
    pHook->nOrder=nOrder;
    pHook->szName=apr_hook_debug_current;
    if(apr_hook_debug_enabled) apr_hook_debug_show("handler",aszPre,aszSucc);
}
apr_array_header_t * ap_hook_get_handler(void) {
    return _hooks.link_handler;
}
int ap_run_handler (request_rec *r) {
    ap_LINK_handler_t *pHook;
    int n;
    int rv = -1;
    if(_hooks.link_handler) {
        pHook=(ap_LINK_handler_t *)_hooks.link_handler->elts;
        for(n=0 ; n < _hooks.link_handler->nelts ; ++n) {
            rv=pHook[n].pFunc (r);
            if(rv != -1) break;
        }
    }
    return rv;
}

(AP_DECLARE(void) became plain void because on non-Windows platforms AP_DECLARE(type) is defined as just type (include/ap_config.h lines 58–67). -1 is what DECLINED expands to.)

That is all a hook is.

The macros that make one hook. AP_DECLARE_HOOK in the header produces only declarations: the function-shape typedef, a struct holding one registration, and three function declarations. APR_HOOK_STRUCT and AP_IMPLEMENT_HOOK_RUN_FIRST in config.c create the bodies under the same names. Around the static array _hooks.link_handler, a module's register_hooks adds an entry with ap_hook_handler(), and the core's ap_invoke_handler walks the array with ap_run_handler(). The registering side (modules) and the running side (the core) meet across one static array — the yellow box. They do not know about each other.

Easy to get wrong

4. RUN_FIRST and RUN_ALL — the return value changes the flow

There are three kinds of macro for implementing a hook, and the difference is when they stop.

Macro What the run function does Examples at this commit
AP_IMPLEMENT_HOOK_VOID No return value. Calls every attached function child_init, test_config, insert_filter
AP_IMPLEMENT_HOOK_RUN_ALL Continues on OK or DECLINED; on any other value, stops and returns it. Returns OK if it reaches the end pre_config, post_config, fixups, access_checker
AP_IMPLEMENT_HOOK_RUN_FIRST Continues on DECLINED; on any other value, stops and returns it. Returns DECLINED if it reaches the end handler, translate_name, map_to_storage, type_checker

The examples come from server/config.c lines 85–175 and server/request.c lines 79–107. Where RUN_ALL's body differs from RUN_FIRST's is inside the loop.

#define APR_IMPLEMENT_EXTERNAL_HOOK_RUN_ALL(ns,link,ret,name,args_decl,args_use,ok,decline) \
/* ... */
    ret rv = ok; \
/* ... */
        for(n=0 ; n < _hooks.link_##name->nelts ; ++n) \
            { \
            /* ... */
            rv=pHook[n].pFunc args_use; \
            /* ... */
            if(rv != ok && rv != decline) \
                break; \
            rv = ok; \
            } \
/* ... */
    return rv; \
    }

APR-util include/apr_hooks.h lines 222–250

Look at rv = ok; right after if(rv != ok && rv != decline) break;. Even if a function returned DECLINED, the result is overwritten with OK. In a RUN_ALL hook, OK and DECLINED make no difference to the flow — both mean "next function". Only other values such as DONE or HTTP_FORBIDDEN (403) stop it. In RUN_FIRST the same spot is the single line if(rv != decline) break; (APR-util include/apr_hooks.h lines 267–295). There, OK is also a reason to stop.

The loops of RUN_FIRST and RUN_ALL. RUN_FIRST calls a function and moves to the next on DECLINED; otherwise, whether OK or 404, it returns that value and stops. RUN_ALL moves to the next on OK or DECLINED; on other values such as DONE, 403 or 500 it returns that value and stops, and if it gets through them all it returns OK. The red boxes are where the remaining functions stop being called.

Each hook gets the style that fits the question it asks.

How the core uses the result

The place that actually calls the handler hook is ap_invoke_handler().

    result = ap_run_handler(r);
    /* ... */
    return result == DECLINED ? HTTP_INTERNAL_SERVER_ERROR : result;
}

server/config.c lines 443–470

If nobody takes it all the way to the end (DECLINED), the result is 500. In practice that almost never happens, because the core has attached default_handler, which serves files, at the very end of the handler hook (APR_HOOK_REALLY_LAST) (server/core.c line 6171). We look at that function in Part 12. The result of fixups is returned as-is at the end of ap_process_request_internal() if it is not OK (server/request.c lines 435–439). The full order of request-processing stages is in Part 10.

Easy to get wrong

5. Deciding the order — order values and the names of other modules

Of the four arguments passed when attaching a function, three were about order: ap_hook_handler(fn, aszPre, aszSucc, nOrder).

The order value (nOrder) is a rough position. Smaller numbers come first.

    /* Hook orderings */
/** run this hook first, before ANYTHING */
#define APR_HOOK_REALLY_FIRST	(-10)
/** run this hook first */
#define APR_HOOK_FIRST		0
/** run this hook somewhere */
#define APR_HOOK_MIDDLE		10
/** run this hook after every other hook which is defined*/
#define APR_HOOK_LAST		20
/** run this hook last, after EVERYTHING */
#define APR_HOOK_REALLY_LAST	30

APR-util include/apr_hooks.h lines 297–307

They are plain integers, so values in between such as APR_HOOK_MIDDLE - 1 are allowed. The comment above the handler hook declaration recommends MIDDLE for handlers that respond only to a specific handler name and LAST for handlers that take anything (include/http_config.h line 1396).

The names of other modules (aszPre, aszSucc) express exact relations. You pass a list of module names as a NULL-terminated array: "the function from mod_include.c must come before me", "I must come before mod_userdir.c". In section 1, mod_alias said on the translate_name hook "before mod_userdir.c and mod_vhost_alias.c", and mod_userdir writes the matching half.

static void register_hooks(apr_pool_t *p)
{
    static const char * const aszPre[]={ "mod_alias.c",NULL };
    static const char * const aszSucc[]={ "mod_vhost_alias.c",NULL };

    ap_hook_translate_name(translate_userdir,aszPre,aszSucc,APR_HOOK_MIDDLE);

modules/mappers/mod_userdir.c lines 385–390

When rules from mod_alias and mod_userdir overlap in a config — say Alias /~foo /somewhere — these two lines are why Alias wins. All three are MIDDLE, but the names put mod_alias first, and since translate_name is RUN_FIRST, once mod_alias returns OK, mod_userdir is not called.

apr_hook_sort_all() — when and how sorting happens

Registration only appended to the end of the array. Sorting is done separately by apr_hook_sort_all(). It walks the list of arrays that apr_hook_sort_register() recorded on each hook's first ap_hook_*() call and passes them one by one to sort_hook() (APR-util hooks/apr_hooks.c lines 244–259). It is called at the end of ap_setup_prelinked_modules() (section 2) and, in main(), right after reading the config and right after processing the config tree (server/main.c line 740, line 758; when the restart loop re-reads the config, the same two spots again at line 836 and line 855). New modules may have joined via LoadModule while the config was read, so it sorts again each time.

Sorting has two steps.

  1. Line up roughly by order value. prepare() first sorts by nOrder with the C standard library's qsort() (line 88). The comparison function is even called crude_order, and all it does is a->nOrder-b->nOrder (lines 75–81). Then it matches the names in aszPre and aszSucc against the other entries' szName with strcmp to build "who comes before whom" relations. aszSucc is turned around and added to the other side's "must come before" list, so in the end every relation is expressed as a single kind, "must come before" (lines 96–125).
  2. Re-line them up to respect the relations. tsort() (topological sort) picks the result one entry at a time. It looks from the front for an entry not yet picked, and if that entry has a "must come before" entry not yet picked, it jumps over to that one and picks it first (lines 139–186). The source comment describes this as "dragging out-of-order items to the front" (lines 130–138).

Step 2 has one consequence: relations beat order values. Even a function with order value LAST, once named as a "must come before" of a FIRST function, is pulled forward in front of it. The example in section 6 is exactly this case.

The two steps of hook sorting. First, qsort looks only at order values and lines up core.c (-10), mod_gamma.c (0, must come after mod_alpha.c), mod_beta.c (10), mod_alpha.c (20). Second, tsort picks from the front but places a pending predecessor first, giving core.c, mod_alpha.c, mod_gamma.c, mod_beta.c. The fixups hook from the section 6 example. mod_alpha.c, last by order value, is pulled to second place by mod_gamma.c.

6. Example: three modules on the same hook

Three modules written for this post each attach one function to fixups (RUN_ALL) and one to handler (RUN_FIRST). Each time a function is called it leaves a line in the response header X-Trail saying "I was called and I'm returning this value". It writes to r->err_headers_out so the line survives on error responses too.

Module fixups order fixups returns handler order handler returns
mod_alpha.c LAST DECLINED FIRST always DECLINED
mod_beta.c MIDDLE OK MIDDLE OK, but DECLINED for ?pass
mod_gamma.c FIRST + mod_alpha.c before it OK, but 403 for ?stop LAST OK

Here is mod_gamma.c, which shows the most of the three. The other two have the same shape (example folder ex/u06-hooks/).

/* Example written for this post (not httpd source code) */
#include "httpd.h"
#include "http_config.h"
#include "http_protocol.h"
#include "http_request.h"

static int gamma_fixups(request_rec *r)
{
    if (r->args && !strcmp(r->args, "stop")) {   /* on ?stop, cut the request off with 403 */
        apr_table_add(r->err_headers_out, "X-Trail", "fixups: gamma -> 403");
        return HTTP_FORBIDDEN;
    }
    apr_table_add(r->err_headers_out, "X-Trail", "fixups: gamma -> OK");
    return OK;
}

static int gamma_handler(request_rec *r)
{
    if (strcmp(r->handler, "hookdemo")) return DECLINED;
    apr_table_add(r->err_headers_out, "X-Trail", "handler: gamma -> OK");
    ap_set_content_type(r, "text/plain");
    ap_rputs("response written by mod_gamma\n", r);
    return OK;
}

static void register_hooks(apr_pool_t *p)
{
    /* The order value is FIRST, but we pin "after mod_alpha.c".
     * The name is the module struct's name, i.e. the source file name. */
    static const char * const pre[] = { "mod_alpha.c", NULL };

    ap_hook_fixups(gamma_fixups, pre, NULL, APR_HOOK_FIRST);
    ap_hook_handler(gamma_handler, NULL, NULL, APR_HOOK_LAST);
}

AP_DECLARE_MODULE(gamma) = {
    STANDARD20_MODULE_STUFF,
    NULL, NULL, NULL, NULL, NULL,
    register_hooks
};

The first line of the handler function, if (strcmp(r->handler, "hookdemo")) return DECLINED;, is the etiquette of a RUN_FIRST hook: if the name set by SetHandler hookdemo in the config is not yours, pass immediately. The config is the three LoadModule lines from section 2 plus <Location /demo> SetHandler hookdemo </Location>.

Watching the sort: SHOW_HOOKS

With the SHOW_HOOKS environment variable from section 2 turned on, I ran only the config check (-t). The output is long, so grep -n picks out only the fixups and handler lines and the module registration lines (the leading numbers are line numbers in the original output).

### 4. SHOW_HOOKS=1 httpd -t | grep (fixups, handler)
1:Registering hooks for core.c
20:  Hooked handler
21:  Hooked handler
23:  Hooked fixups
36:Registering hooks for mod_so.c
38:Registering hooks for http_core.c
61:Sorting handler: core.c core.c
63:Sorting fixups: core.c
81:Registering hooks for event.c
97:Registering hooks for mod_unixd.c
101:Registering hooks for mod_authz_core.c
106:Registering hooks for mod_alpha.c
107:  Hooked fixups
108:  Hooked handler
109:Registering hooks for mod_beta.c
110:  Hooked fixups
111:  Hooked handler
112:Registering hooks for mod_gamma.c
113:  Hooked fixups pre(mod_alpha.c)
114:  Hooked handler
128:Sorting handler: core.c mod_alpha.c mod_beta.c mod_gamma.c core.c
130:Sorting fixups: core.c mod_alpha.c mod_gamma.c mod_beta.c
173:Sorting handler: core.c mod_alpha.c mod_beta.c mod_gamma.c core.c
175:Sorting fixups: core.c mod_alpha.c mod_gamma.c mod_beta.c

There is a lot to read here.

Sending requests

With the server running, here are three requests sent with curl -si (only the status line, the X-Trail headers and the body are kept).

### 5. curl /demo
HTTP/1.1 200 OK
X-Trail: fixups: alpha -> DECLINED
X-Trail: fixups: gamma -> OK
X-Trail: fixups: beta -> OK
X-Trail: handler: alpha -> DECLINED
X-Trail: handler: beta -> OK
response written by mod_beta

### 6. curl /demo?pass
HTTP/1.1 200 OK
X-Trail: fixups: alpha -> DECLINED
X-Trail: fixups: gamma -> OK
X-Trail: fixups: beta -> OK
X-Trail: handler: alpha -> DECLINED
X-Trail: handler: beta -> DECLINED
X-Trail: handler: gamma -> OK
response written by mod_gamma

### 7. curl /demo?stop
HTTP/1.1 403 Forbidden
X-Trail: fixups: alpha -> DECLINED
X-Trail: fixups: gamma -> 403
  1. /demo — in fixups (RUN_ALL), gamma and beta were all called even though alpha returned DECLINED. In handler (RUN_FIRST), alpha passed, beta returned OK, and it stopped there. gamma's handler was not called.
  2. /demo?pass — when beta also passed with DECLINED, gamma, next in line, produced the response. The same URL can be answered by a different module depending on what the earlier modules say.
  3. /demo?stop — gamma returned 403 in fixups, and RUN_ALL stopped. beta, after it, was not called, and the 403 response went out without ever reaching the handler stage.

The X-Trail of the three requests as a picture. /demo returns 200 with a body written by mod_beta. In fixups all three were called: alpha DECLINED, gamma OK, beta OK. In handler, alpha returned DECLINED and beta returned OK, so gamma was not called. /demo?pass returns 200 with a body written by mod_gamma. fixups is the same; in handler alpha and beta return DECLINED and gamma returns OK. /demo?stop returns 403 Forbidden. In fixups alpha returned DECLINED and gamma returned 403, so beta was not called and the request never reached the handler phase The X-Trail lines of the curl output above, left to right in order. Grey is DECLINED, green is OK, red is an error, and dashed boxes are functions that were not called.

Easy to get wrong: a wrong name in the before/after lists is silently ignored

This is a trap I checked while building the example. I compiled gamma with its "must come before" written as "alpha_module" — the name used in LoadModule — instead of "mod_alpha.c", and ran the same check.

### 8. predecessor written as "alpha_module" instead of "mod_alpha.c"
Sorting fixups: core.c mod_gamma.c mod_beta.c mod_alpha.c

No error, no warning — the result is sorted by order value alone (gamma → beta → alpha). prepare() builds a relation when it finds an entry with a matching name, and just moves on when it does not (APR-util hooks/apr_hooks.c lines 99–111). This behaviour is useful on purpose: it is why mod_alias can name mod_userdir.c without causing trouble on a server that does not load mod_userdir. But typos are ignored just as quietly, so write before/after names as the .c names that httpd -l or SHOW_HOOKS shows, and check the sorted result with your own eyes once.

Two more points.

7. A taste: optional functions and providers

A hook is the way "many modules attach functions to one spot". Sometimes you want the reverse: one module wants to find, by name, a function another module owns, and call it. That other module may not be loaded, so writing it as an ordinary function call would break linking. httpd solves this in two ways.

An optional function is a mechanism for registering and finding a single function by name. mod_so offers one of its own functions with the single line APR_REGISTER_OPTIONAL_FN(ap_find_loaded_module_symbol); inside register_hooks() (modules/core/mod_so.c lines 440–446). APR_REGISTER_OPTIONAL_FN puts the function pointer into a hash table keyed by the function name as a string, and the user takes it out with APR_RETRIEVE_OPTIONAL_FN(name) (APR-util include/apr_optional.h lines 70–85). If that module is absent you get NULL, so the user only needs to check. main() finds ap_signal_server this way too (server/main.c line 785). It resembles hooks in that registration happens inside register_hooks(), and that, like hooks, it is wiped and registered again on restart (apr_hook_deregister_all() empties this hash as well, APR-util hooks/apr_hooks.c lines 266–284).

A provider is a mechanism for registering several implementations of the same job under "group + name + version" and choosing one. You register with ap_register_provider(pool, group, name, version, struct address) and look up with ap_lookup_provider(group, name, version) (server/provider.c lines 35–39, lines 99–101). The file in a config line like AuthBasicProvider file is exactly such a name. How authentication modules are tied together this way is in Part 13, and how mod_proxy's load balancer picks its method is in Part 14.

The two mechanisms side by side. An optional function is a way to find one function by name. When mod_so's register_hooks() registers it with APR_REGISTER_OPTIONAL_FN(ap_find_loaded_module_symbol), the function pointer goes into the hash table s_phOptionalFunctions with the function's name string as the key. The user takes it out with APR_RETRIEVE_OPTIONAL_FN(name); if the module isn't loaded the result is NULL, so a simple check is enough. main() finds ap_signal_server this way. On restart apr_hook_deregister_all() empties this hash too, so it is registered again in register_hooks(). A provider is a way to choose among several implementations of the same job. ap_register_provider(pool, group, name, version, struct address) puts the struct into three nested hashes keyed by group, name and version, and ap_lookup_provider(group, name, version) takes it out. The file in AuthBasicProvider file is this name The hash name and the three-level structure were checked in APR-util hooks/apr_hooks.c lines 378–392 and server/provider.c lines 51–71.

If you want to go deeper

This post left a lot out to keep to the main thread. Here are names and locations only.

Recap

Concept One-line summary
module struct Everything a module is. ID card (version, number, name, magic) + four config functions + directive table + register_hooks
AP_DECLARE_MODULE(foo) Creates the global variable module foo_module. The first LoadModule argument is this variable's name
module_index Slot number in the config vector (a void * array). Handed out in order from 0 at registration
ap_add_module() The registration door for both static and shared modules. Version check → number → list → directives → register_hooks()
mod_so Owner of LoadModule. Opens the .so, finds the struct by name, checks magic, then ap_add_loaded_module()
AP_DECLARE_HOOK In a header, creates the function shape, the one-registration struct, and the ap_hook_X/ap_run_X declarations
AP_IMPLEMENT_HOOK_* In a .c file, creates the bodies of ap_hook_X(), which pushes onto a static array, and ap_run_X(), which walks it
RUN_FIRST Stops on anything but DECLINED. "Who takes this?" hooks (handler, translate_name)
RUN_ALL Continues on OK/DECLINED, stops on anything else. "Everyone once" hooks (fixups, post_config)
Order value REALLY_FIRST (-10) to REALLY_LAST (30). qsort() lines things up roughly
Before/after names Lists of .c names. tsort() respects them, and they beat order values. Wrong names are silently ignored
Optional function / provider The reverse direction of hooks — find another module's function or implementation by name

What to look at next

Of the two channels a module uses to plug into the server, we have seen hooks. The other is cmds from section 1 — directives, which we only named. How the line Alias /icons/ "/usr/share/icons/" in httpd.conf is read and handed to a mod_alias function, and how the config structs created for each <Directory> and <VirtualHost> are merged — where this post's module_index and config vector are actually used — is in Part 7: The Config File and Directives — How One Line of httpd.conf Becomes a C Struct.

Here is how Spring solved the same problem. The idea of "spots where outside code is inserted into the creation flow" is the same as BeanPostProcessor in Spring Part 6. Calling every registered one in turn resembles RUN_ALL, and the rule that a smaller Ordered number comes first resembles nOrder. There are two differences. Spring puts objects that implement an interface into a list, while httpd puts function pointers whose shape is fixed by a typedef into a separate array per hook. And besides numbers, httpd can express order by the other party's name, as in "after mod_alias.c". Picking "the first one that answers", as RUN_FIRST does, resembles how DispatcherServlet in Part 21 takes the first non-null answer from its list of HandlerMappings.

More posts