Arithmetic fun with mod_rewrite

on 20 Dec 2011 by Mukund (@muks)

I've been coding for the Banu Shop which will be opened soon. We store product images after a bit of processing in filesystem directories of this syntax:


where int2 is the product identifier (positive integer) and int1 = int2/10000. This two-level directory structure is so that we don't have tens of thousands of sub-directories in a single directory. There are other approaches such as using hashes and substrings of them, but our approach is fine for us. So sample image filepaths are:


HTML product pages refer to their images using relative urls like "./images/978-81-250-3947-1.jpg". So we eventually need a URL like:

to be rewritten to a passthrough URL:

We already use mod_rewrite for a lot of the rewrites, but it can't directly generate int1 in the URLs because there's arithmetic involved (int1 = int2/10000). After some very hacky solutions, I found that mod_rewrite does have a facility for custom URL mapping. The prg: style external program is a no-go as it would be too inefficient. But the internal compiled map function was very appealing.

This is just a dump of code for anyone else who wants to get something like this done, or any other arithmetic for that matter. The following Apache httpd module is loaded as a plug-in and generates int1 for the URLs above, which can be used in the rewrite rules.

#include <stdlib.h>
#include <apr_strings.h>
#include <httpd.h>
#include <http_protocol.h>
#include <http_config.h>
#include <http_core.h>
#include <http_log.h>

typedef char * (map_t) (request_rec *r, char *key);
APR_DECLARE_OPTIONAL_FN (void, ap_register_rewrite_mapfunc,
                         (char *name, map_t *func));

static char *
banu_product_id_map (request_rec *req,
                     char        *key)
  unsigned long d;

  d = atol (key);
  d /= 10000;

  return apr_ltoa (req->pool, d);

static int
pre_config (apr_pool_t *pool,
            apr_pool_t *plog,
            apr_pool_t *ptemp)
  APR_OPTIONAL_FN_TYPE (ap_register_rewrite_mapfunc) *fn;

  fn = APR_RETRIEVE_OPTIONAL_FN (ap_register_rewrite_mapfunc);
  if (!fn) {
    ap_log_error (APLOG_MARK, APLOG_CRIT, 0, 0,
                  "mod_banu: Error registering map function");

  fn ("banu_product_id_map", banu_product_id_map);

  return OK;

static void
banu_hooks (apr_pool_t *pool)
  static const char * const pre_modules[] = {

  ap_hook_pre_config (pre_config, pre_modules, NULL, APR_HOOK_MIDDLE);

module AP_MODULE_DECLARE_DATA banu_module = {

Download mod_banu.c.

Update: I've updated the code to divide the id by 10000 instead of 1000 before. Also, here's how to setup Apache httpd to use the module. First, compile and install the module (as root):

apxs -ci mod_banu.c

Then, add the following line to your main httpd.conf:

LoadModule banu_module modules/

Then, configure the rewrite rule in your virtual host. For the example above, I use something like:

RewriteMap bm int:banu_product_id_map
RewriteRule ^/shop/([0-9]+)/([0-9a-zA-Z\.\_\-]+)/images/(.*)$ \
            /static/shop/products/${bm:$1}/$1/images/$3       \