NAME

Dancer2::Plugin::ContentCache - Cache HTML/JSON responses for later use.

VERSION

version 1.0000

SYNOPSIS

# In config.yml:
#
plugins:
  ContentCache:
     # REQUIRED, unless you supply your own 'driver' (see DRIVERS, below).
     cache_result_set: ContentCache

     # Optional, with defaults
     driver: DBIx::Class
     schema: default
     create_redirect_route: 1
     redirect_route: '/recall'
     require_login: 0
     cache_aging: 1
     default_life: 86400

# In your Dancer2 application
#
package MyApp;
use Dancer2 appname => 'MyApp';
use Dancer2::Plugin::ContentCache;

post '/this_route' => sub {
  # This route is supposed to return HTML

  my $html = template 'Foo',
             { param1 => 'bar' },
             { layout => 'some_layout'};

  cache_and_redirect $html,
             { life => 6000, created_by => 'baz' };
             # metadata; note 'life'
};

post '/this_other_route' => sub {
  # do some processing
  # This route is supposed to return JSON

  cache_and_send $results, { created_by => 'baz' };
  # $results is a hashref or arrayref.
};

#
# Example of custom cache handler:
#
get '/retrieve_privileged/:uuid' => sub {

   my $cache = retrieve_cache route_parameters->get('uuid');
   my $all_good = 0;

   # Use the stuff in $cache->metadata to decide if you want to send this
   # to the user, and set $all_good as appropriate.

   if ($all_good) {
      send_as $cache->data_format => $cache->data;
   }
   else {
      return send_error('Not Found', 404);
   }
};

DESCRIPTION

This plugin for Dancer2 implements a response-content cache and optionally issues a redirect to that content. In particular, this is useful after a POST route; if the network times out (as can happen on wonky connections), you don't want the user to re-POST. By redirecting them to the now-cached content as a GET, you prevent re-POSTing.

Storage is never touched directly by this plugin; every read and write goes through a small driver object, so you are not locked into DBIx::Class. See "DRIVERS", below.

CONFIGURATION

In your config.yml for your Dancer2 application, use these configuration parameters:

driver

This optional configuration parameter is to allow use of some other driver for the database than the default, which is DBIx::Class. See "DRIVERS".

schema

This parameter lets you specify which schema is the one containing your result set (table). Default value is 'default', which is what's usually used when you only have one. Only used by the default DBIx::Class driver, which will use whichever of Dancer2::Plugin::DBIC or Dancer2::Plugin::DBIx::Class your application has loaded (preferring DBIC if both are present).

cache_result_set

Mandatory, if the driver is the default DBIx::Class. This setting points at the ResultSet name in which the table will be stored.

create_redirect_route

Default is 1, true. The system will create a default route to retrieve and return the cache. The name will be set in redirect_route, below, and will expect a single route parameter, the UUID of the cache entry.

If set to 0, false, then no route will be created, and it's assumed you will roll your own, using the retrieve_cache keyword below.

redirect_route

The name of the built-in redirect route, if it is created. Default is '/recall'.

require_login

If the built-in redirect route is created, is login required? If this is set to 1, true, AND you are using Dancer2::Plugin::Auth::Extensible, then the created route will check for logged_in_user before returning the data. If you are not using the Extensible plugin, or the user is not logged in, they will be returned a 404 error.

cache_aging

Default is 1, true. Set this to 0, false, to disable cache aging. Note in "BUGS AND LIMITATIONS", below, that if you have aging on, your driver must be able to record the two necessary fields. The plugin will croak at application start time, if the necessary bits aren't there.

default_life

Default is 86400 seconds (1 day). If cache_aging is on, this sets the default life of the cache entries, if not specified by life metadata in the cache entry.

DANCER2 KEYWORDS

cache_and_send

This keyword replaces send_as [html|JSON] in your Dancer2 route to first cache the required data, then ship it along without a redirect pointing at the cache.

cache_and_redirect

This keyword also replaces send_as [html|JSON] in your Dancer2 route to first cache the required data, then redirect the user to the cache-retrieval link.

retrieve_cache

Given a UUID of the cache, attempts to retrieve the cache with that key. If the cache is already expired, or doesn't exist under that UUID, return undef. With an unexpired cache, this keyword returns a cache object (see "THE CACHE OBJECT" for details).

set_cache

This is a convenience method for pushing a cache. It expects either some HTML (a scalar) or JSON (a hashref or arrayref), and an additional hashref of optional metadata, and returns the UUID of the cache created.

