Green feathers falling down

Compile your own Apache Module in C

16 June 2022

Before we jump in, I should let you know these instructions are going to be for 64 bit Windows only. I'll be compiling on Windows and for Windows. If you're running Mac or Linux I'm afraid you'll have to find another (probably easier) tutorial.

Compiling Apache for Windows is probably a pretty cooked thing to do anyway, as you'll usually want to deploy Apache on a nice cheap linux virtual machine. The good thing is the source code should be cross platform. The Apache Portable Runtime allows for this. But the compilation steps and downloads won't be. And I haven't double-checked that the source does compile properly on Mac or Linux.

The Apache Server #

The Apache Server was the best way to get a website running for most of the lifetime of the web. As of 2022 it holds a 31.4% market share of web server statistics. It continues to run a large part of the internet, in no small part due to Apache being a first class way to deploy a PHP application (it hosts a whole lot of Wordpress). Apache is the A in LAMP stack (Linux, Apache, MySQL and PHP). It's the stack that ruled the naughties, for better or for worse. Even though it has had its market share cannibalised by Nginx over the last decade, the Apache Server remains an important piece of tech.

How does Apache connect to PHP? One way is to run PHP is with FastCGI, a cross-platform way to get servers to talk to other programs on your system. More likely though, you'll be running it as an apache module.

The Apache server is a modular system, where modules execute logic across different parts of the request/response cycle of the HTTP protocol. These modules are .SO files - shared library files. They are binaries that live in Apache's modules folder. Apache has the ability to call functions they expose. You can filter which modules apply to which requests and configure how they work in with Apache's config file, and also . Hidden deep within the Apache documentation is a guide for Developing modules. We'll be half-following this guide.

Getting Apache for Windows #

First we'll need to download Apache. You'd expect to download Apache from the Apache website, but they only hold the source code and Unix binaries. Instead, you can grab Apache for Windows from third party vendors such as Apache Lounge. Download the Windows 64 version, and extract it into your favourite folder. The folder structure inside Apache should look like this:

To get a server running, we first set Apache up as a Windows Service. Open up Powershell as administrator and navigate to Apache's bin folder, then run this command:

./httpd.exe -k install

Now you have it installed as a service. This makes it a little but easier to run. We can start our server with this command:

NET START Apache2.4

After running this, you should see a message to say the server is starting. Head to your browser and type in "localhost" in the address bar. You should see a big H1 in times-new-roman saying "It works!". You can stop Apache by heading back to Powershell and running this command:

NET STOP Apache2.4

It might take a few seconds to run its cleanup.

Getting Visual Studio Build Tools #

We're going to write some C code, but we need a way to turn it into a binary shared library. If only there was some way to do that. Oh wait, that's what a compiler does. Let's grab a command-line version of Microsoft's MSVC compiler. Head to the Visual Studio Downloads Page, and scroll past all the IDEs (grab Visual Studio Code if you don't already have it - it's a great editor).

Under "All downloads" click into "Tools for Visual Studio". Scroll to the bottom and download "Build tools for Visual Studio". This is going to get us a compiler we can use from the command line.

But first we need to go through the Visual Studio installer which you can install and open. Select "Desktop development with C++" on the left. On the right, make sure the boxes are ticked for "MSVC" and "Windows 10 SDK". Install these.

Now a good idea is to open up "x64 Native Tools Command Prompt for VS 2022", and type in the letters:

cl

Then hit return and it should print off the version of the Windows Visual Studio C compiler you have installed.

That's the installing part done! Now let's write some code for our Apache module.

Writing the boilerplate #

We're going to make a module which will respond to any request with "hello apache". We'll be writing this in C. Create a file called "mod_hello_apache.c". The first thing we need to do is include headers from the Apache libraries we're going to use:

#include <httpd.h>
#include <http_protocol.h>
#include <http_config.h>

These libraries are provided by Apache for developers to build modules. They give us cross-platform functions to send data to respond to HTTP requests. We also use them to tell Apache how to call our module.

Now we need to expose a module struct. On start-up, Apache will look for this struct and pass it configuration data. It will also call the last item in the struct - a function which we use to set our module up:

module AP_MODULE_DECLARE_DATA hello_apache_module =
{
    STANDARD20_MODULE_STUFF,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    hello_apache_hooks   /* Our hook registering function */
};

In our code the set up function is called hello_apache_hooks. We don't really need any configuration for this simple module, so we're just going to leave the configuration arguments (the middle 5 arguments) as null. Why does the name of our set-up function include the word "hooks"? It's because we use this function to tell Apache how our module will "hook" into the request/response cycle. We use hooks to tell Apache what happens when.

Let's write our hello_apache_hooks function now:

static void hello_apache_hooks(apr_pool_t *pool)
{
    ap_hook_handler(hello_apache_handler, NULL, NULL, APR_HOOK_MIDDLE);
}

What is being passed into the hello_apache_hooks function? A pointer to something called a pool. For each request that hits our module, Apache will create a pool, which is an object to help us manage memory. Using a pool we can fill up memory with data for our handler, and Apache will clean everything up at the end of the processing cycle (when we call a cleanup function). This prevents memory leaks.

We've all heard about how scary C can be with its lack of guard rails for memory allocation. Pools make it a little safer.

We don't really need to do any complex memory assignment so we leave the pool alone.

Then we call an Apache function called ap_hook_handler. This registers that we want to run a function called hello_apache_handler in the middle of the request/response cycle. There are multiple points in the cycle we can hook into, including first, last, and middle.

