line |
stmt |
bran |
cond |
sub |
pod |
time |
code |
1
|
|
|
|
|
|
|
package CatalystX::Fastly::Role::Response; |
2
|
|
|
|
|
|
|
$CatalystX::Fastly::Role::Response::VERSION = '0.05'; |
3
|
1
|
|
|
1
|
|
698629
|
use Moose::Role; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
7
|
|
4
|
1
|
|
|
1
|
|
3390
|
use Carp; |
|
1
|
|
|
|
|
1
|
|
|
1
|
|
|
|
|
73
|
|
5
|
|
|
|
|
|
|
|
6
|
1
|
|
|
|
|
542
|
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
|
|
4
|
}; |
|
1
|
|
|
|
|
2
|
|
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 B<Surrogate-Control> header will have a value of B<private>, |
152
|
|
|
|
|
|
|
this forces Fastly (other CDN's may behave differently) to never cache the |
153
|
|
|
|
|
|
|
results (even for multiple outstanding requests), no matter what other |
154
|
|
|
|
|
|
|
options have been set. |
155
|
|
|
|
|
|
|
|
156
|
|
|
|
|
|
|
=cut |
157
|
|
|
|
|
|
|
|
158
|
|
|
|
|
|
|
has cdn_never_cache => ( |
159
|
|
|
|
|
|
|
is => 'rw', |
160
|
|
|
|
|
|
|
isa => 'Bool', |
161
|
|
|
|
|
|
|
default => sub {0}, |
162
|
|
|
|
|
|
|
); |
163
|
|
|
|
|
|
|
|
164
|
|
|
|
|
|
|
=head1 BROWSER METHODS |
165
|
|
|
|
|
|
|
|
166
|
|
|
|
|
|
|
=head2 browser_max_age |
167
|
|
|
|
|
|
|
|
168
|
|
|
|
|
|
|
$c->browser_max_age( '1m' ); |
169
|
|
|
|
|
|
|
|
170
|
|
|
|
|
|
|
Used to set B<max-age> in the B<Cache-Control> header, browsers use this to |
171
|
|
|
|
|
|
|
determine how long to cache for. B<The CDN will also use this if there is |
172
|
|
|
|
|
|
|
no B<Surrogate-Control> (as set by L</cdn_max_age>)>. |
173
|
|
|
|
|
|
|
|
174
|
|
|
|
|
|
|
=cut |
175
|
|
|
|
|
|
|
|
176
|
|
|
|
|
|
|
has browser_max_age => ( |
177
|
|
|
|
|
|
|
is => 'rw', |
178
|
|
|
|
|
|
|
isa => 'Maybe[Str]', |
179
|
|
|
|
|
|
|
); |
180
|
|
|
|
|
|
|
|
181
|
|
|
|
|
|
|
=head2 browser_stale_while_revalidate |
182
|
|
|
|
|
|
|
|
183
|
|
|
|
|
|
|
$c->browser_stale_while_revalidate('1y'); |
184
|
|
|
|
|
|
|
|
185
|
|
|
|
|
|
|
Applied to B<Cache-Control> only when L</browser_max_age> is set, this |
186
|
|
|
|
|
|
|
informs the browser how long to continue serving stale content from cache while |
187
|
|
|
|
|
|
|
it is revalidating from the CDN. |
188
|
|
|
|
|
|
|
|
189
|
|
|
|
|
|
|
=cut |
190
|
|
|
|
|
|
|
|
191
|
|
|
|
|
|
|
has browser_stale_while_revalidate => ( |
192
|
|
|
|
|
|
|
is => 'rw', |
193
|
|
|
|
|
|
|
isa => 'Maybe[Str]', |
194
|
|
|
|
|
|
|
); |
195
|
|
|
|
|
|
|
|
196
|
|
|
|
|
|
|
=head2 browser_stale_if_error |
197
|
|
|
|
|
|
|
|
198
|
|
|
|
|
|
|
$c->browser_stale_if_error('1y'); |
199
|
|
|
|
|
|
|
|
200
|
|
|
|
|
|
|
Applied to B<Cache-Control> only when L</browser_max_age> is set, this |
201
|
|
|
|
|
|
|
informs the browser how long to continue serving stale content from cache |
202
|
|
|
|
|
|
|
if there is an error at the CDN. |
203
|
|
|
|
|
|
|
|
204
|
|
|
|
|
|
|
=cut |
205
|
|
|
|
|
|
|
|
206
|
|
|
|
|
|
|
has browser_stale_if_error => ( |
207
|
|
|
|
|
|
|
is => 'rw', |
208
|
|
|
|
|
|
|
isa => 'Maybe[Str]', |
209
|
|
|
|
|
|
|
); |
210
|
|
|
|
|
|
|
|
211
|
|
|
|
|
|
|
=head2 browser_never_cache |
212
|
|
|
|
|
|
|
|
213
|
|
|
|
|
|
|
$c->browser_never_cache(1); |
214
|
|
|
|
|
|
|
|
215
|
|
|
|
|
|
|
When true the headers below are set, this forces the browser to never cache |
216
|
|
|
|
|
|
|
the results. B<private> is NOT added as this would also affect the CDN |
217
|
|
|
|
|
|
|
even if C<cdn_max_age> was set. |
218
|
|
|
|
|
|
|
|
219
|
|
|
|
|
|
|
Cache-Control: no-cache, no-store, must-revalidate, max-age=0, max-stale=0, post-check=0, pre-check=0 |
220
|
|
|
|
|
|
|
Pragma: no-cache |
221
|
|
|
|
|
|
|
Expires: 0 |
222
|
|
|
|
|
|
|
|
223
|
|
|
|
|
|
|
N.b. Some versions of IE won't let you download files, such as a PDF if it is |
224
|
|
|
|
|
|
|
not allowed to cache it, it is recommended to set a L</browser_max_age>('1m') |
225
|
|
|
|
|
|
|
in this situation. |
226
|
|
|
|
|
|
|
|
227
|
|
|
|
|
|
|
IE8 have issues with the above and using the back button, and need an additional I<Vary: *> header, |
228
|
|
|
|
|
|
|
L<as noted by Fastly|https://docs.fastly.com/guides/debugging/temporarily-disabling-caching>, |
229
|
|
|
|
|
|
|
this is left for you to impliment. |
230
|
|
|
|
|
|
|
|
231
|
|
|
|
|
|
|
=cut |
232
|
|
|
|
|
|
|
|
233
|
|
|
|
|
|
|
has browser_never_cache => ( |
234
|
|
|
|
|
|
|
is => 'rw', |
235
|
|
|
|
|
|
|
isa => 'Bool', |
236
|
|
|
|
|
|
|
default => sub {0}, |
237
|
|
|
|
|
|
|
); |
238
|
|
|
|
|
|
|
|
239
|
|
|
|
|
|
|
=head1 SURROGATE KEYS |
240
|
|
|
|
|
|
|
|
241
|
|
|
|
|
|
|
=head2 add_surrogate_key |
242
|
|
|
|
|
|
|
|
243
|
|
|
|
|
|
|
$c->add_surrogate_key('FOO','WIBBLE'); |
244
|
|
|
|
|
|
|
|
245
|
|
|
|
|
|
|
This can be called multiple times, the values will be set |
246
|
|
|
|
|
|
|
as the B<Surrogate-Key> header as I<`FOO WIBBLE`>. |
247
|
|
|
|
|
|
|
|
248
|
|
|
|
|
|
|
See L<MooseX::Fastly::Role/cdn_purge_now> if you are |
249
|
|
|
|
|
|
|
interested in purging these keys! |
250
|
|
|
|
|
|
|
|
251
|
|
|
|
|
|
|
=cut |
252
|
|
|
|
|
|
|
|
253
|
|
|
|
|
|
|
has _surrogate_keys => ( |
254
|
|
|
|
|
|
|
traits => ['Array'], |
255
|
|
|
|
|
|
|
is => 'ro', |
256
|
|
|
|
|
|
|
isa => 'ArrayRef[Str]', |
257
|
|
|
|
|
|
|
default => sub { [] }, |
258
|
|
|
|
|
|
|
handles => { |
259
|
|
|
|
|
|
|
add_surrogate_key => 'push', |
260
|
|
|
|
|
|
|
has_surrogate_keys => 'count', |
261
|
|
|
|
|
|
|
surrogate_keys => 'elements', |
262
|
|
|
|
|
|
|
join_surrogate_keys => 'join', |
263
|
|
|
|
|
|
|
}, |
264
|
|
|
|
|
|
|
); |
265
|
|
|
|
|
|
|
|
266
|
|
|
|
|
|
|
=head1 INTERNAL METHODS |
267
|
|
|
|
|
|
|
|
268
|
|
|
|
|
|
|
=head2 finalize_headers |
269
|
|
|
|
|
|
|
|
270
|
|
|
|
|
|
|
The method that actually sets all the headers, should be called |
271
|
|
|
|
|
|
|
automatically by Catalyst. |
272
|
|
|
|
|
|
|
|
273
|
|
|
|
|
|
|
=cut |
274
|
|
|
|
|
|
|
|
275
|
|
|
|
|
|
|
before 'finalize_headers' => sub { |
276
|
|
|
|
|
|
|
my $c = shift; |
277
|
|
|
|
|
|
|
|
278
|
|
|
|
|
|
|
if ( $c->browser_never_cache ) { |
279
|
|
|
|
|
|
|
|
280
|
|
|
|
|
|
|
$c->res->header( 'Cache-Control' => |
281
|
|
|
|
|
|
|
'no-cache, no-store, must-revalidate, max-age=0, max-stale=0, post-check=0, pre-check=0' |
282
|
|
|
|
|
|
|
); |
283
|
|
|
|
|
|
|
$c->res->header( 'Pragma' => 'no-cache' ); |
284
|
|
|
|
|
|
|
$c->res->header( 'Expires' => '0' ); |
285
|
|
|
|
|
|
|
|
286
|
|
|
|
|
|
|
} elsif ( my $browser_max_age = $c->browser_max_age ) { |
287
|
|
|
|
|
|
|
|
288
|
|
|
|
|
|
|
my @cache_control; |
289
|
|
|
|
|
|
|
|
290
|
|
|
|
|
|
|
push @cache_control, sprintf 'max-age=%s', |
291
|
|
|
|
|
|
|
$convert_string_to_seconds->($browser_max_age); |
292
|
|
|
|
|
|
|
|
293
|
|
|
|
|
|
|
if ( my $duration = $c->browser_stale_while_revalidate ) { |
294
|
|
|
|
|
|
|
push @cache_control, sprintf 'stale-while-revalidate=%s', |
295
|
|
|
|
|
|
|
$convert_string_to_seconds->($duration); |
296
|
|
|
|
|
|
|
|
297
|
|
|
|
|
|
|
} |
298
|
|
|
|
|
|
|
|
299
|
|
|
|
|
|
|
if ( my $duration = $c->browser_stale_if_error ) { |
300
|
|
|
|
|
|
|
push @cache_control, sprintf 'stale-if-error=%s', |
301
|
|
|
|
|
|
|
$convert_string_to_seconds->($duration); |
302
|
|
|
|
|
|
|
|
303
|
|
|
|
|
|
|
} |
304
|
|
|
|
|
|
|
|
305
|
|
|
|
|
|
|
$c->res->header( 'Cache-Control' => join( ', ', @cache_control ) ); |
306
|
|
|
|
|
|
|
|
307
|
|
|
|
|
|
|
} |
308
|
|
|
|
|
|
|
|
309
|
|
|
|
|
|
|
# Set the caching at CDN, seperate to what the user's browser does |
310
|
|
|
|
|
|
|
# https://docs.fastly.com/guides/tutorials/cache-control-tutorial |
311
|
|
|
|
|
|
|
if ( $c->cdn_never_cache ) { |
312
|
|
|
|
|
|
|
|
313
|
|
|
|
|
|
|
# Make sure fastly doesn't cache this by accident |
314
|
|
|
|
|
|
|
# tell them it's private, must be on the Cache-Control header |
315
|
|
|
|
|
|
|
my $cc = $c->res->header('Cache-Control'); |
316
|
|
|
|
|
|
|
if ( $cc && $cc !~ /private/ ) { |
317
|
|
|
|
|
|
|
$c->res->headers->push_header( 'Cache-Control' => 'private' ); |
318
|
|
|
|
|
|
|
} |
319
|
|
|
|
|
|
|
|
320
|
|
|
|
|
|
|
} elsif ( my $cdn_max_age = $c->cdn_max_age ) { |
321
|
|
|
|
|
|
|
|
322
|
|
|
|
|
|
|
my @surrogate_control; |
323
|
|
|
|
|
|
|
|
324
|
|
|
|
|
|
|
push @surrogate_control, sprintf 'max-age=%s', |
325
|
|
|
|
|
|
|
$convert_string_to_seconds->($cdn_max_age); |
326
|
|
|
|
|
|
|
|
327
|
|
|
|
|
|
|
if ( my $duration = $c->cdn_stale_while_revalidate ) { |
328
|
|
|
|
|
|
|
push @surrogate_control, sprintf 'stale-while-revalidate=%s', |
329
|
|
|
|
|
|
|
$convert_string_to_seconds->($duration); |
330
|
|
|
|
|
|
|
|
331
|
|
|
|
|
|
|
} |
332
|
|
|
|
|
|
|
|
333
|
|
|
|
|
|
|
if ( my $duration = $c->cdn_stale_if_error ) { |
334
|
|
|
|
|
|
|
push @surrogate_control, sprintf 'stale-if-error=%s', |
335
|
|
|
|
|
|
|
$convert_string_to_seconds->($duration); |
336
|
|
|
|
|
|
|
|
337
|
|
|
|
|
|
|
} |
338
|
|
|
|
|
|
|
|
339
|
|
|
|
|
|
|
$c->res->header( |
340
|
|
|
|
|
|
|
'Surrogate-Control' => join( ', ', @surrogate_control ) ); |
341
|
|
|
|
|
|
|
|
342
|
|
|
|
|
|
|
} |
343
|
|
|
|
|
|
|
|
344
|
|
|
|
|
|
|
# Surrogate key |
345
|
|
|
|
|
|
|
if ( $c->has_surrogate_keys ) { |
346
|
|
|
|
|
|
|
|
347
|
|
|
|
|
|
|
# See http://www.fastly.com/blog/surrogate-keys-part-1/ |
348
|
|
|
|
|
|
|
$c->res->header( 'Surrogate-Key' => $c->join_surrogate_keys(' ') ); |
349
|
|
|
|
|
|
|
} |
350
|
|
|
|
|
|
|
|
351
|
|
|
|
|
|
|
}; |
352
|
|
|
|
|
|
|
|
353
|
|
|
|
|
|
|
=head1 SEE ALSO |
354
|
|
|
|
|
|
|
|
355
|
|
|
|
|
|
|
L<MooseX::Fastly::Role> - provides cdn_purge_now and access to L<Net::Fastly> |
356
|
|
|
|
|
|
|
L<stale-while-validate|https://www.fastly.com/blog/stale-while-revalidate/> |
357
|
|
|
|
|
|
|
|
358
|
|
|
|
|
|
|
=head1 AUTHOR |
359
|
|
|
|
|
|
|
|
360
|
|
|
|
|
|
|
Leo Lapworth <LLAP@cpan.org> |
361
|
|
|
|
|
|
|
|
362
|
|
|
|
|
|
|
=cut |
363
|
|
|
|
|
|
|
|
364
|
|
|
|
|
|
|
1; |