1 package Catalyst::Plugin::Static::Simple;
5 use base qw/Class::Accessor::Fast Class::Data::Inheritable/;
11 our $VERSION = '0.11';
13 __PACKAGE__->mk_classdata( qw/_static_mime_types/ );
14 __PACKAGE__->mk_accessors( qw/_static_file
15 _static_debug_message/ );
19 my $path = $c->req->path;
21 # is the URI in a static-defined path?
22 foreach my $dir ( @{ $c->config->{static}->{dirs} } ) {
23 my $re = ( $dir =~ /^qr\//xms ) ? eval $dir : qr/^${dir}/;
25 $c->error( "Error compiling static dir regex '$dir': $@" );
28 if ( $c->_locate_static_file ) {
29 $c->_debug_msg( 'from static directory' )
30 if ( $c->config->{static}->{debug} );
33 $c->_debug_msg( "404: file not found: $path" )
34 if ( $c->config->{static}->{debug} );
35 $c->res->status( 404 );
41 # Does the path have an extension?
42 if ( $path =~ /.*\.(\S{1,})$/xms ) {
44 return if ( $c->_locate_static_file );
47 return $c->NEXT::ACTUAL::prepare_action(@_);
53 return if ( $c->res->status != 200 );
55 if ( $c->_static_file ) {
56 if ( $c->config->{static}->{no_logs} && $c->log->can('abort') ) {
59 return $c->_serve_static;
62 return $c->NEXT::ACTUAL::dispatch(@_);
69 # display all log messages
70 if ( $c->config->{static}->{debug} && scalar @{$c->_debug_msg} ) {
71 $c->log->debug( 'Static::Simple: ' . join q{ }, @{$c->_debug_msg} );
74 if ( $c->res->status =~ /^(1\d\d|[23]04)$/xms ) {
75 $c->res->headers->remove_content_headers;
76 return $c->finalize_headers;
79 return $c->NEXT::ACTUAL::finalize(@_);
87 if ( Catalyst->VERSION le '5.33' ) {
91 $c->config->{static}->{dirs} ||= [];
92 $c->config->{static}->{include_path} ||= [ $c->config->{root} ];
93 $c->config->{static}->{mime_types} ||= {};
94 $c->config->{static}->{ignore_extensions} ||= [ qw/tt tt2 html xhtml/ ];
95 $c->config->{static}->{ignore_dirs} ||= [];
96 $c->config->{static}->{debug} ||= $c->debug;
97 if ( ! defined $c->config->{static}->{no_logs} ) {
98 $c->config->{static}->{no_logs} = 1;
101 # load up a MIME::Types object, only loading types with
102 # at least 1 file extension
103 $c->_static_mime_types( MIME::Types->new( only_complete => 1 ) );
105 # preload the type index hash so it's not built on the first request
106 $c->_static_mime_types->create_type_index;
109 # Search through all included directories for the static file
110 # Based on Template Toolkit INCLUDE_PATH code
111 sub _locate_static_file {
114 my $path = $c->req->path;
116 my @ipaths = @{ $c->config->{static}->{include_path} };
118 my $count = 64; # maximum number of directories to search
121 while ( @ipaths && --$count) {
122 my $dir = shift @ipaths || next DIR_CHECK;
124 if ( ref $dir eq 'CODE' ) {
125 eval { $dpaths = &$dir( $c ) };
127 $c->log->error( 'Static::Simple: include_path error: ' . $@ );
129 unshift @ipaths, @$dpaths;
134 if ( -d $dir && -f $dir . '/' . $path ) {
136 # do we need to ignore the file?
137 for my $ignore ( @{ $c->config->{static}->{ignore_dirs} } ) {
139 if ( $path =~ /^$ignore\// ) {
140 $c->_debug_msg( "Ignoring directory `$ignore`" )
141 if ( $c->config->{static}->{debug} );
146 # do we need to ignore based on extension?
148 ( @{ $c->config->{static}->{ignore_extensions} } ) {
149 if ( $path =~ /.*\.${ignore_ext}$/ixms ) {
150 $c->_debug_msg( "Ignoring extension `$ignore_ext`" )
151 if ( $c->config->{static}->{debug} );
156 $c->_debug_msg( 'Serving ' . $dir . '/' . $path )
157 if ( $c->config->{static}->{debug} );
158 return $c->_static_file( $dir . '/' . $path );
169 my $path = $c->req->path;
170 my $type = $c->_ext_to_type;
172 my $full_path = $c->_static_file;
173 my $stat = stat $full_path;
175 # the below code all from C::P::Static
176 if ( $c->req->headers->if_modified_since ) {
177 if ( $c->req->headers->if_modified_since == $stat->mtime ) {
178 $c->res->status( 304 ); # Not Modified
179 $c->res->headers->remove_content_headers;
184 $c->res->headers->content_type( $type );
185 $c->res->headers->content_length( $stat->size );
186 $c->res->headers->last_modified( $stat->mtime );
188 if ( Catalyst->VERSION le '5.33' ) {
189 # old File::Slurp method
190 my $content = File::Slurp::read_file( $full_path );
191 $c->res->body( $content );
194 # new method, pass an IO::File object to body
195 my $fh = IO::File->new( $full_path, 'r' );
198 $c->res->body( $fh );
201 Catalyst::Exception->throw(
202 message => "Unable to open $full_path for reading" );
209 # looks up the correct MIME type for the current file extension
212 my $path = $c->req->path;
214 if ( $path =~ /.*\.(\S{1,})$/xms ) {
216 my $user_types = $c->config->{static}->{mime_types};
217 my $type = $user_types->{$ext}
218 || $c->_static_mime_types->mimeTypeOf( $ext );
220 $c->_debug_msg( "as $type" )
221 if ( $c->config->{static}->{debug} );
222 return ( ref $type ) ? $type->type : $type;
225 $c->_debug_msg( "as text/plain (unknown extension $ext)" )
226 if ( $c->config->{static}->{debug} );
231 $c->_debug_msg( 'as text/plain (no extension)' )
232 if ( $c->config->{static}->{debug} );
238 my ( $c, $msg ) = @_;
240 if ( !defined $c->_static_debug_message ) {
241 $c->_static_debug_message( [] );
245 push @{ $c->_static_debug_message }, $msg;
248 return $c->_static_debug_message;
256 Catalyst::Plugin::Static::Simple - Make serving static pages painless.
261 MyApp->setup( qw/Static::Simple/ );
265 The Static::Simple plugin is designed to make serving static content in your
266 application during development quick and easy, without requiring a single
267 line of code from you.
269 It will detect static files used in your application by looking for file
270 extensions in the URI. By default, you can simply load this plugin and it
271 will immediately begin serving your static files with the correct MIME type.
272 The light-weight MIME::Types module is used to map file extensions to
273 IANA-registered MIME types.
275 Note that actions mapped to paths using periods (.) will still operate
278 You may further tweak the operation by adding configuration options, described
281 =head1 ADVANCED CONFIGURATION
283 Configuration is completely optional and is specified within
284 MyApp->config->{static}. If you use any of these options, the module will
285 probably feel less "simple" to you!
287 =head2 Aborting request logging
289 Since Catalyst 5.50, there has been added support for dropping logging for a
290 request. This is enabled by default for static files, as static requests tend
291 to clutter the log output. However, if you want logging of static requests,
292 you can enable it by setting MyApp->config->{static}->{no_logs} to 0.
294 =head2 Forcing directories into static mode
296 Define a list of top-level directories beneath your 'root' directory that
297 should always be served in static mode. Regular expressions may be
298 specified using qr//.
300 MyApp->config->{static}->{dirs} = [
305 =head2 Including additional directories
307 You may specify a list of directories in which to search for your static
308 files. The directories will be searched in order and will return the first
309 file found. Note that your root directory is B<not> automatically added to
310 the search path when you specify an include_path. You should use
311 MyApp->config->{root} to add it.
313 MyApp->config->{static}->{include_path} = [
316 MyApp->config->{root}
319 With the above setting, a request for the file /images/logo.jpg will search
320 for the following files, returning the first one found:
322 /path/to/overlay/images/logo.jpg
323 /dynamic/path/images/logo.jpg
324 /your/app/home/root/images/logo.jpg
326 The include path can contain a subroutine reference to dynamically return a
327 list of available directories. This method will receive the $c object as a
328 parameter and should return a reference to a list of directories. Errors can
329 be reported using die(). This method will be called every time a file is
330 requested that appears to be a static file (i.e. it has an extension).
334 sub incpath_generator {
337 if ( $c->session->{customer_dir} ) {
338 return [ $c->session->{customer_dir} ];
340 die "No customer dir defined.";
344 =head2 Ignoring certain types of files
346 There are some file types you may not wish to serve as static files. Most
347 important in this category are your raw template files. By default, files
348 with the extensions tt, tt2, html, and xhtml will be ignored by Static::Simple
349 in the interest of security. If you wish to define your own extensions to
350 ignore, use the ignore_extensions option:
352 MyApp->config->{static}->{ignore_extensions} = [ qw/tt tt2 html xhtml/ ];
354 =head2 Ignoring entire directories
356 To prevent an entire directory from being served statically, you can use the
357 ignore_dirs option. This option contains a list of relative directory paths
358 to ignore. If using include_path, the path will be checked against every
361 MyApp->config->{static}->{ignore_dirs} = [ qw/tmpl css/ ];
363 For example, if combined with the above include_path setting, this
364 ignore_dirs value will ignore the following directories if they exist:
366 /path/to/overlay/tmpl
370 /your/app/home/root/tmpl
371 /your/app/home/root/css
373 =head2 Custom MIME types
375 To override or add to the default MIME types set by the MIME::Types module,
376 you may enter your own extension to MIME type mapping.
378 MyApp->config->{static}->{mime_types} = {
383 =head2 Bypassing other plugins
385 This plugin checks for a static file in the prepare_action stage. If the
386 request is for a static file, it will bypass all remaining prepare_action
387 steps. This means that by placing Static::Simple before all other plugins,
388 they will not execute when a static file is found. This can be helpful by
389 skipping session cookie checks for example. Or, if you want some plugins
390 to run even on static files, list them before Static::Simple.
392 Currently, work done by plugins in any other prepare method will execute
395 =head2 Debugging information
397 Enable additional debugging information printed in the Catalyst log. This
398 is automatically enabled when running Catalyst in -Debug mode.
400 MyApp->config->{static}->{debug} = 1;
402 =head1 USING WITH APACHE
404 While Static::Simple will work just fine serving files through Catalyst in
405 mod_perl, for increased performance, you may wish to have Apache handle the
406 serving of your static files. To do this, simply use a dedicated directory
407 for your static files and configure an Apache Location block for that
408 directory. This approach is recommended for production installations.
411 SetHandler default-handler
414 =head1 INTERNAL EXTENDED METHODS
416 Static::Simple extends the following steps in the Catalyst process.
418 =head2 prepare_action
420 prepare_action is used to first check if the request path is a static file.
421 If so, we skip all other prepare_action steps to improve performance.
425 dispatch takes the file found during prepare_action and writes it to the
430 finalize serves up final header information and displays any log messages.
434 setup initializes all default values.
438 L<Catalyst>, L<Catalyst::Plugin::Static>,
439 L<http://www.iana.org/assignments/media-types/>
443 Andy Grundman, <andy@hybridized.org>
447 Marcus Ramberg, <mramberg@cpan.org>
451 The authors of Catalyst::Plugin::Static:
457 For the include_path code from Template Toolkit:
463 This program is free software, you can redistribute it and/or modify it under
464 the same terms as Perl itself.