Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
added a few more caching features to Mojolicious::Static
  • Loading branch information
kraih committed Aug 6, 2014
1 parent 6e5f702 commit c58536c
Show file tree
Hide file tree
Showing 5 changed files with 89 additions and 33 deletions.
20 changes: 11 additions & 9 deletions Changes
@@ -1,7 +1,10 @@

5.25 2014-08-06
- Added reduce method to Mojo::Collection. (sri, batman)
- Added if_none_match method to Mojo::Headers.
- Added is_fresh method to Mojolicious::Static.
- Improved sort method in Mojo::Collection to use $a and $b. (batman)
- Improved Mojolicious::Static to support ETag and If-None-Match headers.
- Improved documentation browser CSS.
- Fixed escaping bugs in Mojo::DOM::CSS.

Expand Down Expand Up @@ -136,7 +139,7 @@
invocant.
- Changed return value of path_for method in Mojolicious::Routes::Match.
- Changed return value and arguments of error method in Mojo::Message.
- Removed deprecated support for "X-Forwarded-HTTPS".
- Removed deprecated support for X-Forwarded-HTTPS.
- Removed return values from wait method in Mojo::IOLoop::Delay.
- Removed list context support from header method in Mojo::Headers.
- Removed generate_port method from Mojo::IOLoop.
Expand Down Expand Up @@ -174,8 +177,7 @@
- Improved accept performance in Mojo::IOLoop::Server.

4.97 2014-04-30
- Deprecated support for "X-Forwarded-HTTPS" in favor of
"X-Forwarded-Proto".
- Deprecated support for X-Forwarded-HTTPS in favor of X-Forwarded-Proto.
- Added multi-name support to param method in Mojo::Parameters.

4.96 2014-04-28
Expand Down Expand Up @@ -2646,7 +2648,7 @@
- Fixed size limits in message parser.

1.18 2011-04-19
- Added support for "X-Forwarded-HTTPS" and "X-Forwarded-Host" headers.
- Added support for X-Forwarded-HTTPS and X-Forwarded-Host headers.
- Added argument localization to the include helper. (crab, moritz, sri)
- Fixed test case.

Expand Down Expand Up @@ -3176,7 +3178,7 @@
print $client->get('http://search.cpan.org')->res->body;
my $tx = $client->post_form('http://kraih.com', {q => 'mojo'});
- Made plugins much more configurable.
- Improved PSGI support and added "psgi" command.
- Improved PSGI support and added psgi command.
- Added automatic environment detection for Plack based servers, there is
no technical way to detect all PSGI compliant servers yet though. That
means "plackup myapp.pl" and "plackup script/myapp" should just work.
Expand All @@ -3203,8 +3205,8 @@
- Added I18N support. (vti, memowe)
- Added template detection again, you can now just mix multiple template
engines and the renderer will automatically pick the right template. So
you don't have to use the "handler" argument anymore, even though it's
still available and overrides auto detection.
you don't have to use the handler argument anymore, even though it's still
available and overrides auto detection.
- Added Flash Policy Server example. (xantus)
- Added more reference docs.
- Added ".gitignore" generator command. (marcus)
Expand Down Expand Up @@ -3485,8 +3487,8 @@
- Merged eplite and epl, this change is not backwards compatible, you will
have to rename all your eplite templates to epl.
- Simplified MojoX::Renderer, this change is not backwards compatible!
Handler can no longer be detected, that means "default_handler" or the
"handler" argument are required. The template argument can no longer
Handler can no longer be detected, that means default_handler or the
handler argument are required. The template argument can no longer
contain format or handler.
$self->render(template => 'foo.html.epl');
becomes
Expand Down
6 changes: 3 additions & 3 deletions lib/Mojo/Date.pm
Expand Up @@ -16,15 +16,15 @@ sub new { @_ > 1 ? shift->SUPER::new->parse(@_) : shift->SUPER::new }
sub parse {
my ($self, $date) = @_;

# epoch (784111777)
return $self->epoch($date) if $date =~ /^\d+$/;

# RFC 822/1123 (Sun, 06 Nov 1994 08:49:37 GMT)
my ($day, $month, $year, $h, $m, $s);
if ($date =~ /^\w+\,\s+(\d+)\s+(\w+)\s+(\d+)\s+(\d+):(\d+):(\d+)\s+GMT$/) {
($day, $month, $year, $h, $m, $s) = ($1, $MONTHS{$2}, $3, $4, $5, $6);
}

# epoch (784111777)
elsif ($date =~ /^\d+$/) { return $self->epoch($date) }

# RFC 850/1036 (Sunday, 06-Nov-94 08:49:37 GMT)
elsif ($date =~ /^\w+\,\s+(\d+)-(\w+)-(\d+)\s+(\d+):(\d+):(\d+)\s+GMT$/) {
($day, $month, $year, $h, $m, $s) = ($1, $MONTHS{$2}, $3, $4, $5, $6);
Expand Down
20 changes: 14 additions & 6 deletions lib/Mojo/Headers.pm
Expand Up @@ -10,11 +10,12 @@ my %NORMALCASE = map { lc($_) => $_ } (
qw(Accept Accept-Charset Accept-Encoding Accept-Language Accept-Ranges),
qw(Allow Authorization Cache-Control Connection Content-Disposition),
qw(Content-Encoding Content-Length Content-Range Content-Type Cookie DNT),
qw(Date ETag Expect Expires Host If-Modified-Since Last-Modified Link),
qw(Location Origin Proxy-Authenticate Proxy-Authorization Range),
qw(Sec-WebSocket-Accept Sec-WebSocket-Extensions Sec-WebSocket-Key),
qw(Sec-WebSocket-Protocol Sec-WebSocket-Version Server Set-Cookie Status),
qw(TE Trailer Transfer-Encoding Upgrade User-Agent Vary WWW-Authenticate)
qw(Date ETag Expect Expires Host If-Modified-Since If-None-Match),
qw(Last-Modified Link Location Origin Proxy-Authenticate),
qw(Proxy-Authorization Range Sec-WebSocket-Accept Sec-WebSocket-Extensions),
qw(Sec-WebSocket-Key Sec-WebSocket-Protocol Sec-WebSocket-Version Server),
qw(Set-Cookie Status TE Trailer Transfer-Encoding Upgrade User-Agent Vary),
qw(WWW-Authenticate)
);
for my $header (values %NORMALCASE) {
my $name = lc $header;
Expand Down Expand Up @@ -341,7 +342,7 @@ but is very commonly used.
=head2 etag
my $etag = $headers->etag;
$headers = $headers->etag('abc321');
$headers = $headers->etag('"abc321"');
Shortcut for the C<ETag> header.
Expand Down Expand Up @@ -389,6 +390,13 @@ Shortcut for the C<Host> header.
Shortcut for the C<If-Modified-Since> header.
=head2 if_none_match
my $etag = $headers->if_none_match;
$headers = $headers->if_none_match('"abc321"');
Shortcut for the C<If-None-Match> header.
=head2 is_finished
my $bool = $headers->is_finished;
Expand Down
49 changes: 36 additions & 13 deletions lib/Mojolicious/Static.pm
Expand Up @@ -7,6 +7,7 @@ use Mojo::Asset::Memory;
use Mojo::Date;
use Mojo::Home;
use Mojo::Loader;
use Mojo::Util 'md5_sum';

has classes => sub { ['main'] };
has paths => sub { [] };
Expand Down Expand Up @@ -49,6 +50,27 @@ sub file {
return $self->_get_file(catfile($PUBLIC, split('/', $rel)));
}

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

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

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

# If-Modified-Since
my $last = Mojo::Date->new($res_headers->last_modified // '')->epoch;
$unmodified = !$since || $last <= (Mojo::Date->new($since)->epoch // 0);

return $matches && $unmodified;
}

sub serve {
my ($self, $c, $rel) = @_;
return undef unless my $asset = $self->file($rel);
Expand All @@ -61,24 +83,17 @@ sub serve {
sub serve_asset {
my ($self, $c, $asset) = @_;

# Last modified
# 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))
->accept_ranges('bytes');

# If modified since
my $headers = $c->req->headers;
if (my $date = $headers->if_modified_since) {
my $since = Mojo::Date->new($date)->epoch;
return $res->code(304) if defined $since && $since == $mtime;
}
->etag('"' . md5_sum($mtime) . '"')->accept_ranges('bytes');
return $res->code(304) if $self->is_fresh($c);

# Range
my $size = $asset->size;
my $start = 0;
my $end = $size - 1;
if (my $range = $headers->range) {
my $size = $asset->size;
my ($start, $end) = (0, $size - 1);
if (my $range = $c->req->headers->range) {

# Not satisfiable
return $res->code(416) unless $size && $range =~ m/^bytes=(\d+)?-(\d+)?/;
Expand Down Expand Up @@ -191,6 +206,14 @@ protect from traversing to parent directories.
my $content = $static->file('foo/bar.html')->slurp;
=head2 is_fresh
my $bool = $static->is_fresh(Mojolicious::Controller->new);
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.
=head2 serve
my $bool = $static->serve(Mojolicious::Controller->new, 'images/logo.png');
Expand Down
27 changes: 25 additions & 2 deletions t/mojolicious/app.t
Expand Up @@ -374,8 +374,9 @@ my $mtime = Mojo::Date->new((stat $path)[9])->to_string;

# Static file /hello.txt
$t->get_ok('/hello.txt')->status_is(200)
->header_is(Server => 'Mojolicious (Perl)')
->header_is('Last-Modified' => $mtime)->header_is('Content-Length' => $size)
->header_is(Server => 'Mojolicious (Perl)')
->header_is('Last-Modified' => $mtime)->header_like('ETag' => qr/^"\w+"$/)
->header_is('Content-Length' => $size)
->content_type_is('text/plain;charset=UTF-8')
->content_like(qr/Hello Mojo from a development static file!/);

Expand All @@ -389,6 +390,28 @@ $t->get_ok('/../../mojolicious/secret.txt')->status_is(404)
$t->get_ok('/hello.txt' => {'If-Modified-Since' => $mtime})->status_is(304)
->header_is(Server => 'Mojolicious (Perl)')->content_is('');

# Check If-None-Match
my $etag = $t->tx->res->headers->etag;
$t->get_ok('/hello.txt' => {'If-None-Match' => $etag})->status_is(304)
->header_is(Server => 'Mojolicious (Perl)')->content_is('');

# Check If-None-Match and If-Last-Modified
$t->get_ok(
'/hello.txt' => {'If-None-Match' => $etag, 'If-Last-Modified' => $mtime})
->status_is(304)->header_is(Server => 'Mojolicious (Perl)')->content_is('');

# Bad If-None-Match with correct If-Modified-Since
$t->get_ok(
'/hello.txt' => {'If-None-Match' => '"123"', 'If-Modified-Since' => $mtime})
->status_is(200)->header_is(Server => 'Mojolicious (Perl)')
->content_like(qr/Hello Mojo from a development static file!/);

# Bad If-Modified-Since with correct If-None-Match
$t->get_ok('/hello.txt' =>
{'If-Modified-Since' => Mojo::Date->new(23), 'If-None-Match' => $etag})
->status_is(200)->header_is(Server => 'Mojolicious (Perl)')
->content_like(qr/Hello Mojo from a development static file!/);

# Embedded development static file
$t->get_ok('/some/static/file.txt')->status_is(200)
->header_is(Server => 'Mojolicious (Perl)')
Expand Down

0 comments on commit c58536c

Please sign in to comment.