Mimsy Were the Borogoves

Hacks: Articles about programming in Python, Perl, Swift, BASIC, and whatever else I happen to feel like hacking at.

Creating a C Apache module on Mac OS X Leopard

Jerry Stratton, February 15, 2008

One of the items on my task list for a long time now has been a specialized module for Apache. There doesn’t appear to be a whole lot in the way of really simple Apache module writing tutorials. Most seem to either start well into the process instead of at square one, or provide non-working examples that don’t even go so far as to actually compile a working module.

Much of the documentation—even on the Apache web site—is 1.3 documentation retitled as 2.0. So I’m going to see about blogging the learning process for my module. It might be useful to others, and if I’m doing this completely wrong you can let me know!

I’m going to try to give you everything you need to compile a very simple working module. I’m doing this on OS X Leopard, so some of my notes will be specific to that. Leopard uses Apache 2.2, so that’s what I’m using. If you’re using Leopard, you’ll need to install the Developer Tools that came with your Mac so that you have the Gnu C Compiler.

You’ll also need the Apache web server. On the Mac, start it by going to Sharing in your System Preferences and turning on Web Sharing. You should be able to go to http://localhost/ in your browser and see the boilerplate text from Apache.

A do-nothing module

This module will do nothing, which is only slightly less than what the finished module will do. The finished module is going to put a header on every page that counts up the number of requests that process has served. You’ll be able to look at that header using the program “curl” on the command line.

Go into the terminal and type “curl --head http://localhost/”. You should see a list of headers, like this:

  • HTTP/1.1 200 OK
  • Date: Sat, 16 Feb 2008 01:05:55 GMT
  • Server: Apache/2.2.6 (Unix) mod_ssl/2.2.6 OpenSSL/0.9.7l DAV/2 mod_python/3.3.1 Python/2.5.1 PHP/5.2.4
  • Content-Location: index.html.en
  • Vary: negotiate,accept-language,accept-charset
  • TCN: choice
  • Last-Modified: Mon, 24 Sep 2007 01:12:03 GMT
  • ETag: "a47417-5b0-43ad74ee73ec0"
  • Accept-Ranges: bytes
  • Content-Length: 1456
  • Content-Type: text/html
  • Content-Language: en

These are provided every time you visit a web page. They tell the browser useful information about the page such as the language of the page, when it was last modified, and what time the server thinks it currently is.

This module will add its own header to that list.

[toggle code]

  • /* a very simple module: put a header in the reply with the number of hits this process has received */
  • #include "httpd.h"
  • #include "http_config.h"
  • static int myHitsCounter(request_rec *request) {
    • apr_table_set(request->headers_out, "X-Process-Hits", "Not Implemented");
    • return OK;
  • }
  • static void myRegisterHooks(apr_pool_t *p) {
    • ap_hook_fixups(myHitsCounter, NULL, NULL, APR_HOOK_MIDDLE);
  • }
  • module AP_MODULE_DECLARE_DATA myhits_module = {
    • STANDARD20_MODULE_STUFF,
    • NULL, /* create per-directory config structures */
    • NULL, /* merge per-directory config structures */
    • NULL, /* create per-server config structures */
    • NULL, /* merge per-server config structures */
    • NULL, /* command handlers */
    • myRegisterHooks /* register hooks */
  • };

Save this as “mod_myhits.c”. The central item in this code is the bottom one, “myhits_module”. The first part—in this case, “myhits”—should be the same as the part of the filename after “mod_”. Since the filename is “mod_myhits.c” this section is called “myhits_module”.

When setting up a module, there are various places where Apache can call it. What I’m doing here is telling it that I want to register some “hooks” into the process that Apache uses to display pages. To register those hooks with Apache I need a function that registers the hooks. I’m telling Apache that the name of the function that will register hooks is “myRegisterHooks”.

So the second level in this module is the function myRegisterHooks. It has only one line: it registers a hook into the “fixups” phase of Apache’s page display process. I’m telling Apache to put my hook somewhere in the middle of that phase. I could also use APR_HOOK_FIRST or APR_HOOK_LAST (or, rarely, APR_HOOK_REALLY_FIRST and APR_HOOK_REALLY_LAST). According to the docs, the middle is normally what we want.

