File Coverage

blib/lib/Dancer/Plugin/Cache/CHI.pm
Criterion Covered Total %
statement 17 19 89.4
branch n/a
condition n/a
subroutine 7 7 100.0
pod n/a
total 24 26 92.3


line stmt bran cond sub pod time code
1             package Dancer::Plugin::Cache::CHI;
2             BEGIN {
3 5     5   1653351 $Dancer::Plugin::Cache::CHI::AUTHORITY = 'cpan:YANICK';
4             }
5             {
6             $Dancer::Plugin::Cache::CHI::VERSION = '1.4.0';
7             }
8             # ABSTRACT: Dancer plugin to cache response content (and anything else)
9              
10 5     5   55 use strict;
  5         8  
  5         138  
11 5     5   26 use warnings;
  5         9  
  5         131  
12 5     5   24 no warnings qw/ uninitialized /;
  5         6  
  5         262  
13              
14 5     5   24 use Dancer 1.1904 ':syntax';
  5         212  
  5         40  
15              
16             my $dancer_version = int Dancer->VERSION;
17              
18 5     5   2254 use Carp;
  5         11  
  5         358  
19 5     5   882903 use CHI;
  0            
  0            
20              
21             use Dancer::Plugin;
22              
23             use Moo;
24              
25             if ( $dancer_version < 2 ) {
26             require Dancer::Hook;
27             require Dancer::Factory::Hook;
28             require Dancer::Response;
29             require Dancer::SharedData;
30             }
31             else {
32             with 'Dancer::Plugin';
33              
34             register_hook 'before_create_cache';
35             }
36              
37              
38             my %cache;
39             my $cache_page; # actually hold the ref to the args
40             my $cache_page_key_generator = sub {
41             return request()->{path_info};
42             };
43              
44             hook after => sub {
45             return unless $cache_page;
46              
47             my $resp = shift;
48             cache()->set( $cache_page_key_generator->(),
49             {
50             status => $resp->status,
51             headers => $resp->headers_to_array,
52             content => $resp->content
53             },
54             @$cache_page,
55             );
56              
57             $cache_page = undef;
58             };
59              
60             register cache => sub {
61             shift if $dancer_version >= 2;
62             return $cache{$_[0]//''} ||= _create_cache( @_ );
63             };
64              
65             my $honor_no_cache = 0;
66              
67             sub _create_cache {
68             my $namespace = shift;
69             my $args = shift || {};
70              
71             if ( $dancer_version < 2 ) {
72             Dancer::Factory::Hook->execute_hooks( 'before_create_cache' );
73             }
74             else {
75             execute_hook 'before_create_cache';
76             }
77              
78             my %setting = %{ plugin_setting() };
79              
80             $setting{namespace} = $namespace if defined $namespace;
81              
82             while( my ( $k, $v ) = each %$args ) {
83             $setting{$k} = $v;
84             }
85              
86             $honor_no_cache = delete $setting{honor_no_cache}
87             if exists $setting{honor_no_cache};
88              
89             return CHI->new(%setting);
90             }
91              
92              
93              
94             register check_page_cache => sub {
95             shift if $dancer_version >= 2;
96              
97             my $hook = sub {
98             my $dsl = shift if $dancer_version >= 2;
99              
100             # Instead halt() now we use a more correct method - setting of a
101             # response to Dancer::Response object for a more correct returning of
102             # some HTTP headers (X-Powered-By, Server)
103              
104             my $cached = cache()->get( $cache_page_key_generator->() )
105             or return;
106              
107             if ( $honor_no_cache ) {
108             $DB::single = 1;
109              
110             my $req = $dancer_version >=2
111             ? $dsl->request
112             : Dancer::SharedData->request
113             ;
114              
115             return if grep {
116             # eval is there to protect from a regression in Dancer 1.31
117             # where headers can be undef
118             eval { $req->header($_) eq 'no-cache' }
119             } qw/ Cache-Control Pragma /;
120             }
121              
122             if ( $dancer_version < 2 ) {
123             Dancer::SharedData->response(
124             Dancer::Response->new(
125             ref $cached eq 'HASH'
126             ?
127             (
128             status => $cached->{status},
129             headers => $cached->{headers},
130             content => $cached->{content}
131             )
132             :
133             ( content => $cached )
134             )
135             );
136             } else {
137             $dsl->response(
138             Dancer::Core::Response->new(
139             is_halted => 1,
140             ref $cached eq 'HASH'
141             ?
142             (
143             status => $cached->{status},
144             headers => $cached->{headers},
145             content => $cached->{content}
146             )
147             :
148             ( content => $cached )
149             )
150             );
151             }
152             };
153              
154             hook before => $hook;
155             };
156              
157              
158             register cache_page => sub {
159             shift if $dancer_version >= 2;
160              
161             my ( $content, @args ) = @_;
162              
163             $cache_page = \@args;
164              
165             return $content;
166             };
167              
168              
169              
170             register cache_page_key => sub {
171             shift if $dancer_version >= 2;
172             return $cache_page_key_generator->();
173             };
174              
175              
176             register cache_page_key_generator => sub {
177             shift if $dancer_version >= 2;
178             $cache_page_key_generator = shift;
179             };
180              
181              
182             for my $method ( qw/ set get remove clear compute / ) {
183             register 'cache_'.$method => sub {
184             shift if $dancer_version >= 2;
185             return cache()->$method( @_ );
186             }
187             }
188              
189             Dancer::Factory::Hook->instance->install_hooks(qw/ before_create_cache /)
190             if $dancer_version < 2;
191              
192              
193             register_plugin for_versions => [1,2];
194              
195             __END__
196              
197             =pod
198              
199             =head1 NAME
200              
201             Dancer::Plugin::Cache::CHI - Dancer plugin to cache response content (and anything else)
202              
203             =head1 VERSION
204              
205             version 1.4.0
206              
207             =head1 SYNOPSIS
208              
209             In your configuration:
210              
211             plugins:
212             'Cache::CHI':
213             driver: Memory
214             global: 1
215              
216             In your application:
217              
218             use Dancer ':syntax';
219             use Dancer::Plugin::Cache::CHI;
220              
221             # caching pages' response
222              
223             check_page_cache;
224              
225             get '/cache_me' => sub {
226             cache_page template 'foo';
227             };
228              
229             # using the helper functions
230              
231             get '/clear' => sub {
232             cache_clear;
233             };
234              
235             put '/stash' => sub {
236             cache_set secret_stash => request->body;
237             };
238              
239             get '/stash' => sub {
240             return cache_get 'secret_stash';
241             };
242              
243             del '/stash' => {
244             return cache_remove 'secret_stash';
245             };
246              
247             # using the cache directly
248              
249             get '/something' => sub {
250             my $thingy = cache->compute( 'thingy', sub { compute_thingy() } );
251              
252             return template 'foo' => { thingy => $thingy };
253             };
254              
255             =head1 DESCRIPTION
256              
257             This plugin provides Dancer with an interface to a L<CHI> cache. Also, it
258             includes a mechanism to easily cache the response of routes.
259              
260             =head1 CONFIGURATION
261              
262             Unrecognized configuration elements are passed directly to the L<CHI> object's
263             constructor. For example, the configuration given in the L</SYNOPSIS>
264             will create a cache object equivalent to
265              
266             $cache = CHI->new( driver => 'Memory', global => 1, );
267              
268             =head2 honor_no_cache
269              
270             If the parameter 'C<honor_no_cache>' is set to true, a request with the http
271             header 'C<Cache-Control>' or 'C<Pragma>' set to 'I<no-cache>' will ignore any
272             content cached via 'C<cache_page>' and will have the page regenerated anew.
273              
274             =head1 KEYWORDS
275              
276             =head2 cache
277              
278             Returns the L<CHI> cache object.
279              
280             =head2 cache $namespace, \%args
281              
282             L<CHI> only allows one namespace per object. But you can create more caches by
283             using I<cache $namespace, \%args>. The new cache uses the arguments as defined in
284             the configuration, which values can be overriden by the optional arguments
285             (which are only used on the first invocation of the namespace).
286              
287             get '/memory' => sub {
288             cache('elephant')->get( 'stuff' );
289             };
290              
291             get '/goldfish' => sub {
292             cache( 'goldfish' => { expires_in => 300 } )->get( 'stuff' );
293             };
294              
295             Note that all the other keywords (C<cache_page>, C<cache_set>, etc) will still
296             use the main cache object.
297              
298             =head2 check_page_cache
299              
300             If invoked, returns the cached response of a route, if available.
301              
302             The C<path_info> attribute of the request is used as the key for the route,
303             so the same route requested with different parameters will yield the same
304             cached content. Caveat emptor.
305              
306             =head2 cache_page($content, $expiration)
307              
308             Caches the I<$content> to be served to subsequent requests.
309             The headers and http status of the response are also cached.
310              
311             The I<$expiration> parameter is optional.
312              
313             =head2 cache_page_key
314              
315             Returns the cache key used by 'C<cache_page>'. Defaults to
316             to the request's I<path_info>, but can be modified via
317             I<cache_page_key_generator>.
318              
319             =head2 cache_page_key_generator( \&sub )
320              
321             Sets the function that generates the cache key for I<cache_page>.
322              
323             For example, to have the key contains both information about the request's
324             hostname and path_info (useful to deal with multi-machine applications):
325              
326             cache_page_key_generator sub {
327             return join ':', request()->host, request()->path_info;
328             };
329              
330             =head2 cache_set, cache_get, cache_remove, cache_clear, cache_compute
331              
332             Shortcut to the cache's object methods.
333              
334             get '/cache/:attr/:value' => sub {
335             # equivalent to cache->set( ... );
336             cache_set $params->{attr} => $params->{value};
337             };
338              
339             See the L<CHI> documentation for further info on these methods.
340              
341             =head1 HOOKS
342              
343             =head2 before_create_cache
344              
345             Called before the creation of the cache, which is lazily done upon
346             its first use.
347              
348             Useful, for example, to change the cache's configuration at run time:
349              
350             use Sys::Hostname;
351              
352             # set the namespace to the current hostname
353             hook before_create_cache => sub {
354             config->{plugins}{'Cache::CHI'}{namespace} = hostname;
355             };
356              
357             =head1 SEE ALSO
358              
359             Dancer Web Framework - L<Dancer>
360              
361             L<CHI>
362              
363             L<Dancer::Plugin::Memcached> - plugin that heavily inspired this one.
364              
365             =head1 AUTHOR
366              
367             Yanick Champoux <yanick@cpan.org>
368              
369             =head1 COPYRIGHT AND LICENSE
370              
371             This software is copyright (c) 2011 by Yanick Champoux.
372              
373             This is free software; you can redistribute it and/or modify it under
374             the same terms as the Perl 5 programming language system itself.
375              
376             =cut