Skip to content

Commit

Permalink
is_fresh can now add headers as well
Browse files Browse the repository at this point in the history
  • Loading branch information
kraih committed Aug 6, 2014
1 parent 1dae377 commit 19a8da6
Show file tree
Hide file tree
Showing 3 changed files with 67 additions and 18 deletions.
23 changes: 15 additions & 8 deletions lib/Mojolicious/Plugin/DefaultHelpers.pm
Expand Up @@ -29,14 +29,13 @@ sub register {

$app->helper($_ => $self->can("_$_"))
for qw(accepts content content_for csrf_token current_route delay),
qw(inactivity_timeout url_with);
qw(inactivity_timeout is_fresh url_with);
$app->helper(b => sub { shift; Mojo::ByteStream->new(@_) });
$app->helper(c => sub { shift; Mojo::Collection->new(@_) });
$app->helper(config => sub { shift->app->config(@_) });
$app->helper(dumper => sub { shift; dumper(@_) });
$app->helper(include => sub { shift->render_to_string(@_) });
$app->helper(is_fresh => sub { $_[0]->app->static->is_fresh($_[0]) });
$app->helper(ua => sub { shift->app->ua });
$app->helper(config => sub { shift->app->config(@_) });
$app->helper(dumper => sub { shift; dumper(@_) });
$app->helper(include => sub { shift->render_to_string(@_) });
$app->helper(ua => sub { shift->app->ua });
}

sub _accepts {
Expand Down Expand Up @@ -88,6 +87,11 @@ sub _inactivity_timeout {
$stream->timeout(shift);
}

sub _is_fresh {
my ($c, %options) = @_;
return $c->app->static->is_fresh($c, \%options);
}

sub _url_with {
my $c = shift;
return $c->url_for(@_)->query($c->req->url->query->clone);
Expand Down Expand Up @@ -274,14 +278,17 @@ Alias for C<Mojolicious::Controller/"render_to_string">.
=head2 is_fresh
my $bool = $c->is_fresh;
my $bool = $c->is_fresh(etag => 'abc');
my $bool = $c->is_fresh(last_modified => $epoch);
Check freshness of response by comparing the C<If-None-Match> and
C<If-Modified-Since> request headers with the C<ETag> and C<Last-Modified>
response headers with L<Mojolicious::Static/"is_fresh">.
# Add ETag header and check freshness before rendering
$c->res->headers->etag('"abc"');
$c->is_fresh ? $c->rendered(304) : $c->render(text => 'I ♥ Mojolicious!');
$c->is_fresh(etag => 'abc')
? $c->rendered(304)
: $c->render(text => 'I ♥ Mojolicious!');
=head2 layout
Expand Down
38 changes: 30 additions & 8 deletions lib/Mojolicious/Static.pm
Expand Up @@ -51,19 +51,23 @@ sub file {
}

sub is_fresh {
my ($self, $c) = @_;
my ($self, $c, $options) = @_;

my $res_headers = $c->res->headers;
my ($last, $etag) = @$options{qw(last_modified etag)};
$res_headers->last_modified(Mojo::Date->new($last)) if $last;
$res_headers->etag($etag = qq{"$etag"}) if $etag;

# Unconditional
my $req_headers = $c->req->headers;
my $match = $req_headers->if_none_match;
return undef unless (my $since = $req_headers->if_modified_since) || $match;

# If-None-Match
my $res_headers = $c->res->headers;
return undef if $match && ($res_headers->etag // '') ne $match;
return undef if $match && ($etag // $res_headers->etag // '') ne $match;

# If-Modified-Since
return !!$match unless (my $last = $res_headers->last_modified) && $since;
return !!$match unless ($last //= $res_headers->last_modified) && $since;
return _epoch($last) <= (_epoch($since) // 0);
}

Expand All @@ -82,9 +86,9 @@ sub serve_asset {
# Last-Modified and ETag
my $mtime = $asset->is_file ? (stat $asset->path)[9] : $MTIME;
my $res = $c->res;
$res->code(200)->headers->last_modified(Mojo::Date->new($mtime))
->etag('"' . md5_sum($mtime) . '"')->accept_ranges('bytes');
return $res->code(304) if $self->is_fresh($c);
$res->code(200)->headers->accept_ranges('bytes');
return $res->code(304)
if $self->is_fresh($c, {etag => md5_sum($mtime), last_modified => $mtime});

# Range
my $size = $asset->size;
Expand Down Expand Up @@ -206,12 +210,30 @@ protect from traversing to parent directories.
=head2 is_fresh
my $bool = $static->is_fresh(Mojolicious::Controller->new);
my $bool = $static->is_fresh(Mojolicious::Controller->new, {etag => 'abc'});
Check freshness of response by comparing the C<If-None-Match> and
C<If-Modified-Since> request headers with the C<ETag> and C<Last-Modified>
response headers.
These options are currently available:
=over 2
=item etag
etag => 'abc'
Add C<ETag> header.
=item last_modified
last_modified => $epoch
Add C<Last-Modified> header.
=back
=head2 serve
my $bool = $static->serve(Mojolicious::Controller->new, 'images/logo.png');
Expand Down
24 changes: 22 additions & 2 deletions t/mojolicious/static_lite_app.t
Expand Up @@ -14,12 +14,32 @@ get '/hello3.txt' => sub { shift->render_static('hello2.txt') };

get '/etag' => sub {
my $c = shift;
$c->res->headers->etag('"abc"');
$c->is_fresh ? $c->rendered(304) : $c->render(text => 'I ♥ Mojolicious!');
$c->is_fresh(etag => 'abc')
? $c->rendered(304)
: $c->render(text => 'I ♥ Mojolicious!');
};

my $t = Test::Mojo->new;

# Freshness
my $c = $t->app->build_controller;
ok !$c->is_fresh, 'content is stale';
$c->res->headers->etag('"abc"');
$c->req->headers->if_none_match('"abc"');
ok $c->is_fresh, 'content is fresh';
$c = $t->app->build_controller;
my $date = Mojo::Date->new(23);
$c->res->headers->last_modified($date);
$c->req->headers->if_modified_since($date);
ok $c->is_fresh, 'content is fresh';
$c = $t->app->build_controller;
$c->req->headers->if_none_match('"abc"');
$c->req->headers->if_modified_since($date);
ok $c->is_fresh(etag => 'abc', last_modified => $date->epoch),
'content is fresh';
is $c->res->headers->etag, '"abc"', 'right "ETag" value';
is $c->res->headers->last_modified, "$date", 'right "Last-Modified" value';

# Static file
$t->get_ok('/hello.txt')->status_is(200)
->header_is(Server => 'Mojolicious (Perl)')
Expand Down

0 comments on commit 19a8da6

Please sign in to comment.