Subject: | Allow PageCache to use HTTP Headers for expiry time |
Consolidates the logic of selecting an expiration time to a single function and adds in the ability (used by default) for HTTP headers to provide the values.
It consider Cache-Control and Expiry. This is a work in progress and no tests have been added yet. The real catch is the module now requires DateTime::Format::HTTP in order to parse the Expiry header.
The big upshot of this is that Applications setting a Cache-Control header for client browsers will now automatically take benefit of that same value for PageCache based caching.
Current state of progress attached and may be applied. More testing for the headers should be done.
It consider Cache-Control and Expiry. This is a work in progress and no tests have been added yet. The real catch is the module now requires DateTime::Format::HTTP in order to parse the Expiry header.
The big upshot of this is that Applications setting a Cache-Control header for client browsers will now automatically take benefit of that same value for PageCache based caching.
Current state of progress attached and may be applied. More testing for the headers should be done.
Subject: | PageCache.pm.patch |
*** Catalyst-Plugin-PageCache-0.22.orig/lib/Catalyst/Plugin/PageCache.pm 2009-06-25 06:16:57.000000000 -0400
--- Catalyst-Plugin-PageCache-0.22/lib/Catalyst/Plugin/PageCache.pm 2010-01-02 19:45:08.000000000 -0500
***************
*** 3,8 ****
--- 3,10 ----
use strict;
use base qw/Class::Accessor::Fast/;
use MRO::Compat;
+ use DateTime;
+ use DateTime::Format::HTTP;
our $VERSION = '0.22';
***************
*** 37,48 ****
$expires = $expires->epoch - time
if ref($expires) && $expires->isa('DateTime');
! $expires ||= $c->config->{'Plugin::PageCache'}->{expires};
! # mark the page for caching during finalize
! if ( $expires > 0 ) {
! $c->_cache_page( { cache_seconds => $expires } );
! }
}
sub clear_cached_page {
--- 39,51 ----
$expires = $expires->epoch - time
if ref($expires) && $expires->isa('DateTime');
! # Mark that cache_page has been called but defer on selecting the time period
! # to cache for until finalize. _get_expiration_time has more options at that
! # time, such as various headers which may have been set.
! my $options = {};
! $options->{cache_seconds} = $expires if ($expires and $expires > 0);
! $c->_cache_page( $options );
}
sub clear_cached_page {
***************
*** 81,86 ****
--- 84,154 ----
}
}
+
+ # Internal function to return the time that the item should expire
+ # from the cache using all available resources, including HTTP headers
+ # of the outgoing response.
+ sub _get_expiration_time {
+ my ($c, $options) = @_;
+ my $is_debug = $c->config->{'Plugin::PageCache'}->{debug};
+
+ my $expires;
+
+ # Use the explicitely passed in time if available.
+ if ($options->{cache_seconds} and $options->{cache_seconds} > 0) {
+ $c->log->debug("Set expires time via provided value") if ($is_debug);
+
+ $expires = time() + $options->{cache_seconds};
+ }
+
+ # Use the value from Cache-Control if available. Respect no-cache and no-store
+ # directives as well by setting $expires to immediate.
+ elsif (my $cc_header = $c->response->headers->header('Cache-Control')) {
+ $c->log->debug("Set expires time via Cache-Control header: $cc_header") if ($is_debug);
+ if ($cc_header !~ 'no-cache|no-store|must-revalidate') { # Expire immediately
+ $expires = time();
+ }
+
+ # s-maxage overrides max-age and the Expires header
+ # Both s-maxage and max-age coould be provided so we use two separate
+ # if blocks to handle them in the correct order.
+ elsif ($cc_header =~ m/^s-maxage\s*=\s*(\d+)/g) {
+ my $seconds = $1;
+ $expires = time() + $seconds;
+ }
+ elsif ($cc_header =~ m/%^max-age\s*=\s*(\d+)/g) {
+ my $seconds = $1;
+ $expires = time() + $seconds;
+ }
+
+ # Sent by itself, we are allowed to cache for some unspecified time period.
+ # Use the default amount provided by the configuration file.
+ # Private is okay since we assume the catalyst server will be private.
+ elsif ($cc_header =~ 'public|private') {
+ $expires = time() + $c->config->{'Plugin::PageCache'}->{expires};
+ }
+
+ }
+ # Look for and use the value from the Expires header if configured
+ elsif (my $expiry_header = $c->response->headers->header('Expires')) {
+ $c->log->debug("Set expires time via Expires header: $expiry_header") if ($is_debug);
+
+ my $dt = DateTime::Format::HTTP->parse_datetime($expiry_header);
+
+ if ($dt and $dt->epoch() > time()) {
+ $expires = $dt->epoch();
+ }
+ }
+
+ # If all else fails, fallback to the default configuration value.
+ else {
+ $c->log->debug("Set expires time via to default time") if ($is_debug);
+ $expires = time() + $c->config->{'Plugin::PageCache'}->{expires}
+ }
+
+ return $expires;
+ }
+
sub dispatch {
my $c = shift;
***************
*** 272,281 ****
my $now = time;
- $c->log->debug(
- "Caching page $key for $options->{cache_seconds} seconds"
- ) if ($c->config->{'Plugin::PageCache'}->{debug});
-
# Cache some additional metadata along with the content
# Some caches don't support expirations, so we do it manually
my $data = {
--- 340,345 ----
***************
*** 285,292 ****
create_time => $options->{last_modified}
|| $c->res->headers->last_modified
|| $now,
! expire_time => $now + $options->{cache_seconds},
};
if ( $c->config->{'Plugin::PageCache'}->{cache_headers} ) {
$data->{headers} = {
--- 349,361 ----
create_time => $options->{last_modified}
|| $c->res->headers->last_modified
|| $now,
! expire_time => $c->_get_expiration_time($options),
};
+
+ $c->log->debug(
+ "Caching page $key for ". ($data->{expire_time} - time()) ." seconds"
+ ) if ($c->config->{'Plugin::PageCache'}->{debug});
+
if ( $c->config->{'Plugin::PageCache'}->{cache_headers} ) {
$data->{headers} = {
***************
*** 466,471 ****
--- 535,543 ----
This plugin requires that you also load a Cache plugin. Please see the Known
Issues when choosing a cache backend.
+ When possible PageCache will use the existing Content-Control or Expires headers
+ for defining the duration a page will remain in the cache.
+
=head1 WARNINGS
PageCache should be placed at the end of your plugin list.