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
DBICif both are present). cache_result_set-
Mandatory, if the
driveris 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 inredirect_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 theretrieve_cachekeyword 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 forlogged_in_userbefore 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 to0, 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 willcroakat application start time, if the necessary bits aren't there. default_life-
Default is 86400 seconds (1 day). If
cache_agingis on, this sets the default life of the cache entries, if not specified bylifemetadata 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_agingis turned on. It will look through the store and delete all expired caches, returning the number of expired entities. Ifcache_agingis 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
htmlif the original data sent into the cache was HTML or some other scalar, andJSONif a hashref or arrayref was put in the cache. Useful forsend_as-ing a retrieved cache. - data
-
The data that was placed in the cache, exactly as it was sent in with
cache_and_sendorset_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_dtbelow) is not dependent on the value ofcache_aging, merely the driver's capability. - expiry_dt
-
If
cache_agingis 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
lifewill always be present if thecache_agingsetting is turned on.Note that
lifeis 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
Dancer2, obviously.
Dancer2::Plugin::DBIC or Dancer2::Plugin::DBIx::Class, if you're using the default driver.
DateTime::Format::Strptime, if you're using the default driver.
JSON::MaybeXS, which requires any one of three different JSON processors; you probably already have at least one of them installed.
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 recordcreated_dtandexpiry_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.