# A JSON cache, with five minutes of life.
my $uuid = set_cache $hashref, { life => 300 };

# Another JSON cache, this time of an arrayref, with default life.
my $uuid = set_cache $arrayref, {};

# An html/scalar cache, with the default life.
my $uuid = set_cache $html;
clean_up_cache

This convenience term should be run from time to time if cache_aging is turned on. It will look through the store and delete all expired caches, returning the number of expired entities. If cache_aging is off, it will always return zero. You might put this in an admin route for super-users, or it can be included in a cron job to run from time to time.

THE CACHE OBJECT

The cache object, Dancer2::Plugin::ContentCache::CacheEntry, is a simple Moo object containing the following field/methods:

data_format

This will return html if the original data sent into the cache was HTML or some other scalar, and JSON if a hashref or arrayref was put in the cache. Useful for send_as-ing a retrieved cache.

data

The data that was placed in the cache, exactly as it was sent in with cache_and_send or set_cache--either a scalar, arrayref, or hashref.

created_dt

If the driver is able to record a creation time (see "DRIVERS"), it is automatically set at cache create time, and may be retrieved here as a DateTime object. If not, this will return undefined. This behavior (unlike expiry_dt below) is not dependent on the value of cache_aging, merely the driver's capability.

expiry_dt

If cache_aging is turned on, this will recall the DateTime that the cache will expire. If not, this will return undefined.

metadata

A hashref containing all the metadata stored with this object. The life will always be present if the cache_aging setting is turned on.

Note that life is the only reserved metadata term. If aging is turned on, it will be automatically set by the default unless you override it with a value.

DRIVERS

Dancer2::Plugin::ContentCache never touches a database (or anything else) directly; all storage happens through a driver class that consumes the Dancer2::Plugin::ContentCache::Driver role. The bundled default, Dancer2::Plugin::ContentCache::Driver::DBIC, stores entries with DBIx::Class via Dancer2::Plugin::DBIx::Class or Dancer2::Plugin::DBIC, whichever your application is using..

To use a different backend, write a class that consumes Dancer2::Plugin::ContentCache::Driver and set the driver configuration option to its full package name:

plugins:
  ContentCache:
    driver: MyApp::ContentCache::Driver::Redis

See Dancer2::Plugin::ContentCache::Driver for the small set of methods your driver needs to implement.

SUGGESTED SCHEMA

Here's the sort of schema you'll need for the default DBIx::Class driver (this one, for PostgreSQL). If you're using more-recent versions of PostgreSQL that have a UUID type, you could use that, but this is the lowest-common-denominator form of the schema:

CREATE TABLE IF NOT EXISTS content_cache (
  uuid TEXT NOT NULL PRIMARY KEY,         -- Or "id", your choice.
  metadata TEXT NOT NULL,                 -- REQUIRED
  data TEXT NOT NULL,                     -- REQUIRED
  created_dt TIMESTAMP WITHOUT TIME ZONE, -- Plugin will always assume "local"
                                          -- unless you store it WITH TIMEZONE
                                          -- Only needed if cache_aging is on.
  expiry_dt TIMESTAMP WITHOUT TIME ZONE   -- Same here as with created_dt
);

DEPENDENCIES

BUGS AND LIMITATIONS

  • This tool provides no mechanism for "refreshing" or "updating" a cache. Once an entry is created, it is immutable, both in content and in its expiry. That may be a limitation, but we may also call it a feature, depending on your mindset.

  • If you specify cache_aging, but your driver cannot record created_dt and expiry_dt (for the default DBIx::Class driver: your schema does not have those two columns), aging won't work!

ACKNOWLEDGEMENTS

The idea for this plugin comes from Mike Weisenborn of Clearbuilt. Clearbuilt graciously sponsored the development of this work.

SEE ALSO

  • Dancer2::Plugin::CHI is a less-feature-rich key-value cache that could be used for some of what we're doing here. If this one is doing too much for your tastes, it's a good choice.

  • Dancer2::Plugin::ViewCache also stores HTML page content, but is deliberately intended for "guest" viewing without requiring a login.

AUTHOR

D Ruth Holloway <ruth@hiruthie.me>

COPYRIGHT AND LICENSE

This software is copyright (c) 2026 by D Ruth Holloway.

This is free software; you can redistribute it and/or modify it under the same terms as the Perl 5 programming language system itself.