1 package Catalyst::Plugin::Static::Simple;
5 use base qw/Class::Accessor::Fast Class::Data::Inheritable/;
11 our $VERSION = '0.12';
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} );
32 $c->_debug_msg( "404: file not found: $path" )
33 if ( $c->config->{static}->{debug} );
34 $c->res->status( 404 );
39 # Does the path have an extension?
40 if ( $path =~ /.*\.(\S{1,})$/xms ) {
42 $c->_locate_static_file;
45 return $c->NEXT::ACTUAL::prepare_action(@_);
51 return if ( $c->res->status != 200 );
53 if ( $c->_static_file ) {
54 if ( $c->config->{static}->{no_logs} && $c->log->can('abort') ) {
57 return $c->_serve_static;
60 return $c->NEXT::ACTUAL::dispatch(@_);
67 # display all log messages
68 if ( $c->config->{static}->{debug} && scalar @{$c->_debug_msg} ) {
69 $c->log->debug( 'Static::Simple: ' . join q{ }, @{$c->_debug_msg} );
72 if ( $c->res->status =~ /^(1\d\d|[23]04)$/xms ) {
73 $c->res->headers->remove_content_headers;
74 return $c->finalize_headers;
77 return $c->NEXT::ACTUAL::finalize(@_);
85 if ( Catalyst->VERSION le '5.33' ) {
89 $c->config->{static}->{dirs} ||= [];
90 $c->config->{static}->{include_path} ||= [ $c->config->{root} ];
91 $c->config->{static}->{mime_types} ||= {};
92 $c->config->{static}->{ignore_extensions}
93 ||= [ qw/tmpl tt tt2 html xhtml/ ];
94 $c->config->{static}->{ignore_dirs} ||= [];
95 $c->config->{static}->{debug} ||= $c->debug;
96 if ( ! defined $c->config->{static}->{no_logs} ) {
97 $c->config->{static}->{no_logs} = 1;
100 # load up a MIME::Types object, only loading types with
101 # at least 1 file extension
102 $c->_static_mime_types( MIME::Types->new( only_complete => 1 ) );
104 # preload the type index hash so it's not built on the first request
105 $c->_static_mime_types->create_type_index;
108 # Search through all included directories for the static file
109 # Based on Template Toolkit INCLUDE_PATH code
110 sub _locate_static_file {
113 my $path = $c->req->path;
115 my @ipaths = @{ $c->config->{static}->{include_path} };
117 my $count = 64; # maximum number of directories to search
120 while ( @ipaths && --$count) {
121 my $dir = shift @ipaths || next DIR_CHECK;
123 if ( ref $dir eq 'CODE' ) {
124 eval { $dpaths = &$dir( $c ) };
126 $c->log->error( 'Static::Simple: include_path error: ' . $@ );
128 unshift @ipaths, @$dpaths;
133 if ( -d $dir && -f $dir . '/' . $path ) {
135 # do we need to ignore the file?
136 for my $ignore ( @{ $c->config->{static}->{ignore_dirs} } ) {
138 if ( $path =~ /^$ignore\// ) {
139 $c->_debug_msg( "Ignoring directory `$ignore`" )
140 if ( $c->config->{static}->{debug} );
145 # do we need to ignore based on extension?
147 ( @{ $c->config->{static}->{ignore_extensions} } ) {
148 if ( $path =~ /.*\.${ignore_ext}$/ixms ) {
149 $c->_debug_msg( "Ignoring extension `$ignore_ext`" )
150 if ( $c->config->{static}->{debug} );
155 $c->_debug_msg( 'Serving ' . $dir . '/' . $path )
156 if ( $c->config->{static}->{debug} );
157 return $c->_static_file( $dir . '/' . $path );
168 my $path = $c->req->path;
169 my $type = $c->_ext_to_type;
171 my $full_path = $c->_static_file;
172 my $stat = stat $full_path;
174 $c->res->headers->content_type( $type );
175 $c->res->headers->content_length( $stat->size );
176 $c->res->headers->last_modified( $stat->mtime );
178 if ( Catalyst->VERSION le '5.33' ) {
179 # old File::Slurp method
180 my $content = File::Slurp::read_file( $full_path );
181 $c->res->body( $content );
184 # new method, pass an IO::File object to body
185 my $fh = IO::File->new( $full_path, 'r' );
188 $c->res->body( $fh );
191 Catalyst::Exception->throw(
192 message => "Unable to open $full_path for reading" );
199 # looks up the correct MIME type for the current file extension
202 my $path = $c->req->path;
204 if ( $path =~ /.*\.(\S{1,})$/xms ) {
206 my $user_types = $c->config->{static}->{mime_types};
207 my $type = $user_types->{$ext}
208 || $c->_static_mime_types->mimeTypeOf( $ext );
210 $c->_debug_msg( "as $type" )
211 if ( $c->config->{static}->{debug} );
212 return ( ref $type ) ? $type->type : $type;
215 $c->_debug_msg( "as text/plain (unknown extension $ext)" )
216 if ( $c->config->{static}->{debug} );
221 $c->_debug_msg( 'as text/plain (no extension)' )
222 if ( $c->config->{static}->{debug} );
228 my ( $c, $msg ) = @_;
230 if ( !defined $c->_static_debug_message ) {
231 $c->_static_debug_message( [] );
235 push @{ $c->_static_debug_message }, $msg;
238 return $c->_static_debug_message;
246 Catalyst::Plugin::Static::Simple - Make serving static pages painless.
251 MyApp->setup( qw/Static::Simple/ );
255 The Static::Simple plugin is designed to make serving static content in your
256 application during development quick and easy, without requiring a single
257 line of code from you.
259 It will detect static files used in your application by looking for file
260 extensions in the URI. By default, you can simply load this plugin and it
261 will immediately begin serving your static files with the correct MIME type.
262 The light-weight MIME::Types module is used to map file extensions to
263 IANA-registered MIME types.
265 Note that actions mapped to paths using periods (.) will still operate
268 You may further tweak the operation by adding configuration options, described
271 =head1 ADVANCED CONFIGURATION
273 Configuration is completely optional and is specified within
274 MyApp->config->{static}. If you use any of these options, the module will
275 probably feel less "simple" to you!
277 =head2 Aborting request logging
279 Since Catalyst 5.50, there has been added support for dropping logging for a
280 request. This is enabled by default for static files, as static requests tend
281 to clutter the log output. However, if you want logging of static requests,
282 you can enable it by setting MyApp->config->{static}->{no_logs} to 0.
284 =head2 Forcing directories into static mode
286 Define a list of top-level directories beneath your 'root' directory that
287 should always be served in static mode. Regular expressions may be
288 specified using qr//.
290 MyApp->config->{static}->{dirs} = [
295 =head2 Including additional directories
297 You may specify a list of directories in which to search for your static
298 files. The directories will be searched in order and will return the first
299 file found. Note that your root directory is B<not> automatically added to
300 the search path when you specify an include_path. You should use
301 MyApp->config->{root} to add it.
303 MyApp->config->{static}->{include_path} = [
306 MyApp->config->{root}
309 With the above setting, a request for the file /images/logo.jpg will search
310 for the following files, returning the first one found:
312 /path/to/overlay/images/logo.jpg
313 /dynamic/path/images/logo.jpg
314 /your/app/home/root/images/logo.jpg
316 The include path can contain a subroutine reference to dynamically return a
317 list of available directories. This method will receive the $c object as a
318 parameter and should return a reference to a list of directories. Errors can
319 be reported using die(). This method will be called every time a file is
320 requested that appears to be a static file (i.e. it has an extension).
324 sub incpath_generator {
327 if ( $c->session->{customer_dir} ) {
328 return [ $c->session->{customer_dir} ];
330 die "No customer dir defined.";
334 =head2 Ignoring certain types of files
336 There are some file types you may not wish to serve as static files. Most
337 important in this category are your raw template files. By default, files
338 with the extensions tmpl, tt, tt2, html, and xhtml will be ignored by
339 Static::Simple in the interest of security. If you wish to define your own
340 extensions to ignore, use the ignore_extensions option:
342 MyApp->config->{static}->{ignore_extensions}
343 = [ qw/tmpl tt tt2 html xhtml/ ];
345 =head2 Ignoring entire directories
347 To prevent an entire directory from being served statically, you can use the
348 ignore_dirs option. This option contains a list of relative directory paths
349 to ignore. If using include_path, the path will be checked against every
352 MyApp->config->{static}->{ignore_dirs} = [ qw/tmpl css/ ];
354 For example, if combined with the above include_path setting, this
355 ignore_dirs value will ignore the following directories if they exist:
357 /path/to/overlay/tmpl
361 /your/app/home/root/tmpl
362 /your/app/home/root/css
364 =head2 Custom MIME types
366 To override or add to the default MIME types set by the MIME::Types module,
367 you may enter your own extension to MIME type mapping.
369 MyApp->config->{static}->{mime_types} = {
374 =head2 Compatibility with other plugins
376 Since version 0.12, Static::Simple plays nice with other plugins. It no
377 longer short-circuits the prepare_action stage as it was causing too many
378 compatibility issues with other plugins.
380 =head2 Debugging information
382 Enable additional debugging information printed in the Catalyst log. This
383 is automatically enabled when running Catalyst in -Debug mode.
385 MyApp->config->{static}->{debug} = 1;
387 =head1 USING WITH APACHE
389 While Static::Simple will work just fine serving files through Catalyst in
390 mod_perl, for increased performance, you may wish to have Apache handle the
391 serving of your static files. To do this, simply use a dedicated directory
392 for your static files and configure an Apache Location block for that
393 directory. This approach is recommended for production installations.
396 SetHandler default-handler
399 =head1 INTERNAL EXTENDED METHODS
401 Static::Simple extends the following steps in the Catalyst process.
403 =head2 prepare_action
405 prepare_action is used to first check if the request path is a static file.
406 If so, we skip all other prepare_action steps to improve performance.
410 dispatch takes the file found during prepare_action and writes it to the
415 finalize serves up final header information and displays any log messages.
419 setup initializes all default values.
423 L<Catalyst>, L<Catalyst::Plugin::Static>,
424 L<http://www.iana.org/assignments/media-types/>
428 Andy Grundman, <andy@hybridized.org>
432 Marcus Ramberg, <mramberg@cpan.org>
436 The authors of Catalyst::Plugin::Static:
442 For the include_path code from Template Toolkit:
448 This program is free software, you can redistribute it and/or modify it under
449 the same terms as Perl itself.