line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package CatalystX::Fastly::Role::Response; |
2
|
|
|
|
|
|
|
$CatalystX::Fastly::Role::Response::VERSION = '0.07'; |
3
|
1
|
|
|
1
|
|
896798
|
use Moose::Role; |
|
1
|
|
|
|
|
3
|
|
|
1
|
|
|
|
|
9
|
|
4
|
1
|
|
|
1
|
|
4426
|
use Carp; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
96
|
|
5
|
|
|
|
|
|
|
|
6
|
1
|
|
|
|
|
690
|
use constant CACHE_DURATION_CONVERSION => { |
7
|
|
|
|
|
|
|
s => 1, |
8
|
|
|
|
|
|
|
m => 60, |
9
|
|
|
|
|
|
|
h => 3600, |
10
|
|
|
|
|
|
|
d => 86_400, |
11
|
|
|
|
|
|
|
M => 2_628_000, |
12
|
|
|
|
|
|
|
y => 31_556_952, |
13
|
1
|
|
|
1
|
|
7
|
}; |
|
1
|
|
|
|
|
1
|
|
14
|
|
|
|
|
|
|
|
15
|
|
|
|
|
|
|
my $convert_string_to_seconds = sub { |
16
|
|
|
|
|
|
|
my $input = $_[0]; |
17
|
|
|
|
|
|
|
my $measure = chop($input); |
18
|
|
|
|
|
|
|
|
19
|
|
|
|
|
|
|
my $unit = CACHE_DURATION_CONVERSION->{$measure} || # |
20
|
|
|
|
|
|
|
carp |
21
|
|
|
|
|
|
|
"Unknown duration unit: $measure, valid options are Xs, Xm, Xh, Xd, XM or Xy"; |
22
|
|
|
|
|
|
|
|
23
|
|
|
|
|
|
|
carp "Initial duration start (currently: $input) must be an integer" |
24
|
|
|
|
|
|
|
unless $input =~ /^\d+$/; |
25
|
|
|
|
|
|
|
|
26
|
|
|
|
|
|
|
return $unit * $input; |
27
|
|
|
|
|
|
|
}; |
28
|
|
|
|
|
|
|
|
29
|
|
|
|
|
|
|
=head1 NAME |
30
|
|
|
|
|
|
|
|
31
|
|
|
|
|
|
|
CatalystX::Fastly::Role::Response - Methods for Fastly intergration to Catalyst |
32
|
|
|
|
|
|
|
|
33
|
|
|
|
|
|
|
=head1 SYNOPTIS |
34
|
|
|
|
|
|
|
|
35
|
|
|
|
|
|
|
package MyApp; |
36
|
|
|
|
|
|
|
|
37
|
|
|
|
|
|
|
... |
38
|
|
|
|
|
|
|
|
39
|
|
|
|
|
|
|
use Catalyst qw/ |
40
|
|
|
|
|
|
|
+CatalystX::Fastly::Role::Response |
41
|
|
|
|
|
|
|
/; |
42
|
|
|
|
|
|
|
|
43
|
|
|
|
|
|
|
extends 'Catalyst'; |
44
|
|
|
|
|
|
|
|
45
|
|
|
|
|
|
|
... |
46
|
|
|
|
|
|
|
|
47
|
|
|
|
|
|
|
package MyApp::Controller::Root |
48
|
|
|
|
|
|
|
|
49
|
|
|
|
|
|
|
sub a_page :Path('some_page') { |
50
|
|
|
|
|
|
|
my ( $self, $c ) = @_; |
51
|
|
|
|
|
|
|
|
52
|
|
|
|
|
|
|
$c->cdn_max_age('10d'); |
53
|
|
|
|
|
|
|
$c->browser_max_age('1d'); |
54
|
|
|
|
|
|
|
|
55
|
|
|
|
|
|
|
$c->add_surrogate_key('FOO','WIBBLE'); |
56
|
|
|
|
|
|
|
|
57
|
|
|
|
|
|
|
$c->response->body( 'Add cache and surrogate key headers' ); |
58
|
|
|
|
|
|
|
} |
59
|
|
|
|
|
|
|
|
60
|
|
|
|
|
|
|
=head1 DESCRIPTION |
61
|
|
|
|
|
|
|
|
62
|
|
|
|
|
|
|
This role adds methods to set appropreate cache headers in Catalyst responses, |
63
|
|
|
|
|
|
|
relating to use of a Content Distribution Network (CDN) and/or Cacheing |
64
|
|
|
|
|
|
|
proxy as well as cache settings for HTTP clients (e.g. web browser). It is |
65
|
|
|
|
|
|
|
specifically targeted at L<Fastly|https://www.fastly.com> but may also be |
66
|
|
|
|
|
|
|
useful to others. |
67
|
|
|
|
|
|
|
|
68
|
|
|
|
|
|
|
Values are converted and headers set in C<finalize_headers>. Headers |
69
|
|
|
|
|
|
|
affected are: |
70
|
|
|
|
|
|
|
|
71
|
|
|
|
|
|
|
=over 4 |
72
|
|
|
|
|
|
|
|
73
|
|
|
|
|
|
|
=item - |
74
|
|
|
|
|
|
|
|
75
|
|
|
|
|
|
|
Cache-Control: HTTP client (e.g. browser) and CDN (if Surrogate-Control not used) cache settings |
76
|
|
|
|
|
|
|
|
77
|
|
|
|
|
|
|
=item - |
78
|
|
|
|
|
|
|
|
79
|
|
|
|
|
|
|
Surrogate-Control: CDN only cache settings |
80
|
|
|
|
|
|
|
|
81
|
|
|
|
|
|
|
=item - |
82
|
|
|
|
|
|
|
|
83
|
|
|
|
|
|
|
Surrogate-Key: CDN only, can then later be used to purge content |
84
|
|
|
|
|
|
|
|
85
|
|
|
|
|
|
|
=item - |
86
|
|
|
|
|
|
|
|
87
|
|
|
|
|
|
|
Pragma: only set for for L<browser_never_cache> |
88
|
|
|
|
|
|
|
|
89
|
|
|
|
|
|
|
=item - |
90
|
|
|
|
|
|
|
|
91
|
|
|
|
|
|
|
Expires: only for L<browser_never_cache> |
92
|
|
|
|
|
|
|
|
93
|
|
|
|
|
|
|
=back |
94
|
|
|
|
|
|
|
|
95
|
|
|
|
|
|
|
=head1 TIME PERIOD FORMAT |
96
|
|
|
|
|
|
|
|
97
|
|
|
|
|
|
|
All time periods are expressed as: C<Xs>, C<Xm>, C<Xh>, C<Xd>, C<XM> or C<Xy>, |
98
|
|
|
|
|
|
|
e.g. seconds, minutes, hours, days, months or years, e.g. C<3h> is three hours. |
99
|
|
|
|
|
|
|
|
100
|
|
|
|
|
|
|
=head1 CDN METHODS |
101
|
|
|
|
|
|
|
|
102
|
|
|
|
|
|
|
=head2 cdn_max_age |
103
|
|
|
|
|
|
|
|
104
|
|
|
|
|
|
|
$c->cdn_max_age( '1d' ); |
105
|
|
|
|
|
|
|
|
106
|
|
|
|
|
|
|
Used to set B<max-age> in the B<Surrogate-Control> header, which CDN's use |
107
|
|
|
|
|
|
|
to determine how long to cache for. B<If I<not> supplied the CDN will use the |
108
|
|
|
|
|
|
|
B<Cache-Control> headers value> (as set by L</browser_max_age>). |
109
|
|
|
|
|
|
|
|
110
|
|
|
|
|
|
|
=cut |
111
|
|
|
|
|
|
|
|
112
|
|
|
|
|
|
|
has cdn_max_age => ( |
113
|
|
|
|
|
|
|
is => 'rw', |
114
|
|
|
|
|
|
|
isa => 'Maybe[Str]', |
115
|
|
|
|
|
|
|
); |
116
|
|
|
|
|
|
|
|
117
|
|
|
|
|
|
|
=head2 cdn_stale_while_revalidate |
118
|
|
|
|
|
|
|
|
119
|
|
|
|
|
|
|
$c->cdn_stale_while_revalidate('1y'); |
120
|
|
|
|
|
|
|
|
121
|
|
|
|
|
|
|
Applied to B<Surrogate-Control> only when L</cdn_max_age> is set, this |
122
|
|
|
|
|
|
|
informs the CDN how long to continue serving stale content from cache while |
123
|
|
|
|
|
|
|
it is revalidating in the background. |
124
|
|
|
|
|
|
|
|
125
|
|
|
|
|
|
|
=cut |
126
|
|
|
|
|
|
|
|
127
|
|
|
|
|
|
|
has cdn_stale_while_revalidate => ( |
128
|
|
|
|
|
|
|
is => 'rw', |
129
|
|
|
|
|
|
|
isa => 'Maybe[Str]', |
130
|
|
|
|
|
|
|
); |
131
|
|
|
|
|
|
|
|
132
|
|
|
|
|
|
|
=head2 cdn_stale_if_error |
133
|
|
|
|
|
|
|
|
134
|
|
|
|
|
|
|
$c->cdn_stale_if_error('1y'); |
135
|
|
|
|
|
|
|
|
136
|
|
|
|
|
|
|
Applied to B<Surrogate-Control> only when L</cdn_max_age> is set, this |
137
|
|
|
|
|
|
|
informs the CDN how long to continue serving stale content from cache |
138
|
|
|
|
|
|
|
if there is an error at the origin. |
139
|
|
|
|
|
|
|
|
140
|
|
|
|
|
|
|
=cut |
141
|
|
|
|
|
|
|
|
142
|
|
|
|
|
|
|
has cdn_stale_if_error => ( |
143
|
|
|
|
|
|
|
is => 'rw', |
144
|
|
|
|
|
|
|
isa => 'Maybe[Str]', |
145
|
|
|
|
|
|
|
); |
146
|
|
|
|
|
|
|
|
147
|
|
|
|
|
|
|
=head2 cdn_never_cache |
148
|
|
|
|
|
|
|
|
149
|
|
|
|
|
|
|
$c->cdn_never_cache(1); |
150
|
|
|
|
|
|
|
|
151
|
|
|
|
|
|
|
When true the a B<private> will be added to the B<Cache-Control> header |
152
|
|
|
|
|
|
|
this forces Fastly to never cache the results, no matter what other |
153
|
|
|
|
|
|
|
options have been set. |
154
|
|
|
|
|
|
|
|
155
|
|
|
|
|
|
|
=cut |
156
|
|
|
|
|
|
|
|
157
|
|
|
|
|
|
|
has cdn_never_cache => ( |
158
|
|
|
|
|
|
|
is => 'rw', |
159
|
|
|
|
|
|
|
isa => 'Bool', |
160
|
|
|
|
|
|
|
default => sub {0}, |
161
|
|
|
|
|
|
|
); |
162
|
|
|
|
|
|
|
|
163
|
|
|
|
|
|
|
=head1 BROWSER METHODS |
164
|
|
|
|
|
|
|
|
165
|
|
|
|
|
|
|
=head2 browser_max_age |
166
|
|
|
|
|
|
|
|
167
|
|
|
|
|
|
|
$c->browser_max_age( '1m' ); |
168
|
|
|
|
|
|
|
|
169
|
|
|
|
|
|
|
Used to set B<max-age> in the B<Cache-Control> header, browsers use this to |
170
|
|
|
|
|
|
|
determine how long to cache for. B<The CDN will also use this if there is |
171
|
|
|
|
|
|
|
no B<Surrogate-Control> (as set by L</cdn_max_age>)>. |
172
|
|
|
|
|
|
|
|
173
|
|
|
|
|
|
|
=cut |
174
|
|
|
|
|
|
|
|
175
|
|
|
|
|
|
|
has browser_max_age => ( |
176
|
|
|
|
|
|
|
is => 'rw', |
177
|
|
|
|
|
|
|
isa => 'Maybe[Str]', |
178
|
|
|
|
|
|
|
); |
179
|
|
|
|
|
|
|
|
180
|
|
|
|
|
|
|
=head2 browser_stale_while_revalidate |
181
|
|
|
|
|
|
|
|
182
|
|
|
|
|
|
|
$c->browser_stale_while_revalidate('1y'); |
183
|
|
|
|
|
|
|
|
184
|
|
|
|
|
|
|
Applied to B<Cache-Control> only when L</browser_max_age> is set, this |
185
|
|
|
|
|
|
|
informs the browser how long to continue serving stale content from cache while |
186
|
|
|
|
|
|
|
it is revalidating from the CDN. |
187
|
|
|
|
|
|
|
|
188
|
|
|
|
|
|
|
=cut |
189
|
|
|
|
|
|
|
|
190
|
|
|
|
|
|
|
has browser_stale_while_revalidate => ( |
191
|
|
|
|
|
|
|
is => 'rw', |
192
|
|
|
|
|
|
|
isa => 'Maybe[Str]', |
193
|
|
|
|
|
|
|
); |
194
|
|
|
|
|
|
|
|
195
|
|
|
|
|
|
|
=head2 browser_stale_if_error |
196
|
|
|
|
|
|
|
|
197
|
|
|
|
|
|
|
$c->browser_stale_if_error('1y'); |
198
|
|
|
|
|
|
|
|
199
|
|
|
|
|
|
|
Applied to B<Cache-Control> only when L</browser_max_age> is set, this |
200
|
|
|
|
|
|
|
informs the browser how long to continue serving stale content from cache |
201
|
|
|
|
|
|
|
if there is an error at the CDN. |
202
|
|
|
|
|
|
|
|
203
|
|
|
|
|
|
|
=cut |
204
|
|
|
|
|
|
|
|
205
|
|
|
|
|
|
|
has browser_stale_if_error => ( |
206
|
|
|
|
|
|
|
is => 'rw', |
207
|
|
|
|
|
|
|
isa => 'Maybe[Str]', |
208
|
|
|
|
|
|
|
); |
209
|
|
|
|
|
|
|
|
210
|
|
|
|
|
|
|
=head2 browser_never_cache |
211
|
|
|
|
|
|
|
|
212
|
|
|
|
|
|
|
$c->browser_never_cache(1); |
213
|
|
|
|
|
|
|
|
214
|
|
|
|
|
|
|
When true the headers below are set, this forces the browser to never cache |
215
|
|
|
|
|
|
|
the results. B<private> is NOT added as this would also affect the CDN |
216
|
|
|
|
|
|
|
even if C<cdn_max_age> was set. |
217
|
|
|
|
|
|
|
|
218
|
|
|
|
|
|
|
Cache-Control: no-cache, no-store, must-revalidate, max-age=0, max-stale=0, post-check=0, pre-check=0 |
219
|
|
|
|
|
|
|
Pragma: no-cache |
220
|
|
|
|
|
|
|
Expires: 0 |
221
|
|
|
|
|
|
|
|
222
|
|
|
|
|
|
|
N.b. Some versions of IE won't let you download files, such as a PDF if it is |
223
|
|
|
|
|
|
|
not allowed to cache it, it is recommended to set a L</browser_max_age>('1m') |
224
|
|
|
|
|
|
|
in this situation. |
225
|
|
|
|
|
|
|
|
226
|
|
|
|
|
|
|
IE8 have issues with the above and using the back button, and need an additional I<Vary: *> header, |
227
|
|
|
|
|
|
|
L<as noted by Fastly|https://docs.fastly.com/guides/debugging/temporarily-disabling-caching>, |
228
|
|
|
|
|
|
|
this is left for you to impliment. |
229
|
|
|
|
|
|
|
|
230
|
|
|
|
|
|
|
=cut |
231
|
|
|
|
|
|
|
|
232
|
|
|
|
|
|
|
has browser_never_cache => ( |
233
|
|
|
|
|
|
|
is => 'rw', |
234
|
|
|
|
|
|
|
isa => 'Bool', |
235
|
|
|
|
|
|
|
default => sub {0}, |
236
|
|
|
|
|
|
|
); |
237
|
|
|
|
|
|
|
|
238
|
|
|
|
|
|
|
=head1 SURROGATE KEYS |
239
|
|
|
|
|
|
|
|
240
|
|
|
|
|
|
|
=head2 add_surrogate_key |
241
|
|
|
|
|
|
|
|
242
|
|
|
|
|
|
|
$c->add_surrogate_key('FOO','WIBBLE'); |
243
|
|
|
|
|
|
|
|
244
|
|
|
|
|
|
|
This can be called multiple times, the values will be set |
245
|
|
|
|
|
|
|
as the B<Surrogate-Key> header as I<`FOO WIBBLE`>. |
246
|
|
|
|
|
|
|
|
247
|
|
|
|
|
|
|
See L<MooseX::Fastly::Role/cdn_purge_now> if you are |
248
|
|
|
|
|
|
|
interested in purging these keys! |
249
|
|
|
|
|
|
|
|
250
|
|
|
|
|
|
|
=cut |
251
|
|
|
|
|
|
|
|
252
|
|
|
|
|
|
|
has _surrogate_keys => ( |
253
|
|
|
|
|
|
|
traits => ['Array'], |
254
|
|
|
|
|
|
|
is => 'ro', |
255
|
|
|
|
|
|
|
isa => 'ArrayRef[Str]', |
256
|
|
|
|
|
|
|
default => sub { [] }, |
257
|
|
|
|
|
|
|
handles => { |
258
|
|
|
|
|
|
|
add_surrogate_key => 'push', |
259
|
|
|
|
|
|
|
has_surrogate_keys => 'count', |
260
|
|
|
|
|
|
|
surrogate_keys => 'elements', |
261
|
|
|
|
|
|
|
join_surrogate_keys => 'join', |
262
|
|
|
|
|
|
|
}, |
263
|
|
|
|
|
|
|
); |
264
|
|
|
|
|
|
|
|
265
|
|
|
|
|
|
|
=head1 INTERNAL METHODS |
266
|
|
|
|
|
|
|
|
267
|
|
|
|
|
|
|
=head2 finalize_headers |
268
|
|
|
|
|
|
|
|
269
|
|
|
|
|
|
|
The method that actually sets all the headers, should be called |
270
|
|
|
|
|
|
|
automatically by Catalyst. |
271
|
|
|
|
|
|
|
|
272
|
|
|
|
|
|
|
=cut |
273
|
|
|
|
|
|
|
|
274
|
|
|
|
|
|
|
before 'finalize_headers' => sub { |
275
|
|
|
|
|
|
|
my $c = shift; |
276
|
|
|
|
|
|
|
|
277
|
|
|
|
|
|
|
if ( $c->browser_never_cache ) { |
278
|
|
|
|
|
|
|
|
279
|
|
|
|
|
|
|
$c->res->header( 'Cache-Control' => |
280
|
|
|
|
|
|
|
'no-cache, no-store, must-revalidate, max-age=0, max-stale=0, post-check=0, pre-check=0' |
281
|
|
|
|
|
|
|
); |
282
|
|
|
|
|
|
|
$c->res->header( 'Pragma' => 'no-cache' ); |
283
|
|
|
|
|
|
|
$c->res->header( 'Expires' => '0' ); |
284
|
|
|
|
|
|
|
|
285
|
|
|
|
|
|
|
} elsif ( my $browser_max_age = $c->browser_max_age ) { |
286
|
|
|
|
|
|
|
|
287
|
|
|
|
|
|
|
my @cache_control; |
288
|
|
|
|
|
|
|
|
289
|
|
|
|
|
|
|
push @cache_control, sprintf 'max-age=%s', |
290
|
|
|
|
|
|
|
$convert_string_to_seconds->($browser_max_age); |
291
|
|
|
|
|
|
|
|
292
|
|
|
|
|
|
|
if ( my $duration = $c->browser_stale_while_revalidate ) { |
293
|
|
|
|
|
|
|
push @cache_control, sprintf 'stale-while-revalidate=%s', |
294
|
|
|
|
|
|
|
$convert_string_to_seconds->($duration); |
295
|
|
|
|
|
|
|
|
296
|
|
|
|
|
|
|
} |
297
|
|
|
|
|
|
|
|
298
|
|
|
|
|
|
|
if ( my $duration = $c->browser_stale_if_error ) { |
299
|
|
|
|
|
|
|
push @cache_control, sprintf 'stale-if-error=%s', |
300
|
|
|
|
|
|
|
$convert_string_to_seconds->($duration); |
301
|
|
|
|
|
|
|
|
302
|
|
|
|
|
|
|
} |
303
|
|
|
|
|
|
|
|
304
|
|
|
|
|
|
|
$c->res->header( 'Cache-Control' => join( ', ', @cache_control ) ); |
305
|
|
|
|
|
|
|
|
306
|
|
|
|
|
|
|
} |
307
|
|
|
|
|
|
|
|
308
|
|
|
|
|
|
|
# Set the caching at CDN, seperate to what the user's browser does |
309
|
|
|
|
|
|
|
# https://docs.fastly.com/guides/tutorials/cache-control-tutorial |
310
|
|
|
|
|
|
|
if ( $c->cdn_never_cache ) { |
311
|
|
|
|
|
|
|
|
312
|
|
|
|
|
|
|
# Make sure fastly doesn't cache this by accident |
313
|
|
|
|
|
|
|
# tell them it's private, must be on the Cache-Control header |
314
|
|
|
|
|
|
|
my $cc = $c->res->header('Cache-Control') || ''; |
315
|
|
|
|
|
|
|
if ( $cc !~ /private/ ) { |
316
|
|
|
|
|
|
|
$c->res->headers->push_header( 'Cache-Control' => 'private' ); |
317
|
|
|
|
|
|
|
} |
318
|
|
|
|
|
|
|
|
319
|
|
|
|
|
|
|
} elsif ( my $cdn_max_age = $c->cdn_max_age ) { |
320
|
|
|
|
|
|
|
|
321
|
|
|
|
|
|
|
my @surrogate_control; |
322
|
|
|
|
|
|
|
|
323
|
|
|
|
|
|
|
push @surrogate_control, sprintf 'max-age=%s', |
324
|
|
|
|
|
|
|
$convert_string_to_seconds->($cdn_max_age); |
325
|
|
|
|
|
|
|
|
326
|
|
|
|
|
|
|
if ( my $duration = $c->cdn_stale_while_revalidate ) { |
327
|
|
|
|
|
|
|
push @surrogate_control, sprintf 'stale-while-revalidate=%s', |
328
|
|
|
|
|
|
|
$convert_string_to_seconds->($duration); |
329
|
|
|
|
|
|
|
|
330
|
|
|
|
|
|
|
} |
331
|
|
|
|
|
|
|
|
332
|
|
|
|
|
|
|
if ( my $duration = $c->cdn_stale_if_error ) { |
333
|
|
|
|
|
|
|
push @surrogate_control, sprintf 'stale-if-error=%s', |
334
|
|
|
|
|
|
|
$convert_string_to_seconds->($duration); |
335
|
|
|
|
|
|
|
|
336
|
|
|
|
|
|
|
} |
337
|
|
|
|
|
|
|
|
338
|
|
|
|
|
|
|
$c->res->header( |
339
|
|
|
|
|
|
|
'Surrogate-Control' => join( ', ', @surrogate_control ) ); |
340
|
|
|
|
|
|
|
|
341
|
|
|
|
|
|
|
} |
342
|
|
|
|
|
|
|
|
343
|
|
|
|
|
|
|
# Surrogate key |
344
|
|
|
|
|
|
|
if ( $c->has_surrogate_keys ) { |
345
|
|
|
|
|
|
|
|
346
|
|
|
|
|
|
|
# See http://www.fastly.com/blog/surrogate-keys-part-1/ |
347
|
|
|
|
|
|
|
$c->res->header( 'Surrogate-Key' => $c->join_surrogate_keys(' ') ); |
348
|
|
|
|
|
|
|
} |
349
|
|
|
|
|
|
|
|
350
|
|
|
|
|
|
|
}; |
351
|
|
|
|
|
|
|
|
352
|
|
|
|
|
|
|
=head1 SEE ALSO |
353
|
|
|
|
|
|
|
|
354
|
|
|
|
|
|
|
L<MooseX::Fastly::Role> - provides cdn_purge_now and access to L<Net::Fastly> |
355
|
|
|
|
|
|
|
L<stale-while-validate|https://www.fastly.com/blog/stale-while-revalidate/> |
356
|
|
|
|
|
|
|
|
357
|
|
|
|
|
|
|
=head1 AUTHOR |
358
|
|
|
|
|
|
|
|
359
|
|
|
|
|
|
|
Leo Lapworth <LLAP@cpan.org> |
360
|
|
|
|
|
|
|
|
361
|
|
|
|
|
|
|
=cut |
362
|
|
|
|
|
|
|
|
363
|
|
|
|
|
|
|
1; |