# 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](https://metacpan.org/pod/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](https://metacpan.org/pod/DBIx%3A%3AClass). See ["DRIVERS"](#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](https://metacpan.org/pod/DBIx%3A%3AClass). See ["DRIVERS"](#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](https://metacpan.org/pod/DBIx%3A%3AClass) driver, which will use whichever of [Dancer2::Plugin::DBIC](https://metacpan.org/pod/Dancer2%3A%3APlugin%3A%3ADBIC) or [Dancer2::Plugin::DBIx::Class](https://metacpan.org/pod/Dancer2%3A%3APlugin%3A%3ADBIx%3A%3AClass) your application has loaded (preferring `DBIC` if both are present). - `cache_result_set` **Mandatory**, if the `driver` is the default [DBIx::Class](https://metacpan.org/pod/DBIx%3A%3AClass). 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](https://metacpan.org/pod/Dancer2%3A%3APlugin%3A%3AAuth%3A%3AExtensible), 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"](#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"](#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](https://metacpan.org/pod/Dancer2%3A%3APlugin%3A%3AContentCache%3A%3ACacheEntry), is a simple [Moo](https://metacpan.org/pod/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"](#drivers)), it is automatically set at cache create time, and may be retrieved here as a [DateTime](https://metacpan.org/pod/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](https://metacpan.org/pod/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](https://metacpan.org/pod/Dancer2%3A%3APlugin%3A%3AContentCache) never touches a database (or anything else) directly; all storage happens through a driver class that consumes the [Dancer2::Plugin::ContentCache::Driver](https://metacpan.org/pod/Dancer2%3A%3APlugin%3A%3AContentCache%3A%3ADriver) role. The bundled default, [Dancer2::Plugin::ContentCache::Driver::DBIC](https://metacpan.org/pod/Dancer2%3A%3APlugin%3A%3AContentCache%3A%3ADriver%3A%3ADBIC), stores entries with [DBIx::Class](https://metacpan.org/pod/DBIx%3A%3AClass) via [Dancer2::Plugin::DBIx::Class](https://metacpan.org/pod/Dancer2%3A%3APlugin%3A%3ADBIx%3A%3AClass) or [Dancer2::Plugin::DBIC](https://metacpan.org/pod/Dancer2%3A%3APlugin%3A%3ADBIC), whichever your application is using.. To use a different backend, write a class that consumes [Dancer2::Plugin::ContentCache::Driver](https://metacpan.org/pod/Dancer2%3A%3APlugin%3A%3AContentCache%3A%3ADriver) and set the `driver` configuration option to its full package name: plugins: ContentCache: driver: MyApp::ContentCache::Driver::Redis See [Dancer2::Plugin::ContentCache::Driver](https://metacpan.org/pod/Dancer2%3A%3APlugin%3A%3AContentCache%3A%3ADriver) 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](https://metacpan.org/pod/DBIx%3A%3AClass) 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](https://metacpan.org/pod/Dancer2), obviously. - [Dancer2::Plugin::DBIC](https://metacpan.org/pod/Dancer2%3A%3APlugin%3A%3ADBIC) or [Dancer2::Plugin::DBIx::Class](https://metacpan.org/pod/Dancer2%3A%3APlugin%3A%3ADBIx%3A%3AClass), if you're using the default driver. - [DateTime](https://metacpan.org/pod/DateTime) - [DateTime::Format::Strptime](https://metacpan.org/pod/DateTime%3A%3AFormat%3A%3AStrptime), if you're using the default driver. - [JSON::MaybeXS](https://metacpan.org/pod/JSON%3A%3AMaybeXS), which requires any one of three different JSON processors; you probably already have at least one of them installed. - [Module::Runtime](https://metacpan.org/pod/Module%3A%3ARuntime) - [UUID::Tiny](https://metacpan.org/pod/UUID%3A%3ATiny) # 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](https://clearbuilt.com). Clearbuilt graciously sponsored the development of this work. # SEE ALSO - [Dancer2::Plugin::CHI](https://metacpan.org/pod/Dancer2%3A%3APlugin%3A%3ACHI) 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](https://metacpan.org/pod/Dancer2%3A%3APlugin%3A%3AViewCache) also stores HTML page content, but is deliberately intended for "guest" viewing without requiring a login. # AUTHOR D Ruth Holloway # 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.