We've put our handler in the middle, but you can imagine we might have a security module which kicks out blacklisted IP addresses before they reach our handler, or a module which attaches cache control headers to outgoing images. The middle two arguments are for telling Apache about functions to run before and after this one. We'll leave these as null.

Writing the handler function #

Okay now we're finished with the boilerplate! Well done, for making it this far!

Let's write our hello_apache_handler. This is a static function that contains the logic we run when a request comes in. We'll take in a pointer to a request_rec as a parameter. This is a struct which will describe all the information about the HTTP request - all the request headers, any query parameters, and a whole lot of other information you might need.

Our function will return an integer which serves as a status code to tell Apache whether our handler was successful.

static int hello_apache_handler(request_rec *r){}

Now let's do some checks. First we want to make sure that this request is actually meant to be for our handler (it's our job as module developers to do this):

static int hello_apache_handler(request_rec *r)
{
    if (!r->handler || (strcmp(r->handler, "hello_apache") != 0))
    {
        return DECLINED;
    }
}

Here we're making sure that the request actually needs a handler, then we make sure that the handler is our module.

We need to make sure this is a GET request. This is going to be the request method our module will respond to:

static int hello_apache_handler(request_rec *r)
{
    if (!r->handler || (strcmp(r->handler, "hello_apache") != 0))
    {
        return DECLINED;
    }
     if (r->method_number != M_GET)
    {
        return HTTP_METHOD_NOT_ALLOWED;
    }
}

If it's not a GET request, we return a status code provided by the Apache libraries to say that this method isn't allowed.

And now let's send something to the client. First we'll send a response header to say this is HTML, then we'll send some HTML over the wire:

static int hello_apache_handler(request_rec *r)
{
    if (!r->handler || (strcmp(r->handler, "hello_apache") != 0))
    {
        return DECLINED;
    }
    if (r->method_number != M_GET)
    {
        return HTTP_METHOD_NOT_ALLOWED;
    }
    ap_set_content_type(r, "text/html;charset=utf8");
    ap_rputs("<html>"
             "<head><title>Hello apache</title></head>"
             "<body>"
             "<h1>Hello from apache module!</h1>"
             "</body>"
             "</html>",
             r);
    return OK;
}

We return OK at the end of the function to say things went smoothly. Nice.

The entire module source #

Here's the entire module all together:

#include <httpd.h>
#include <http_protocol.h>
#include <http_config.h>

static int hello_apache_handler(request_rec *r)
{
    if (!r->handler || (strcmp(r->handler, "hello_apache") != 0))
    {
        return DECLINED;
    }
    if (r->method_number != M_GET)
    {
        return HTTP_METHOD_NOT_ALLOWED;
    }
    ap_set_content_type(r, "text/html;charset=utf8");
    ap_rputs("<html>"
             "<head><title>Hello apache</title></head>"
             "<body>"
             "<h1>Hello from apache module!</h1>"
             "</body>"
             "</html>",
             r);
    return OK;
}

static void hello_apache_hooks(apr_pool_t *pool)
{
    ap_hook_handler(hello_apache_handler, NULL, NULL, APR_HOOK_MIDDLE);
}

module AP_MODULE_DECLARE_DATA hello_apache = {
    STANDARD20_MODULE_STUFF,
    NULL,
    NULL,
    NULL,
    NULL,
    NULL,
    hello_apache_hooks};

That's our module! The functions need to be defined in this order to make sure that they've been declared before they get referenced.

Compiling the module #

Apache provides a tool called APSX. This is a perl script that looks at your machine and the file you're compiling and sets the appropriate compiler flags. I gave it a try but I couldn't get it working, so instead we're going to just use the the Visual Studio C compiler we downloaded earlier - MSVC.

Before you use MSVC, you'd usually need to set a bunch of environment variables for where windows libraries and headers live. An easier way is to open "x64 Native Tools Command Prompt for VS 2022" which will get installed with the MVSC package we grabbed earlier. This is a preconfigured shell which has all environment variables we need.

Open this up, navigate to the folder with our C source file, then we can create an object file with the following command:

cl /nologo /MD /W3 /O2 /D WIN32 /D WINDOWS /D NDEBUG -I"C:\Path\to\apache\include" /c mod_hello_apache.c

You can read about what these different command line arguments do on the Microsoft documentation. Now we should have a file called mod_hello_apache.obj. We aren't quite done yet. Now we just need to link the Apache libraries to the file:

link kernel32.lib ws2_32.lib "C:\Path\to\apache\lib\libhttpd.lib" /nologo
/subsystem:windows /dll /machine:x64 /out:mod_hello_apache.so mod_hello_apache.obj

You can check out what these linker arguments do too on the Microsoft documentation.

We now have a compiled Apache module called mod_hello_apache.so.

Move it into Apache's modules folder. The last thing we need to do is add this to the bottom of the Apache configuration file in conf/httpd.conf:

LoadModule hello_apache modules/mod_hello_apache.so

<Location "/hello">
SetHandler hello_apache
</Location>

The first line tells Apache we want to load a module called "hello_apache" and provides the path to it. Then inside the location tags we're telling it to use our module for all requests in the URL "/hello".

Now open up your favourite browser (mosaic, of course), and head to the URL "localhost". You should get the same message from before. However, change the url to "localhost/hello" and you should see the words "Hello from apache module!" in beautiful, big, bold times-new-roman.

Well done! You've created your first Apache module! Now you can do your web development in C like it's 1995.

Back to blog