Using Drupal Imagecache with Lighttpd proxied Apache

This article shows how to serve imagecache images with Apache if the website is proxied via lighttpd.

Using Lighttpd as a proxy for Apache is a popular solution to achieve smaller web server memory footprint and to speed up websites. However things can get messy when it comes to Drupal and the brilliant Imagecache module. Imagecache transforms images using presets and then caches them.

An imagecache url is something like: http://domain.tld/sites/default/files/imagecache/presetname/photo.jpg

This will take the the image placed at sites/default/files/photo.jpg and transforms it according to presetname. After succesful operation the image will be available as a static file at sites/default/files/imagecache/presetname/photo.jpg

No need to say, this functionality breaks when the frontend web server only forwards requests to php files so imagecache images won’t show up and will not be generated.

One solution is: set up a separate subdomain to serve these images and redirect all imagecache images to this subdomain. This needs some simple LUA scripting, Lighttpd will forward an imagecache url if it hasn’t created yet, and will serve it directly if it’s already there.

Four steps to get this working:

Configure Lighttpd

Create this little LUA script:

attr = lighty.stat(lighty.env['physical.path'])
if (not attr) then
  cuthere = string.find(lighty.env['uri.authority'], '.', 1, true) + 1
  redirhost = string.sub(lighty.env['uri.authority'], cuthere)
  lighty.header["Location"] = "http://" .. redirhost  ..
lighty.env["request.orig-uri"]
  return 302
end

This script will stat for the file requested and if not found, redirect the request to the main domain. Link the lua script to the vhost configuration (and enable mod_magnet of course if you haven’t done already):

magnet.attract-physical-path-to = ( "/etc/lighttpd/filecheckredirect.lua" )

Set up the subdomain

Configure your subdomain or multiple subdomains for imagecache hosted images and set this in the Drupal settings.php:

/**
 * Alternate hosts for imagecache created images.
 * A string can be set for single hosts,
 * An array of hosts can be set for multiple hosts.
 */
$conf['static_file_hosts'] = array(
  'ic1.example.com',
  'ic2.example.com',
);
//$conf['static_file_hosts'] = 'ic.example.com';

Override imagecache theme function

/**
 * Replace the domain part of imagecache urls with 'static_file_hosts' if
 * available.
 *
 * 'static_file_hosts' can be set from settings.php.
 */
function YOURTHEME_imagecache($presetname, $path, $alt = '', $title = '', $attributes = NULL, $getsize = TRUE) {
  // Check is_null() so people can intentionally pass an empty array of
  // to override the defaults completely.
  if (is_null($attributes)) {
    $attributes = array('class' => 'imagecache imagecache-'. $presetname);
  }
  if ($getsize && ($image = image_get_info(imagecache_create_path($presetname, $path)))) {
    $attributes['width'] = $image['width'];
    $attributes['height'] = $image['height'];
  }
  $attributes = drupal_attributes($attributes);

  $domain = variable_get('static_file_hosts', $GLOBALS['base_url']);
  if (is_array($domain) && !empty($domain)) {
    if (count($domain) > 1) {
      $domain = $domain[rand(0, count($domain) - 1)];
    }
    else {
      $domain = $domain[0];
    }
  }
  if ($domain !== $GLOBALS['base_url']) {
    $base_url_parsed = parse_url($GLOBALS['base_url']);
    $imagecache_url = str_replace($GLOBALS['base_url'], $base_url_parsed['scheme'] . '://' . $domain, imagecache_create_url($presetname, $path));
  }
  else {
    $imagecache_url = imagecache_create_url($presetname, $path);
  }

  return '<img src="'. $imagecache_url .'" alt="'. check_plain($alt) .'" title="'. check_plain($title) .'" '. $attributes .' />';
}

download this snippet.

Patch imagecache:

The patch below is for imagecache version 6.x-2.0-beta10:

--- imagecache.module  19 Aug 2009 20:59:07 -0000  1.112.2.5
+++ imagecache.module
@@ -318,7 +318,15 @@
   $args = array('absolute' => TRUE, 'query' => empty($bypass_browser_cache) ? NULL : time());
   switch (variable_get('file_downloads', FILE_DOWNLOADS_PUBLIC)) {
     case FILE_DOWNLOADS_PUBLIC:
-      return url($GLOBALS['base_url'] . '/' . file_directory_path() .'/imagecache/'. $presetname .'/'. $path, $args);
+      $domain = variable_get('static_file_hosts', $GLOBALS['base_url']);
+      if ($domain !== $GLOBALS['base_url']) {
+        if (is_array($domain) && !empty($domain)) {
+          $domain = count($domain) > 1 ? $domain[rand(0, count($domain) - 1)] : $domain[0];
+          $base_url_parsed = parse_url($GLOBALS['base_url']);
+          $domain = $base_url_parsed['scheme'] . '://' . $domain;
+        }
+      }
+      return url($domain . '/' . file_directory_path() .'/imagecache/'. $presetname .'/'. $path, $args);
     case FILE_DOWNLOADS_PRIVATE:
       return url('system/files/imagecache/'. $presetname .'/'. $path, $args);
   }

Final steps

Empty the theme registry and watch your imagecache files served from ic.example.com using Apache the first time and using Lighttpd after image generation.

Update

The solution above is only suitable in scenarios where a reverse proxy is used for static file handling. I believe that setting up a Lighttpd instance in front of Apache is not the best way to serve a Drupal page. According to my experience it is better to run either nginx as a reverse proxy or just have nginx handle all your pages via php-fpm. Or you can strip down Apache and use mpm_worker with php in fastcgi mode, however that is not my cup of tea either.

Last updated on by Attila