The hook that myRegisterHooks registers is myHitsCounter. That’s the top function. It’s going to create a header called “X-Process-Hits” with the value of “Not Implemented”. Normally when adding special headers, “X-” is put in front of the name.

When Apache calls a hook, it sends the hook information about the current page request. I’m storing that in the variable “request”. One of the pieces of information in the request is the list of headers the request will send back to the browser. The headers are stored in an Apache table (much like an associative array or dictionary in Perl and Python), and apr_table_set adds new items to that table.

So this function should add the header “X-Process-Hits” with the value of “Not Implemented”.

Compile the module

Compile it using “sudo apxs -c -i -a mod_myhits.c”. (You will need to be an administrative user to install Apache modules.)

Apxs compiles and installs Apache modules. The -c tells apxs to compile the module; the -i tells apxs to install the module; and the -a tells apxs to put a line for the module into Apache’s configuration file.

When you run apxs, it should look something like this:

[toggle code]

  • /usr/share/apr-1/build-1/libtool --silent --mode=compile gcc    -DDARWIN -DSIGPROCMASK_SETS_THREAD_MASK -no-cpp-precomp  -I/usr/include/apache2  -I/usr/include/apr-1   -I/usr/include/apr-1  -arch ppc64  -c -o mod_myhits.lo mod_myhits.c && touch mod_myhits.slo
  • /usr/share/apr-1/build-1/libtool --silent --mode=link gcc -o mod_myhits.la -arch ppc64  -rpath /usr/libexec/apache2 -module -avoid-version    mod_myhits.lo
  • /usr/share/httpd/build/instdso.sh SH_LIBTOOL='/usr/share/apr-1/build-1/libtool' mod_myhits.la /usr/libexec/apache2
  • /usr/share/apr-1/build-1/libtool --mode=install cp mod_myhits.la /usr/libexec/apache2/
  • cp .libs/mod_myhits.so /usr/libexec/apache2/mod_myhits.so
  • cp .libs/mod_myhits.lai /usr/libexec/apache2/mod_myhits.la
  • cp .libs/mod_myhits.a /usr/libexec/apache2/mod_myhits.a
  • ranlib /usr/libexec/apache2/mod_myhits.a
  • chmod 644 /usr/libexec/apache2/mod_myhits.a
  • ----------------------------------------------------------------------
  • Libraries have been installed in:
    • /usr/libexec/apache2
  • If you ever happen to want to link against installed libraries in a given directory, LIBDIR, you must either use libtool, and specify the full pathname of the library, or use the `-LLIBDIR' flag during linking and do at least one of the following:
    • - add LIBDIR to the `DYLD_LIBRARY_PATH' environment variable during execution
  • See any operating system documentation about shared libraries for more information, such as the ld(1) and ld.so(8) manual pages.
  • ----------------------------------------------------------------------
  • chmod 755 /usr/libexec/apache2/mod_myhits.so
  • [activating module `myhits' in /private/etc/apache2/httpd.conf]

The last line is the important one: it means that everything else has been successful, and apxs has told Apache to use the module. If apxs fails, take a look at the errors. You can’t go any further until apxs successfully compiles and installs the module.

Now, type “sudo apachectl configtest” to test the new module. If it responds “Syntax OK”, restart your web server with “sudo apachectl graceful”. Try the curl command again. You should see the new header:

  • 7: curl --head http://localhost/
  • HTTP/1.1 200 OK
  • Date: Sat, 16 Feb 2008 01:33:11 GMT
  • Server: Apache/2.2.6 (Unix) mod_ssl/2.2.6 OpenSSL/0.9.7l DAV/2 mod_python/3.3.1 Python/2.5.1 PHP/5.2.4
  • Content-Location: index.html.en
  • Vary: negotiate,accept-language,accept-charset
  • TCN: choice
  • X-Process-Hits: Not Implemented
  • X-Process-Hits: Not Implemented
  • Last-Modified: Mon, 24 Sep 2007 01:12:03 GMT
  • ETag: "a47417-5b0-43ad74ee73ec0"
  • Accept-Ranges: bytes
  • Content-Length: 1456
  • Content-Type: text/html
  • Content-Language: en

Two headers?

Why is the header in there twice? Because the “fixups” phase will run twice (or more) on any page that requires an internal redirect. Folders require an internal redirect: when Apache sees a request for a URL ending in a slash, it processes that request, looks for the most likely file it really means, such as index.html, and then processes the request again for that file.

We can fix that by checking to see if this is a subrequest or not. If it is a subrequest, then the request will have a “main” request on top of it. Replace the apr_table_set line with:

[toggle code]

  • //if main exists, this is an internal redirect, which means we've already acted on this "hit"
  • if (!request->main) {
    • apr_table_set(request->headers_out, "X-Process-Hits", "Almost Implemented");
  • }

Run the apxs command again, then apachectl graceful, and when you run curl again to get the headers, you should see only one X-Process-Hits header.

Counting hits

In order to count requests, C needs to increment an integer. In order to display them, it needs a string. Apache has a collection of useful string routines in apr_strings.h, so below the two “#include” lines, add a third line to include apr_strings.h, and a fourth line to set up an unsigned integer for counting hits:

  • #include "apr_strings.h"
  • static uint hitCount = 0;

Replace the apr_table_set line with:

  • char *hitString;
  • hitCount++;
  • hitString = apr_itoa(request->pool, hitCount);
  • apr_table_set(request->headers_out, "X-Process-Hits", hitString);

The first new line sets up a variable for keeping track of the text (string) version of the hit count. The second new line increments the hit count by 1.

The third line uses the Apache function apr_itoa to convert the hit count from an integer into a string. It does this by putting the string into a pool of memory available to this request. C is famous for its difficulties working with strings of unknown length. This function and others in apr_strings.h help us get around that.

Finally, the new apr_table_set line creates the HTTP header with hitString as its value.

Run the apxs command again to compile and install it; run apachectl graceful to restart apache; and then run the curl command to see the headers.

If you’re working on a local server (such as your desktop workstation) that no one else has access to, the first time you run the command, you should see “1” as the number of hits. Afterwards, you’ll see it count up, but in an alternating manner. Apache runs several processes so that it can serve multiple requests at the same time. Each of those processes will keep its own count. If your development server is accessed by other people in your local subnet, or by the entire Internet, you may find that the numbers get larger much faster and that the alternation is harder to see.

The final code

Here’s the final code all in one place:

[toggle code]

  • /* a very simple module: put a header in the reply with the number of hits this process has received */
  • #include "httpd.h"
  • #include "http_config.h"
  • #include "apr_strings.h"
  • static uint hitCount = 0;
  • static int myHitsCounter(request_rec *request) {
    • //if main exists, this is an internal redirect, which means we've already acted on this "hit"
    • if (!request->main) {
      • char *hitString;
      • hitCount++;
      • hitString = apr_itoa(request->pool, hitCount);
      • apr_table_set(request->headers_out, "X-Process-Hits", hitString);
    • }
    • return OK;
  • }
  • static void myRegisterHooks(apr_pool_t *p) {
    • ap_hook_fixups(myHitsCounter, NULL, NULL, APR_HOOK_MIDDLE);
  • }
  • module AP_MODULE_DECLARE_DATA myhits_module = {
    • STANDARD20_MODULE_STUFF,
    • NULL, /* create per-directory config structures */
    • NULL, /* merge per-directory config structures */
    • NULL, /* create per-server config structures */
    • NULL, /* merge per-server config structures */
    • NULL, /* command handlers */
    • myRegisterHooks /* register hooks */
  • };

Run these commands to install it and view the new header:

  1. sudo apxs -c -i -a mod_myhits.c
  2. sudo apachectl graceful
  3. curl --head http://localhost/
  1. <- Leopard Django
  2. Less Design is More ->