use warnings;
use base qw/Class::Accessor::Fast Class::Data::Inheritable/;
use File::stat;
-use File::Spec::Functions qw/catdir no_upwards splitdir/;
-use IO::File;
-use MIME::Types;
-use NEXT;
+use File::Spec ();
+use IO::File ();
+use MIME::Types ();
+use MRO::Compat;
-our $VERSION = '0.13';
+our $VERSION = '0.21';
-__PACKAGE__->mk_classdata( qw/_static_mime_types/ );
-__PACKAGE__->mk_accessors( qw/_static_file
- _static_debug_message/ );
+__PACKAGE__->mk_accessors( qw/_static_file _static_debug_message/ );
sub prepare_action {
my $c = shift;
my $path = $c->req->path;
+ my $config = $c->config->{static};
+
+ $path =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg;
# is the URI in a static-defined path?
- foreach my $dir ( @{ $c->config->{static}->{dirs} } ) {
- my $re = ( $dir =~ /^qr\//xms ) ? eval $dir : qr/^${dir}/;
+ foreach my $dir ( @{ $config->{dirs} } ) {
+ my $dir_re = quotemeta $dir;
+
+ # 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': $@" );
}
if ( $path =~ $re ) {
- if ( $c->_locate_static_file ) {
+ if ( $c->_locate_static_file( $path, 1 ) ) {
$c->_debug_msg( 'from static directory' )
- if ( $c->config->{static}->{debug} );
+ if $config->{debug};
} else {
$c->_debug_msg( "404: file not found: $path" )
- if ( $c->config->{static}->{debug} );
+ if $config->{debug};
$c->res->status( 404 );
+ $c->res->content_type( 'text/html' );
}
}
}
# Does the path have an extension?
if ( $path =~ /.*\.(\S{1,})$/xms ) {
# and does it exist?
- $c->_locate_static_file;
+ $c->_locate_static_file( $path );
}
- return $c->NEXT::ACTUAL::prepare_action(@_);
+ return $c->next::method(@_);
}
sub dispatch {
return if ( $c->res->status != 200 );
if ( $c->_static_file ) {
- if ( $c->config->{static}->{no_logs} && $c->log->can('abort') ) {
+ if ( $c->config->{static}{no_logs} && $c->log->can('abort') ) {
$c->log->abort( 1 );
}
return $c->_serve_static;
}
else {
- return $c->NEXT::ACTUAL::dispatch(@_);
+ return $c->next::method(@_);
}
}
my $c = shift;
# display all log messages
- if ( $c->config->{static}->{debug} && scalar @{$c->_debug_msg} ) {
+ if ( $c->config->{static}{debug} && scalar @{$c->_debug_msg} ) {
$c->log->debug( 'Static::Simple: ' . join q{ }, @{$c->_debug_msg} );
}
- if ( $c->res->status =~ /^(1\d\d|[23]04)$/xms ) {
- $c->res->headers->remove_content_headers;
- return $c->finalize_headers;
- }
-
- return $c->NEXT::ACTUAL::finalize(@_);
+ return $c->next::method(@_);
}
sub setup {
my $c = shift;
- $c->NEXT::setup(@_);
+ $c->next::method(@_);
if ( Catalyst->VERSION le '5.33' ) {
require File::Slurp;
}
- $c->config->{static}->{dirs} ||= [];
- $c->config->{static}->{include_path} ||= [ $c->config->{root} ];
- $c->config->{static}->{mime_types} ||= {};
- $c->config->{static}->{ignore_extensions}
- ||= [ qw/tmpl tt tt2 html xhtml/ ];
- $c->config->{static}->{ignore_dirs} ||= [];
- $c->config->{static}->{debug} ||= $c->debug;
- if ( ! defined $c->config->{static}->{no_logs} ) {
- $c->config->{static}->{no_logs} = 1;
- }
+ my $config = $c->config->{static} ||= {};
+
+ $config->{dirs} ||= [];
+ $config->{include_path} ||= [ $c->config->{root} ];
+ $config->{mime_types} ||= {};
+ $config->{ignore_extensions} ||= [ qw/tmpl tt tt2 html xhtml/ ];
+ $config->{ignore_dirs} ||= [];
+ $config->{debug} ||= $c->debug;
+ $config->{no_logs} = 1 unless defined $config->{no_logs};
+ $config->{no_logs} = 0 if $config->{logging};
# load up a MIME::Types object, only loading types with
# at least 1 file extension
- $c->_static_mime_types( MIME::Types->new( only_complete => 1 ) );
+ $config->{mime_types_obj} = MIME::Types->new( only_complete => 1 );
# preload the type index hash so it's not built on the first request
- $c->_static_mime_types->create_type_index;
+ $config->{mime_types_obj}->create_type_index;
}
# Search through all included directories for the static file
# Based on Template Toolkit INCLUDE_PATH code
sub _locate_static_file {
- my $c = shift;
+ my ( $c, $path, $in_static_dir ) = @_;
- my $path = catdir( no_upwards( splitdir( $c->req->path ) ) );
+ $path = File::Spec->catdir(
+ File::Spec->no_upwards( File::Spec->splitdir( $path ) )
+ );
- my @ipaths = @{ $c->config->{static}->{include_path} };
+ my $config = $c->config->{static};
+ my @ipaths = @{ $config->{include_path} };
my $dpaths;
my $count = 64; # maximum number of directories to search
$dir =~ s/(\/|\\)$//xms;
if ( -d $dir && -f $dir . '/' . $path ) {
- # do we need to ignore the file?
- for my $ignore ( @{ $c->config->{static}->{ignore_dirs} } ) {
- $ignore =~ s{(/|\\)$}{};
- if ( $path =~ /^$ignore(\/|\\)/ ) {
- $c->_debug_msg( "Ignoring directory `$ignore`" )
- if ( $c->config->{static}->{debug} );
- next DIR_CHECK;
+ # Don't ignore any files in static dirs defined with 'dirs'
+ unless ( $in_static_dir ) {
+ # do we need to ignore the file?
+ for my $ignore ( @{ $config->{ignore_dirs} } ) {
+ $ignore =~ s{(/|\\)$}{};
+ if ( $path =~ /^$ignore(\/|\\)/ ) {
+ $c->_debug_msg( "Ignoring directory `$ignore`" )
+ if $config->{debug};
+ next DIR_CHECK;
+ }
}
- }
- # do we need to ignore based on extension?
- for my $ignore_ext
- ( @{ $c->config->{static}->{ignore_extensions} } ) {
+ # do we need to ignore based on extension?
+ for my $ignore_ext ( @{ $config->{ignore_extensions} } ) {
if ( $path =~ /.*\.${ignore_ext}$/ixms ) {
$c->_debug_msg( "Ignoring extension `$ignore_ext`" )
- if ( $c->config->{static}->{debug} );
+ if $config->{debug};
next DIR_CHECK;
}
+ }
}
$c->_debug_msg( 'Serving ' . $dir . '/' . $path )
- if ( $c->config->{static}->{debug} );
+ if $config->{debug};
return $c->_static_file( $dir . '/' . $path );
}
}
sub _serve_static {
my $c = shift;
-
- my $path = $c->req->path;
- my $type = $c->_ext_to_type;
-
- my $full_path = $c->_static_file;
- my $stat = stat $full_path;
+
+ my $full_path = shift || $c->_static_file;
+ my $type = $c->_ext_to_type( $full_path );
+ my $stat = stat $full_path;
$c->res->headers->content_type( $type );
$c->res->headers->content_length( $stat->size );
return 1;
}
+sub serve_static_file {
+ my ( $c, $full_path ) = @_;
+
+ my $config = $c->config->{static} ||= {};
+
+ if ( -e $full_path ) {
+ $c->_debug_msg( "Serving static file: $full_path" )
+ if $config->{debug};
+ }
+ else {
+ $c->_debug_msg( "404: file not found: $full_path" )
+ if $config->{debug};
+ $c->res->status( 404 );
+ $c->res->content_type( 'text/html' );
+ return;
+ }
+
+ $c->_serve_static( $full_path );
+}
+
# looks up the correct MIME type for the current file extension
sub _ext_to_type {
- my $c = shift;
- my $path = $c->req->path;
+ my ( $c, $full_path ) = @_;
- if ( $path =~ /.*\.(\S{1,})$/xms ) {
+ my $config = $c->config->{static};
+
+ if ( $full_path =~ /.*\.(\S{1,})$/xms ) {
my $ext = $1;
- my $user_types = $c->config->{static}->{mime_types};
- my $type = $user_types->{$ext}
- || $c->_static_mime_types->mimeTypeOf( $ext );
+ my $type = $config->{mime_types}{$ext}
+ || $config->{mime_types_obj}->mimeTypeOf( $ext );
if ( $type ) {
- $c->_debug_msg( "as $type" )
- if ( $c->config->{static}->{debug} );
+ $c->_debug_msg( "as $type" ) if $config->{debug};
return ( ref $type ) ? $type->type : $type;
}
else {
$c->_debug_msg( "as text/plain (unknown extension $ext)" )
- if ( $c->config->{static}->{debug} );
+ if $config->{debug};
return 'text/plain';
}
}
else {
$c->_debug_msg( 'as text/plain (no extension)' )
- if ( $c->config->{static}->{debug} );
+ if $config->{debug};
return 'text/plain';
}
}
use Catalyst;
MyApp->setup( qw/Static::Simple/ );
- # that's it; static content is automatically served by
- # Catalyst, though you can configure things or bypass
- # Catalyst entirely in a production environment
+ # that's it; static content is automatically served by Catalyst
+ # from the application's root directory, though you can configure
+ # things or bypass Catalyst entirely in a production environment
+ #
+ # one caveat: the files must be served from an absolute path
+ # (i.e. /images/foo.png)
=head1 DESCRIPTION
default; static requests tend to clutter the log output and rarely
reveal anything useful. However, if you want to enable logging of static
requests, you can do so by setting
-C<MyApp-E<gt>config-E<gt>{static}-E<gt>{no_logs}> to 0.
+C<MyApp-E<gt>config-E<gt>{static}-E<gt>{logging}> to 1.
=head2 Forcing directories into static mode
=head1 USING WITH APACHE
-While Static::Simple will work just fine serving files through Catalyst in
-mod_perl, for increased performance, you may wish to have Apache handle the
-serving of your static files. To do this, simply use a dedicated directory
-for your static files and configure an Apache Location block for that
-directory. This approach is recommended for production installations.
+While Static::Simple will work just fine serving files through Catalyst
+in mod_perl, for increased performance you may wish to have Apache
+handle the serving of your static files directly. To do this, simply use
+a dedicated directory for your static files and configure an Apache
+Location block for that directory This approach is recommended for
+production installations.
- <Location /static>
+ <Location /myapp/static>
SetHandler default-handler
</Location>
application, and it will continue to function on a development server,
or using Catalyst's built-in server.
+In practice, your Catalyst application is probably (i.e. should be)
+structured in the recommended way (i.e., that generated by bootstrapping
+the application with the C<catalyst.pl> script, with a main directory
+under which is a C<lib/> directory for module files and a C<root/>
+directory for templates and static files). Thus, unless you break up
+this structure when deploying your app by moving the static files to a
+different location in your filesystem, you will need to use an Alias
+directive in Apache to point to the right place. You will then need to
+add a Directory block to give permission for Apache to serve these
+files. The final configuration will look something like this:
+
+ Alias /myapp/static /filesystem/path/to/MyApp/root/static
+ <Directory /filesystem/path/to/MyApp/root/static>
+ allow from all
+ </Directory>
+ <Location /myapp/static>
+ SetHandler default-handler
+ </Location>
+
+If you are running in a VirtualHost, you can just set the DocumentRoot
+location to the location of your root directory; see
+L<Catalyst::Engine::Apache2::MP20>.
+
+=head1 PUBLIC METHODS
+
+=head2 serve_static_file $file_path
+
+Will serve the file located in $file_path statically. This is useful when
+you need to autogenerate them if they don't exist, or they are stored in a model.
+
+ package MyApp::Controller::User;
+
+ sub curr_user_thumb : PathPart("my_thumbnail.png") {
+ my ( $self, $c ) = @_;
+ my $file_path = $c->user->picture_thumbnail_path;
+ $c->serve_static_file($file_path);
+ }
+
=head1 INTERNAL EXTENDED METHODS
Static::Simple extends the following steps in the Catalyst process.
=head1 CONTRIBUTORS
Marcus Ramberg, <mramberg@cpan.org>
+
Jesse Sheidlower, <jester@panix.com>
+Guillermo Roditi, <groditi@cpan.org>
+
=head1 THANKS
The authors of Catalyst::Plugin::Static: