From: Andy Grundman Date: Mon, 5 Sep 2005 19:37:48 +0000 (+0000) Subject: Released Static::Simple 0.06: X-Git-Tag: v0.09~10 X-Git-Url: http://git.shadowcat.co.uk/gitweb/gitweb.cgi?p=catagits%2FCatalyst-Plugin-Static-Simple.git;a=commitdiff_plain;h=b1d96e3e36e4f09e374cee63f84a62630e173828;hp=d6d29b9b2c7db9ccfd3ea8cb3b304b3b017395e7 Released Static::Simple 0.06: - Moved initial file check into prepare_action so processing can bypass other plugins. - Added error-checking to static dir regexes. - Cleaned up various code as per Best Practices. --- diff --git a/Changes b/Changes index 3447161..0a24acd 100644 --- a/Changes +++ b/Changes @@ -1,5 +1,11 @@ Revision history for Perl extension Catalyst::Plugin::Static::Simple +0.06 2005-09-05 15:40:00 + - Moved initial file check into prepare_action so processing can + bypass other plugins. + - Added error-checking to static dir regexes. + - Cleaned up various code as per Best Practices. + 0.05 2005-08-26 12:00:00 - Added use_apache option to enable the Apache DECLINED support. Default is disabled as it appears Catalyst is diff --git a/META.yml b/META.yml index 91e34cc..f929b6d 100644 --- a/META.yml +++ b/META.yml @@ -1,8 +1,8 @@ --- name: Catalyst-Plugin-Static-Simple -version: 0.05 +version: 0.06 author: - - 'Andy Grundman, C' + - 'Andy Grundman, ' abstract: Make serving static pages painless. license: perl requires: @@ -12,5 +12,5 @@ requires: provides: Catalyst::Plugin::Static::Simple: file: lib/Catalyst/Plugin/Static/Simple.pm - version: 0.05 + version: 0.06 generated_by: Module::Build version 0.2611 diff --git a/lib/Catalyst/Plugin/Static/Simple.pm b/lib/Catalyst/Plugin/Static/Simple.pm index 5abcc76..1dc8eca 100644 --- a/lib/Catalyst/Plugin/Static/Simple.pm +++ b/lib/Catalyst/Plugin/Static/Simple.pm @@ -7,173 +7,63 @@ use File::stat; use MIME::Types; use NEXT; -our $VERSION = '0.05'; +our $VERSION = '0.06'; __PACKAGE__->mk_classdata( qw/_mime_types/ ); -__PACKAGE__->mk_accessors( qw/_static_file _apache_mode _debug_message/ ); +__PACKAGE__->mk_accessors( qw/_static_file + _apache_mode + _debug_message/ ); -=head1 NAME - -Catalyst::Plugin::Static::Simple - Make serving static pages painless. - -=head1 SYNOPSIS - - use Catalyst; - MyApp->setup( qw/Static::Simple/ ); - -=head1 DESCRIPTION - -The Static::Simple plugin is designed to make serving static content in your -application during development quick and easy, without requiring a single -line of code from you. - -It will detect static files used in your application by looking for file -extensions in the URI. By default, you can simply load this plugin and it -will immediately begin serving your static files with the correct MIME type. -The light-weight MIME::Types module is used to map file extensions to -IANA-registered MIME types. - -Note that actions mapped to paths using periods (.) will still operate -properly. - -You may further tweak the operation by adding configuration options, described -below. - -=head1 ADVANCED CONFIGURATION - -Configuration is completely optional and is specified within MyApp->config->{static}. -If you use any of these options, the module will probably feel less "simple" to you! - -=over 4 - -=item Forcing directories into static mode - -Define a list of top-level directories beneath your 'root' directory that -should always be served in static mode. Regular expressions may be -specified using qr//. - - MyApp->config->{static}->{dirs} = [ - 'static', - qr/^(images|css)/, - ]; - -=item Including additional directories (experimental!) - -You may specify a list of directories in which to search for your static files. The -directories will be searched in order and will return the first file found. Note that -your root directory is B automatically added to the search path when -you specify an include_path. You should use MyApp->config->{root} to add it. - - MyApp->config->{static}->{include_path} = [ - '/path/to/overlay', - \&incpath_generator, - MyApp->config->{root} - ]; - -With the above setting, a request for the file /images/logo.jpg will search for the -following files, returning the first one found: - - /path/to/overlay/images/logo.jpg - /dynamic/path/images/logo.jpg - /your/app/home/root/images/logo.jpg - -The include path can contain a subroutine reference to dynamically return a list of -available directories. This method will receive the $c object as a parameter and -should return a reference to a list of directories. Errors can be reported using -die(). This method will be called every time a file is requested that appears to -be a static file (i.e. it has an extension). - -For example: - - sub incpath_generator { - my $c = shift; - - if ( $c->session->{customer_dir} ) { - return [ $c->session->{customer_dir} ]; - } else { - die "No customer dir defined."; - } - } - -=item Custom MIME types - -To override or add to the default MIME types set by the MIME::Types module, -you may enter your own extension to MIME type mapping. - - MyApp->config->{static}->{mime_types} = { - jpg => 'image/jpg', - png => 'image/png', - }; - -=item Apache integration and performance - -Optionally, when running under mod_perl, Static::Simple can return DECLINED -on static files to allow Apache to serve the file. A check is first done to -make sure that Apache's DocumentRoot matches your Catalyst root, and that you -are not using any custom MIME types or multiple roots. To enable the Apache -support, you can set the following option. - - MyApp->config->{static}->{use_apache} = 1; - -By default this option is disabled because after several benchmarks it -appears that just serving the file from Catalyst is the better option. On a 3K -file, Catalyst appears to be around 25% faster, and is 42% faster on a 10K file. -My benchmarking was done using the following 'siege' command, so other -benchmarks would be welcome! - - siege -u http://server/static/css/10K.css -b -t 1M -c 1 - -For best static performance, you should still serve your static files directly -from Apache by defining a Location block similar to the following: - - - SetHandler default-handler - - -=item Debugging information - -Enable additional debugging information printed in the Catalyst log. This -is automatically enabled when running Catalyst in -Debug mode. - - MyApp->config->{static}->{debug} = 1; - -=back - -=cut - -sub dispatch { +# prepare_action is used to first check if the request path is a static file. +# If so, we skip all other prepare_action steps to improve performance. +sub prepare_action { my $c = shift; - my $path = $c->req->path; - + # is the URI in a static-defined path? foreach my $dir ( @{ $c->config->{static}->{dirs} } ) { - my $re = ( $dir =~ /^qr\// ) ? eval $dir : qr/^${dir}/; + my $re = ( $dir =~ /^qr\//xms ) ? eval $dir : qr/^${dir}/; + if ($@) { + $c->error( "Error compiling static dir regex '$dir': $@" ); + } if ( $path =~ $re ) { if ( $c->_locate_static_file ) { $c->_debug_msg( "from static directory" ) if ( $c->config->{static}->{debug} ); - return $c->_serve_static; + return; } else { $c->_debug_msg( "404: file not found: $path" ) if ( $c->config->{static}->{debug} ); $c->res->status( 404 ); - return 0; + return; } } } # Does the path have an extension? - if ( $path =~ /.*\.(\S{1,})$/ ) { + if ( $path =~ /.*\.(\S{1,})$/xms ) { # and does it exist? - if ( $c->_locate_static_file ) { - return $c->_serve_static; - } + return if ( $c->_locate_static_file ); } - return $c->NEXT::dispatch(@_); + return $c->NEXT::prepare_action(@_); } +# dispatch takes the file found during prepare_action and serves it +sub dispatch { + my $c = shift; + + return undef if ( $c->res->status == 404 ); + + if ( $c->_static_file ) { + return $c->_serve_static; + } + else { + return $c->NEXT::dispatch(@_); + } +} + +# finalize serves up final header information sub finalize { my $c = shift; @@ -190,17 +80,20 @@ sub finalize { no strict 'subs'; if ( $engine == 13 ) { return Apache::Constants::DECLINED; - } elsif ( $engine == 19 ) { + } + elsif ( $engine == 19 ) { return Apache::Const::DECLINED; - } elsif ( $engine == 20 ) { + } + elsif ( $engine == 20 ) { return Apache2::Const::DECLINED; } } - if ( $c->res->status =~ /^(1\d\d|[23]04)$/ ) { + if ( $c->res->status =~ /^(1\d\d|[23]04)$/xms ) { $c->res->headers->remove_content_headers; return $c->finalize_headers; } + return $c->NEXT::finalize(@_); } @@ -245,7 +138,7 @@ sub _locate_static_file { next; } } else { - $dir =~ s/\/$//; + $dir =~ s/\/$//xms; if ( -d $dir && -f $dir . '/' . $path ) { $c->_debug_msg( $dir . "/" . $path ) if ( $c->config->{static}->{debug} ); @@ -257,31 +150,6 @@ sub _locate_static_file { return undef; } -sub _ext_to_type { - my $c = shift; - - my $path = $c->req->path; - my $type; - - if ( $path =~ /.*\.(\S{1,})$/ ) { - my $ext = $1; - my $user_types = $c->config->{static}->{mime_types}; - if ( $type = $user_types->{$ext} || $c->_mime_types->mimeTypeOf( $ext ) ) { - $c->_debug_msg( "as $type" ) - if ( $c->config->{static}->{debug} ); - return $type; - } else { - $c->_debug_msg( "as text/plain (unknown extension $ext)" ) - if ( $c->config->{static}->{debug} ); - return 'text/plain'; - } - } else { - $c->_debug_msg( "as text/plain (no extension)" ) - if ( $c->config->{static}->{debug} ); - return 'text/plain'; - } -} - sub _serve_static { my $c = shift; @@ -289,30 +157,43 @@ sub _serve_static { # abort if running under mod_perl # note that we do not use the Apache method if the user has defined - # custom MIME types or is using include paths, as Apache would not know about them - if ( $c->config->{static}->{use_apache} ) { - if ( $c->engine =~ /Apache::MP(\d{2})/ && - !keys %{ $c->config->{static}->{mime_types} } && - $c->_static_file eq $c->config->{root} . '/' . $path ) { - - # check that Apache will serve the correct file - if ( $c->apache->document_root ne $c->config->{root} ) { - $c->log->warn( "Static::Simple: Your Apache DocumentRoot must be set to " . - $c->config->{root} . " to use the Apache feature. Yours is currently " . - $c->apache->document_root ); - } else { - $c->_debug_msg( "DECLINED to Apache" ) - if ( $c->config->{static}->{debug} ); - $c->_apache_mode( $1 ); - return undef; - } + # custom MIME types or is using include paths, as Apache would not know + # about them + APACHE_CHECK: + { + if ( $c->config->{static}->{use_apache} ) { + # check engine version + last APACHE_CHECK unless $c->engine =~ /Apache::MP(\d{2})/xms; + my $engine = $1; + + # skip if we have user-defined MIME types + last APACHE_CHECK if keys %{ $c->config->{static}->{mime_types} }; + + # skip if the file is in a user-defined include path + last APACHE_CHECK if $c->_static_file + ne $c->config->{root} . '/' . $path; + + # check that Apache will serve the correct file + if ( $c->apache->document_root ne $c->config->{root} ) { + $c->log->warn( "Static::Simple: Your Apache DocumentRoot" + . " must be set to " . $c->config->{root} + . " to use the Apache feature. Yours is" + . " currently " . $c->apache->document_root + ); + } + else { + $c->_debug_msg( "DECLINED to Apache" ) + if ( $c->config->{static}->{debug} ); + $c->_apache_mode( $engine ); + return undef; + } } } my $type = $c->_ext_to_type; - $path = $c->_static_file; - my $stat = stat( $path ); + my $full_path = $c->_static_file; + my $stat = stat( $full_path ); # the below code all from C::P::Static if ( $c->req->headers->if_modified_since ) { @@ -323,31 +204,209 @@ sub _serve_static { } } - my $content = read_file( $path ); + my $content = read_file( $full_path ); $c->res->headers->content_type( $type ); $c->res->headers->content_length( $stat->size ); $c->res->headers->last_modified( $stat->mtime ); $c->res->output( $content ); - return 1; + return 1; +} + +# looks up the correct MIME type for the current file extension +sub _ext_to_type { + my $c = shift; + + my $path = $c->req->path; + my $type; + + if ( $path =~ /.*\.(\S{1,})$/xms ) { + my $ext = $1; + my $user_types = $c->config->{static}->{mime_types}; + if ( $type = $user_types->{$ext} + || $c->_mime_types->mimeTypeOf( $ext ) ) { + $c->_debug_msg( "as $type" ) + if ( $c->config->{static}->{debug} ); + return $type; + } + else { + $c->_debug_msg( "as text/plain (unknown extension $ext)" ) + if ( $c->config->{static}->{debug} ); + return 'text/plain'; + } + } + else { + $c->_debug_msg( 'as text/plain (no extension)' ) + if ( $c->config->{static}->{debug} ); + return 'text/plain'; + } } sub _debug_msg { my ( $c, $msg ) = @_; - $c->_debug_message( [] ) unless ( $c->_debug_message ); + if ( !defined $c->_debug_message ) { + $c->_debug_message( [] ); + } - push @{ $c->_debug_message }, $msg if $msg; + if ( $msg ) { + push @{ $c->_debug_message }, $msg; + } return $c->_debug_message; } + +1; +__END__ + +=head1 NAME + +Catalyst::Plugin::Static::Simple - Make serving static pages painless. + +=head1 SYNOPSIS + + use Catalyst; + MyApp->setup( qw/Static::Simple/ ); + +=head1 DESCRIPTION + +The Static::Simple plugin is designed to make serving static content in your +application during development quick and easy, without requiring a single +line of code from you. + +It will detect static files used in your application by looking for file +extensions in the URI. By default, you can simply load this plugin and it +will immediately begin serving your static files with the correct MIME type. +The light-weight MIME::Types module is used to map file extensions to +IANA-registered MIME types. + +Note that actions mapped to paths using periods (.) will still operate +properly. + +You may further tweak the operation by adding configuration options, described +below. + +=head1 ADVANCED CONFIGURATION + +Configuration is completely optional and is specified within +MyApp->config->{static}. If you use any of these options, the module will +probably feel less "simple" to you! + +=over 4 + +=item Forcing directories into static mode + +Define a list of top-level directories beneath your 'root' directory that +should always be served in static mode. Regular expressions may be +specified using qr//. + + MyApp->config->{static}->{dirs} = [ + 'static', + qr/^(images|css)/, + ]; + +=item Including additional directories (experimental!) + +You may specify a list of directories in which to search for your static +files. The directories will be searched in order and will return the first +file found. Note that your root directory is B automatically added to +the search path when you specify an include_path. You should use +MyApp->config->{root} to add it. + + MyApp->config->{static}->{include_path} = [ + '/path/to/overlay', + \&incpath_generator, + MyApp->config->{root} + ]; + +With the above setting, a request for the file /images/logo.jpg will search +for the following files, returning the first one found: + + /path/to/overlay/images/logo.jpg + /dynamic/path/images/logo.jpg + /your/app/home/root/images/logo.jpg + +The include path can contain a subroutine reference to dynamically return a +list of available directories. This method will receive the $c object as a +parameter and should return a reference to a list of directories. Errors can +be reported using die(). This method will be called every time a file is +requested that appears to be a static file (i.e. it has an extension). + +For example: + + sub incpath_generator { + my $c = shift; + + if ( $c->session->{customer_dir} ) { + return [ $c->session->{customer_dir} ]; + } else { + die "No customer dir defined."; + } + } + +=item Custom MIME types + +To override or add to the default MIME types set by the MIME::Types module, +you may enter your own extension to MIME type mapping. + + MyApp->config->{static}->{mime_types} = { + jpg => 'image/jpg', + png => 'image/png', + }; + +=item Apache integration and performance + +Optionally, when running under mod_perl, Static::Simple can return DECLINED +on static files to allow Apache to serve the file. A check is first done to +make sure that Apache's DocumentRoot matches your Catalyst root, and that you +are not using any custom MIME types or multiple roots. To enable the Apache +support, you can set the following option. + + MyApp->config->{static}->{use_apache} = 1; + +By default this option is disabled because after several benchmarks it +appears that just serving the file from Catalyst is the better option. On a +3K file, Catalyst appears to be around 25% faster, and is 42% faster on a 10K +file. My benchmarking was done using the following 'siege' command, so other +benchmarks would be welcome! + + siege -u http://server/static/css/10K.css -b -t 1M -c 1 + +For best static performance, you should still serve your static files directly +from Apache by defining a Location block similar to the following: + + + SetHandler default-handler + + +=item Bypassing other plugins + +This plugin checks for a static file in the prepare_action stage. If the +request is for a static file, it will bypass all remaining prepare_action +steps. This means that by placing Static::Simple before all other plugins, +they will not execute when a static file is found. This can be helpful by +skipping session cookie checks for example. Or, if you want some plugins +to run even on static files, list them before Static::Simple. + +Currently, work done by plugins in any other prepare method will execute +normally. + +=item Debugging information + +Enable additional debugging information printed in the Catalyst log. This +is automatically enabled when running Catalyst in -Debug mode. + + MyApp->config->{static}->{debug} = 1; + +=back =head1 SEE ALSO -L, L, L +L, L, +L =head1 AUTHOR -Andy Grundman, C +Andy Grundman, =head1 THANKS @@ -367,5 +426,3 @@ This program is free software, you can redistribute it and/or modify it under the same terms as Perl itself. =cut - -1;