package Catalyst::Plugin::Static::Simple;
-use strict;
-use warnings;
-use base qw/Class::Accessor::Fast Class::Data::Inheritable/;
+use Moose::Role;
use File::stat;
use File::Spec ();
use IO::File ();
use MIME::Types ();
-use MRO::Compat;
+use MooseX::Types::Moose qw/ArrayRef Str/;
+use Catalyst::Utils;
+use namespace::autoclean;
-our $VERSION = '0.24';
+our $VERSION = '0.30';
-__PACKAGE__->mk_accessors( qw/_static_file _static_debug_message/ );
+has _static_file => ( is => 'rw' );
+has _static_debug_message => ( is => 'rw', isa => ArrayRef[Str] );
-sub prepare_action {
+before prepare_action => sub {
my $c = shift;
my $path = $c->req->path;
- my $config = $c->config->{static};
+ my $config = $c->config->{'Plugin::Static::Simple'};
$path =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg;
# strip trailing slashes, they'll be added in our regex
$dir_re =~ s{/$}{};
- my $re = ( $dir =~ m{^qr/}xms ) ? eval $dir : qr{^${dir_re}/};
- if ($@) {
- $c->error( "Error compiling static dir regex '$dir': $@" );
+ my $re;
+
+ if ( $dir =~ m{^qr/}xms ) {
+ $re = eval $dir;
+
+ if ($@) {
+ $c->error( "Error compiling static dir regex '$dir': $@" );
+ }
}
+ else {
+ $re = qr{^${dir_re}/};
+ }
+
if ( $path =~ $re ) {
if ( $c->_locate_static_file( $path, 1 ) ) {
$c->_debug_msg( 'from static directory' )
# and does it exist?
$c->_locate_static_file( $path );
}
+};
- return $c->next::method(@_);
-}
-
-sub dispatch {
+around dispatch => sub {
+ my $orig = shift;
my $c = shift;
return if ( $c->res->status != 200 );
if ( $c->_static_file ) {
- if ( $c->config->{static}{no_logs} && $c->log->can('abort') ) {
+ if ( $c->config->{'Plugin::Static::Simple'}->{no_logs} && $c->log->can('abort') ) {
$c->log->abort( 1 );
}
return $c->_serve_static;
}
else {
- return $c->next::method(@_);
+ return $c->$orig(@_);
}
-}
+};
-sub finalize {
+before finalize => sub {
my $c = shift;
# display all log messages
- if ( $c->config->{static}{debug} && scalar @{$c->_debug_msg} ) {
+ if ( $c->config->{'Plugin::Static::Simple'}->{debug} && scalar @{$c->_debug_msg} ) {
$c->log->debug( 'Static::Simple: ' . join q{ }, @{$c->_debug_msg} );
}
+};
- return $c->next::method(@_);
-}
-
-sub setup {
+before setup_finalize => sub {
my $c = shift;
- $c->maybe::next::method(@_);
-
- my $config = $c->config->{static} ||= {};
+ $c->log->warn("Deprecated 'static' config key used, please use the key 'Plugin::Static::Simple' instead")
+ if exists $c->config->{static};
+ my $config
+ = $c->config->{'Plugin::Static::Simple'}
+ = $c->config->{'static'}
+ = Catalyst::Utils::merge_hashes(
+ $c->config->{'Plugin::Static::Simple'} || {},
+ $c->config->{static} || {}
+ );
$config->{dirs} ||= [];
$config->{include_path} ||= [ $c->config->{root} ];
# preload the type index hash so it's not built on the first request
$config->{mime_types_obj}->create_type_index;
-}
+};
# Search through all included directories for the static file
# Based on Template Toolkit INCLUDE_PATH code
File::Spec->no_upwards( File::Spec->splitdir( $path ) )
);
- my $config = $c->config->{static};
+ my $config = $c->config->{'Plugin::Static::Simple'};
my @ipaths = @{ $config->{include_path} };
my $dpaths;
my $count = 64; # maximum number of directories to search
sub _serve_static {
my $c = shift;
+ my $config = $c->config->{'Plugin::Static::Simple'};
my $full_path = shift || $c->_static_file;
my $type = $c->_ext_to_type( $full_path );
$c->res->headers->content_type( $type );
$c->res->headers->content_length( $stat->size );
$c->res->headers->last_modified( $stat->mtime );
+ # Tell Firefox & friends its OK to cache, even over SSL:
+ $c->res->headers->header('Cache-control' => 'public');
+ # Optionally, set a fixed expiry time:
+ if ($config->{expires}) {
+ $c->res->headers->expires(time() + $config->{expires});
+ }
my $fh = IO::File->new( $full_path, 'r' );
if ( defined $fh ) {
sub serve_static_file {
my ( $c, $full_path ) = @_;
- my $config = $c->config->{static} ||= {};
+ my $config = $c->config->{'Plugin::Static::Simple'};
if ( -e $full_path ) {
$c->_debug_msg( "Serving static file: $full_path" )
sub _ext_to_type {
my ( $c, $full_path ) = @_;
- my $config = $c->config->{static};
+ my $config = $c->config->{'Plugin::Static::Simple'};
if ( $full_path =~ /.*\.(\S{1,})$/xms ) {
my $ext = $1;
# handled by static::simple, not dispatched to your application
/images/exists.png
-
+
# static::simple will not find the file and let your application
# handle the request. You are responsible for generating a file
# or returning a 404 error
environment, you will probably want to use your webserver to deliver
static content; for an example see L<USING WITH APACHE>, below.
-=head1 DEFAULT BEHAVIOR
+=head1 DEFAULT BEHAVIOUR
By default, Static::Simple will deliver all files having extensions
(that is, bits of text following a period (C<.>)), I<except> files
that should always be served in static mode. Regular expressions may be
specified using C<qr//>.
- MyApp->config->{static}->{dirs} = [
- 'static',
- qr/^(images|css)/,
- ];
+ MyApp->config(
+ static => {
+ dirs => [
+ 'static',
+ qr/^(images|css)/,
+ ],
+ }
+ );
=head2 Including additional directories
added to the search path when you specify an C<include_path>. You should
use C<MyApp-E<gt>config-E<gt>{root}> to add it.
- MyApp->config->{static}->{include_path} = [
- '/path/to/overlay',
- \&incpath_generator,
- MyApp->config->{root}
- ];
+ MyApp->config(
+ static => {
+ include_path => [
+ '/path/to/overlay',
+ \&incpath_generator,
+ MyApp->config->{root},
+ ],
+ },
+ );
With the above setting, a request for the file C</images/logo.jpg> will search
for the following files, returning the first one found:
sub incpath_generator {
my $c = shift;
-
+
if ( $c->session->{customer_dir} ) {
return [ $c->session->{customer_dir} ];
} else {
If you wish to define your own extensions to ignore, use the
C<ignore_extensions> option:
- MyApp->config->{static}->{ignore_extensions}
- = [ qw/html asp php/ ];
+ MyApp->config(
+ static => {
+ ignore_extensions => [ qw/html asp php/ ],
+ },
+ );
=head2 Ignoring entire directories
directory paths to ignore. If using C<include_path>, the path will be
checked against every included path.
- MyApp->config->{static}->{ignore_dirs} = [ qw/tmpl css/ ];
+ MyApp->config(
+ static => {
+ ignore_dirs => [ qw/tmpl css/ ],
+ },
+ );
For example, if combined with the above C<include_path> setting, this
C<ignore_dirs> value will ignore the following directories if they exist:
To override or add to the default MIME types set by the L<MIME::Types>
module, you may enter your own extension to MIME type mapping.
- MyApp->config->{static}->{mime_types} = {
- jpg => 'image/jpg',
- png => 'image/png',
- };
+ MyApp->config(
+ static => {
+ mime_types => {
+ jpg => 'image/jpg',
+ png => 'image/png',
+ },
+ },
+ );
+
+=head2 Controlling caching with Expires header
+
+The files served by Static::Simple will have a Last-Modified header set,
+which allows some browsers to cache them for a while. However if you want
+to explicitly set an Expires header, such as to allow proxies to cache your
+static content, then you can do so by setting the "expires" config option.
+
+The value indicates the number of seconds after access time to allow caching.
+So a value of zero really means "don't cache at all", and any higher values
+will keep the file around for that long.
+
+ MyApp->config(
+ static => {
+ expires => 3600, # Caching allowed for one hour.
+ },
+ );
=head2 Compatibility with other plugins
Enable additional debugging information printed in the Catalyst log. This
is automatically enabled when running Catalyst in -Debug mode.
- MyApp->config->{static}->{debug} = 1;
+ MyApp->config(
+ static => {
+ debug => 1,
+ },
+ );
=head1 USING WITH APACHE
Guillermo Roditi, <groditi@cpan.org>
-Florian Ragwitz <rafl@debian.org>
+Florian Ragwitz, <rafl@debian.org>
+
+Tomas Doran, <bobtfish@bobtfish.net>
+
+Justin Wheeler (dnm)
+
+Matt S Trout, <mst@shadowcat.co.uk>
+
+Toby Corkindale, <tjc@wintrmute.net>
=head1 THANKS
=head1 COPYRIGHT
-Copyright (c) 2005 - 2009
+Copyright (c) 2005 - 2011
the Catalyst::Plugin::Static::Simple L</AUTHOR> and L</CONTRIBUTORS>
as listed above.