Programming Caudium Cache
 
    How to program with the Caudium Cache
What is the Caudium Cache?
The Caudium Cache Sub-System appeared in Caudium 1.3 and is responsible for storing and retrieving data in many parts of the server as of 1.3, including rendered images in the configuration interface, and modules such as the htaccess module.
The Cache is highly modular in design, which should make tasks such as implementing other storage backends fairly easy.
The Cache is designed to speed up both server operation and also development of modules, pikescripts and the core server.
Why write another Cache?
The existing caches in Caudium (and I say caches because there are many) are each written for a specific purpose (images, files, responses) and there seemed to me to be a large amount of duplicated code. I thought that it would be an excellent idea to remove these caches and replace then with a more generic interface that is easy to use for all pike programming within Caudium.
Programming with the Cache
Whenever programming with the cache whether in a module, pikescript or the core server there are two things you need to do before you can use the cache effectively.

The first is that you need to inherit some helper functions from cachelib, by adding the following line to your code:

      inherit "cachelib";
    
The second is that you need to get hold of your very own cache with which to store and retrieve objects.
Something like this will do the job nicely:
      object cache = caudium->cache_manager->get_cache( module|namespace );
    
module is the module object that your wanting the cache for if you use this method (ie: object cache = caudium->cache_manager->get_cache( this_object() );) and as long as you have inherited caudiumlib then the cache_manager will sort out finding you an individual namespace without your needing to interfere. This is by far the most preferred method for you to get your cache!.
namespace is a string which uniquely identifies your cache from the potentially many other caches running inside the caching system - all namespaces are running seperately and managed by the cache_manager which does such nice things as monitoring memory and disk usage and shutting down caches that have expired halflives (they havent had a request in a set amount of time).
It is important that your namespace is unique in order to not contaminate data in other caches.
Usage of the configuration variable MyWorldLocation is a good starting place.
Retrieving Objects:
You may think it's a little odd to document cache retrieval before storage however it will make sense once you understand how it works.

When retrieving an object from a cache you call cache->retrieve() with the following arguments:
  • string name - The name of an object you have previously stored in the cache.
  • function get_callback - A function to call in the event of a cache miss.
  • array callback_arguments - Arguments that you want to supply to the get_callback
Storing Objects:
Storing objects is a little more complex, but not overly so.
This is where those helpful functions from cachelib come in handy. cachelib defines functions that allow you to store objects with the correct meta data for the objects type, the following functions are defined:
  • cache_file - You are storing a file, or more specifically you are storing an open Stdio.File object, the contents of which are read into the cache, and written into a new Stdio.File upon retrieval.
  • cache_pike - You are storing a standard pike datatype, such as a multiset, string or mapping.
  • cache_program - This is a slightly more specific handler for a chunk of pike bytecode.
  • cache_string - This is a more specific handler for pike strings.
  • cache_image - This handler knows how to store images or more specifically, pike Image.Image objects.
All of these handlers take three arguments:
  • mixed obj - The object you are storing.
  • string name - The name of the object.
  • void|int expire - The time in Unix timestamp format of when the object will expire, or, if it's less than the current time then it is added to the current time, to give a time in the future. Or, if it is set to -1 then the object is stored indeffinately. Also, if this variable is left void then a default time of five minutes in the future is used.
The output of the handler function (a mapping) is sent to cache->store().
Removing objects:
There are two ways to remove objects from the cache, one is for removing an individual object, and one is for removing large groups of objects.

To remove a single object cache->refresh( string name ) is used, and this causes the object with the corresponding name to be removed.

To remove large numbers of objects (or perhaps, the entire contents of the cache) you can use cache->flush( void|string regex ) The regex argument is optional, and if ommitted the entire contents will be removed from the cache. If a regular expression is supplied along with the call to flush then every object in the cache will have it's name tested against the regex and any matching objects will be removed.
Example code
The following functions are used to re-scale an image to a certain size and uses the cache in order to achieve this without re-scaling images that have already been scaled.

    inherit "module";
    #include <module.h>
    inherit "caudiumlib"; // Inherit the http helper functions.
    inherit "cachelib";  // Inherit the cache helper functions.

    constant module_type   = MODULE_EXTENSION;
    constant module_name   = "Caudium Cache Module Example";
    constant module_doc    = "See caudium.net web site for more example";
    constant module_unique = 1;
    constant thread_safe   = 1;
    constant cvs_version   = "$Id: cacheprog.rxml,v 1.8 2003/02/17 20:19:53 vida Exp $";

    object cache = caudium->cache_manager->get_cache( this_object() );
      // Get ourselves a cache to store stuff in.

    object scale_image( string path, int width, int height ) {
      // This function is a generic function that will open an image file
      // on the filesystem and decode it into a pike Image.Image object
      // and then scale it to the size requested.
      object img;
      object f = Stdio.File( path, "r" );
      string ext = lower_case( path[ sizeof( path ) - 1 ] );
      switch (ext) {
      case "gif":
        img = Image.GIF.decode( f->read() );
	break;
      case "jpg":
        img = Image.JPEG.decode( f->read() );
	break;
      case "png":
        img = Image.PNG.decode( f->read() );
	break;
      }
      f->close();
      img->scale( width, height );
      cache->store( 
                    cache_image( 
		                 img,
                                 sprintf( "%dx%d:%s", width, height, path ), 
                                 -1
                               )
                  );
      // Store the image in the cache with a name something like
      // "120x120:/var/www/background.gif" and tell the cache never to expire
      // the image.
      return img;
      // Return the scaled image.
    }

    object get_image( string path, int width, int height ) {
      if ( id->pragma->nocache ) {
      // If the request has the "nocache" header present then remove
      // it from the cache in advance before retrieving it.
        cache->refresh( sprintf( "%dx%d:%s", width, height, path ) );
      }
      return cache->retrieve(
                              sprintf( "%dx%d:%s", width, height, path ),
                              scale_image,
			      ({ path, width, height })
			    );
      // We're asking the cache to retrieve the image that is associated
      // with the name which is generated by the sprintf statement (see 
      // above), and we're telling it that if the object isn't there then
      // it should call scale_image( path, width, height ) to get it.
      // This way the cache will always return the object, even if it
      // wasn't in the cache, by calling the callback. The callback will
      // also store the image in the cache for any future requests.
    }

    mapping find_file( object id, string path ) {
    }
  
For any further questions contact James Tyson.
 
HTML OK CSS