clean up Static::Simple, remove classdata
[catagits/Catalyst-Plugin-Static-Simple.git] / lib / Catalyst / Plugin / Static / Simple.pm
CommitLineData
d6d29b9b 1package Catalyst::Plugin::Static::Simple;
2
3use strict;
b06be085 4use warnings;
d6d29b9b 5use base qw/Class::Accessor::Fast Class::Data::Inheritable/;
d6d29b9b 6use File::stat;
bdf5afa1 7use File::Spec ();
8use IO::File ();
9use MIME::Types ();
d6d29b9b 10
bdf5afa1 11our $VERSION = '0.15';
d6d29b9b 12
bdf5afa1 13__PACKAGE__->mk_accessors( qw/_static_file _static_debug_message/ );
d6d29b9b 14
b1d96e3e 15sub prepare_action {
d6d29b9b 16 my $c = shift;
d6d29b9b 17 my $path = $c->req->path;
bdf5afa1 18 my $config = $c->config->{static};
792411e6 19
20 $path =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg;
b1d96e3e 21
d6d29b9b 22 # is the URI in a static-defined path?
bdf5afa1 23 foreach my $dir ( @{ $config->{dirs} } ) {
24 my $re = ( $dir =~ m{^qr/}xms ) ? eval $dir : qr/^${dir}/;
b1d96e3e 25 if ($@) {
26 $c->error( "Error compiling static dir regex '$dir': $@" );
27 }
d6d29b9b 28 if ( $path =~ $re ) {
792411e6 29 if ( $c->_locate_static_file( $path ) ) {
b06be085 30 $c->_debug_msg( 'from static directory' )
bdf5afa1 31 if $config->{debug};
d6d29b9b 32 } else {
33 $c->_debug_msg( "404: file not found: $path" )
bdf5afa1 34 if $config->{debug};
d6d29b9b 35 $c->res->status( 404 );
d6d29b9b 36 }
37 }
38 }
39
40 # Does the path have an extension?
b1d96e3e 41 if ( $path =~ /.*\.(\S{1,})$/xms ) {
d6d29b9b 42 # and does it exist?
792411e6 43 $c->_locate_static_file( $path );
d6d29b9b 44 }
45
82239955 46 return $c->NEXT::ACTUAL::prepare_action(@_);
d6d29b9b 47}
48
b1d96e3e 49sub dispatch {
50 my $c = shift;
51
2268e329 52 return if ( $c->res->status != 200 );
b1d96e3e 53
54 if ( $c->_static_file ) {
bdf5afa1 55 if ( $c->config->{static}{no_logs} && $c->log->can('abort') ) {
a28d35e9 56 $c->log->abort( 1 );
57 }
b1d96e3e 58 return $c->_serve_static;
59 }
60 else {
82239955 61 return $c->NEXT::ACTUAL::dispatch(@_);
b1d96e3e 62 }
63}
64
d6d29b9b 65sub finalize {
66 my $c = shift;
67
68 # display all log messages
bdf5afa1 69 if ( $c->config->{static}{debug} && scalar @{$c->_debug_msg} ) {
be327929 70 $c->log->debug( 'Static::Simple: ' . join q{ }, @{$c->_debug_msg} );
d6d29b9b 71 }
72
b1d96e3e 73 if ( $c->res->status =~ /^(1\d\d|[23]04)$/xms ) {
d6d29b9b 74 $c->res->headers->remove_content_headers;
75 return $c->finalize_headers;
76 }
b1d96e3e 77
82239955 78 return $c->NEXT::ACTUAL::finalize(@_);
d6d29b9b 79}
80
81sub setup {
82 my $c = shift;
83
84 $c->NEXT::setup(@_);
85
033a7581 86 if ( Catalyst->VERSION le '5.33' ) {
87 require File::Slurp;
88 }
89
bdf5afa1 90 my $config = $c->config->{static} ||= {};
91
92 $config->{dirs} ||= [];
93 $config->{include_path} ||= [ $c->config->{root} ];
94 $config->{mime_types} ||= {};
95 $config->{ignore_extensions} ||= [ qw/tmpl tt tt2 html xhtml/ ];
96 $config->{ignore_dirs} ||= [];
97 $config->{debug} ||= $c->debug;
98 $config->{no_logs} = 1 unless defined $config->{no_logs};
d6d29b9b 99
100 # load up a MIME::Types object, only loading types with
101 # at least 1 file extension
bdf5afa1 102 $config->{mime_types_obj} = MIME::Types->new( only_complete => 1 );
2268e329 103
d6d29b9b 104 # preload the type index hash so it's not built on the first request
bdf5afa1 105 $config->{mime_types_obj}->create_type_index;
d6d29b9b 106}
107
108# Search through all included directories for the static file
109# Based on Template Toolkit INCLUDE_PATH code
110sub _locate_static_file {
792411e6 111 my ( $c, $path ) = @_;
d6d29b9b 112
bdf5afa1 113 $path = File::Spec->catdir(
114 File::Spec->no_upwards( File::Spec->splitdir( $path ) )
115 );
d6d29b9b 116
bdf5afa1 117 my $config = $c->config->{static};
118 my @ipaths = @{ $config->{include_path} };
d6d29b9b 119 my $dpaths;
120 my $count = 64; # maximum number of directories to search
121
8cc672a2 122 DIR_CHECK:
d6d29b9b 123 while ( @ipaths && --$count) {
8cc672a2 124 my $dir = shift @ipaths || next DIR_CHECK;
d6d29b9b 125
126 if ( ref $dir eq 'CODE' ) {
127 eval { $dpaths = &$dir( $c ) };
128 if ($@) {
b06be085 129 $c->log->error( 'Static::Simple: include_path error: ' . $@ );
d6d29b9b 130 } else {
b06be085 131 unshift @ipaths, @$dpaths;
8cc672a2 132 next DIR_CHECK;
d6d29b9b 133 }
134 } else {
48791b66 135 $dir =~ s/(\/|\\)$//xms;
d6d29b9b 136 if ( -d $dir && -f $dir . '/' . $path ) {
8cc672a2 137
138 # do we need to ignore the file?
bdf5afa1 139 for my $ignore ( @{ $config->{ignore_dirs} } ) {
48791b66 140 $ignore =~ s{(/|\\)$}{};
141 if ( $path =~ /^$ignore(\/|\\)/ ) {
8cc672a2 142 $c->_debug_msg( "Ignoring directory `$ignore`" )
bdf5afa1 143 if $config->{debug};
8cc672a2 144 next DIR_CHECK;
145 }
146 }
147
148 # do we need to ignore based on extension?
bdf5afa1 149 for my $ignore_ext ( @{ $config->{ignore_extensions} } ) {
150 if ( $path =~ /.*\.${ignore_ext}$/ixms ) {
151 $c->_debug_msg( "Ignoring extension `$ignore_ext`" )
152 if $config->{debug};
153 next DIR_CHECK;
154 }
8cc672a2 155 }
156
157 $c->_debug_msg( 'Serving ' . $dir . '/' . $path )
bdf5afa1 158 if $config->{debug};
d6d29b9b 159 return $c->_static_file( $dir . '/' . $path );
160 }
161 }
162 }
163
2268e329 164 return;
d6d29b9b 165}
166
d6d29b9b 167sub _serve_static {
168 my $c = shift;
792411e6 169
b1d96e3e 170 my $full_path = $c->_static_file;
792411e6 171 my $type = $c->_ext_to_type( $full_path );
172 my $stat = stat $full_path;
d6d29b9b 173
d6d29b9b 174 $c->res->headers->content_type( $type );
175 $c->res->headers->content_length( $stat->size );
176 $c->res->headers->last_modified( $stat->mtime );
2cb3d585 177
178 if ( Catalyst->VERSION le '5.33' ) {
179 # old File::Slurp method
180 my $content = File::Slurp::read_file( $full_path );
5224ce15 181 $c->res->body( $content );
2cb3d585 182 }
183 else {
5224ce15 184 # new method, pass an IO::File object to body
185 my $fh = IO::File->new( $full_path, 'r' );
186 if ( defined $fh ) {
033a7581 187 binmode $fh;
5224ce15 188 $c->res->body( $fh );
189 }
190 else {
191 Catalyst::Exception->throw(
2cb3d585 192 message => "Unable to open $full_path for reading" );
2cb3d585 193 }
2cb3d585 194 }
195
b1d96e3e 196 return 1;
197}
198
199# looks up the correct MIME type for the current file extension
200sub _ext_to_type {
792411e6 201 my ( $c, $full_path ) = @_;
b1d96e3e 202
bdf5afa1 203 my $config = $c->config->{static};
204
792411e6 205 if ( $full_path =~ /.*\.(\S{1,})$/xms ) {
b1d96e3e 206 my $ext = $1;
bdf5afa1 207 my $type = $config->{mime_types}{$ext}
208 || $config->{mime_types_obj}->mimeTypeOf( $ext );
2268e329 209 if ( $type ) {
bdf5afa1 210 $c->_debug_msg( "as $type" ) if $config->{debug};
5224ce15 211 return ( ref $type ) ? $type->type : $type;
b1d96e3e 212 }
213 else {
214 $c->_debug_msg( "as text/plain (unknown extension $ext)" )
bdf5afa1 215 if $config->{debug};
b1d96e3e 216 return 'text/plain';
217 }
218 }
219 else {
220 $c->_debug_msg( 'as text/plain (no extension)' )
bdf5afa1 221 if $config->{debug};
b1d96e3e 222 return 'text/plain';
223 }
d6d29b9b 224}
225
226sub _debug_msg {
227 my ( $c, $msg ) = @_;
228
2268e329 229 if ( !defined $c->_static_debug_message ) {
230 $c->_static_debug_message( [] );
b1d96e3e 231 }
d6d29b9b 232
b1d96e3e 233 if ( $msg ) {
2268e329 234 push @{ $c->_static_debug_message }, $msg;
b1d96e3e 235 }
d6d29b9b 236
2268e329 237 return $c->_static_debug_message;
d6d29b9b 238}
b1d96e3e 239
2401;
241__END__
242
243=head1 NAME
244
245Catalyst::Plugin::Static::Simple - Make serving static pages painless.
246
247=head1 SYNOPSIS
248
249 use Catalyst;
250 MyApp->setup( qw/Static::Simple/ );
bc5b1283 251 # that's it; static content is automatically served by
252 # Catalyst, though you can configure things or bypass
253 # Catalyst entirely in a production environment
b1d96e3e 254
255=head1 DESCRIPTION
256
bc5b1283 257The Static::Simple plugin is designed to make serving static content in
258your application during development quick and easy, without requiring a
259single line of code from you.
b1d96e3e 260
bc5b1283 261This plugin detects static files by looking at the file extension in the
262URL (such as B<.css> or B<.png> or B<.js>). The plugin uses the
263lightweight L<MIME::Types> module to map file extensions to
264IANA-registered MIME types, and will serve your static files with the
265correct MIME type directly to the browser, without being processed
266through Catalyst.
b1d96e3e 267
268Note that actions mapped to paths using periods (.) will still operate
269properly.
270
bc5b1283 271Though Static::Simple is designed to work out-of-the-box, you can tweak
272the operation by adding various configuration options. In a production
273environment, you will probably want to use your webserver to deliver
274static content; for an example see L<USING WITH APACHE>, below.
275
276=head1 DEFAULT BEHAVIOR
277
278By default, Static::Simple will deliver all files having extensions
279(that is, bits of text following a period (C<.>)), I<except> files
280having the extensions C<tmpl>, C<tt>, C<tt2>, C<html>, and
281C<xhtml>. These files, and all files without extensions, will be
282processed through Catalyst. If L<MIME::Types> doesn't recognize an
283extension, it will be served as C<text/plain>.
284
285To restate: files having the extensions C<tmpl>, C<tt>, C<tt2>, C<html>,
286and C<xhtml> I<will not> be served statically by default, they will be
287processed by Catalyst. Thus if you want to use C<.html> files from
288within a Catalyst app as static files, you need to change the
289configuration of Static::Simple. Note also that files having any other
290extension I<will> be served statically, so if you're using any other
291extension for template files, you should also change the configuration.
292
293Logging of static files is turned off by default.
b1d96e3e 294
295=head1 ADVANCED CONFIGURATION
296
bc5b1283 297Configuration is completely optional and is specified within
298C<MyApp-E<gt>config-E<gt>{static}>. If you use any of these options,
299this module will probably feel less "simple" to you!
b1d96e3e 300
bc5b1283 301=head2 Enabling request logging
2de14076 302
bc5b1283 303Since Catalyst 5.50, logging of static requests is turned off by
304default; static requests tend to clutter the log output and rarely
305reveal anything useful. However, if you want to enable logging of static
306requests, you can do so by setting
307C<MyApp-E<gt>config-E<gt>{static}-E<gt>{no_logs}> to 0.
2de14076 308
2268e329 309=head2 Forcing directories into static mode
b1d96e3e 310
bc5b1283 311Define a list of top-level directories beneath your 'root' directory
312that should always be served in static mode. Regular expressions may be
313specified using C<qr//>.
b1d96e3e 314
315 MyApp->config->{static}->{dirs} = [
316 'static',
317 qr/^(images|css)/,
318 ];
319
fa43d6b5 320=head2 Including additional directories
b1d96e3e 321
322You may specify a list of directories in which to search for your static
bc5b1283 323files. The directories will be searched in order and will return the
324first file found. Note that your root directory is B<not> automatically
325added to the search path when you specify an C<include_path>. You should
326use C<MyApp-E<gt>config-E<gt>{root}> to add it.
b1d96e3e 327
328 MyApp->config->{static}->{include_path} = [
329 '/path/to/overlay',
330 \&incpath_generator,
331 MyApp->config->{root}
332 ];
333
bc5b1283 334With the above setting, a request for the file C</images/logo.jpg> will search
b1d96e3e 335for the following files, returning the first one found:
336
337 /path/to/overlay/images/logo.jpg
338 /dynamic/path/images/logo.jpg
339 /your/app/home/root/images/logo.jpg
340
341The include path can contain a subroutine reference to dynamically return a
bc5b1283 342list of available directories. This method will receive the C<$c> object as a
b1d96e3e 343parameter and should return a reference to a list of directories. Errors can
bc5b1283 344be reported using C<die()>. This method will be called every time a file is
b1d96e3e 345requested that appears to be a static file (i.e. it has an extension).
346
347For example:
348
349 sub incpath_generator {
350 my $c = shift;
351
352 if ( $c->session->{customer_dir} ) {
353 return [ $c->session->{customer_dir} ];
354 } else {
355 die "No customer dir defined.";
356 }
357 }
8cc672a2 358
359=head2 Ignoring certain types of files
360
bc5b1283 361There are some file types you may not wish to serve as static files.
362Most important in this category are your raw template files. By
363default, files with the extensions C<tmpl>, C<tt>, C<tt2>, C<html>, and
364C<xhtml> will be ignored by Static::Simple in the interest of security.
365If you wish to define your own extensions to ignore, use the
366C<ignore_extensions> option:
8cc672a2 367
d38d0ed6 368 MyApp->config->{static}->{ignore_extensions}
bc5b1283 369 = [ qw/html asp php/ ];
8cc672a2 370
371=head2 Ignoring entire directories
372
bc5b1283 373To prevent an entire directory from being served statically, you can use
374the C<ignore_dirs> option. This option contains a list of relative
375directory paths to ignore. If using C<include_path>, the path will be
376checked against every included path.
8cc672a2 377
378 MyApp->config->{static}->{ignore_dirs} = [ qw/tmpl css/ ];
379
bc5b1283 380For example, if combined with the above C<include_path> setting, this
381C<ignore_dirs> value will ignore the following directories if they exist:
8cc672a2 382
383 /path/to/overlay/tmpl
384 /path/to/overlay/css
385 /dynamic/path/tmpl
386 /dynamic/path/css
387 /your/app/home/root/tmpl
388 /your/app/home/root/css
b1d96e3e 389
2268e329 390=head2 Custom MIME types
b1d96e3e 391
bc5b1283 392To override or add to the default MIME types set by the L<MIME::Types>
393module, you may enter your own extension to MIME type mapping.
b1d96e3e 394
395 MyApp->config->{static}->{mime_types} = {
396 jpg => 'image/jpg',
397 png => 'image/png',
398 };
2268e329 399
d38d0ed6 400=head2 Compatibility with other plugins
b1d96e3e 401
d38d0ed6 402Since version 0.12, Static::Simple plays nice with other plugins. It no
bc5b1283 403longer short-circuits the C<prepare_action> stage as it was causing too
404many compatibility issues with other plugins.
b1d96e3e 405
2268e329 406=head2 Debugging information
b1d96e3e 407
408Enable additional debugging information printed in the Catalyst log. This
409is automatically enabled when running Catalyst in -Debug mode.
410
411 MyApp->config->{static}->{debug} = 1;
2cb3d585 412
413=head1 USING WITH APACHE
414
415While Static::Simple will work just fine serving files through Catalyst in
416mod_perl, for increased performance, you may wish to have Apache handle the
417serving of your static files. To do this, simply use a dedicated directory
418for your static files and configure an Apache Location block for that
419directory. This approach is recommended for production installations.
420
421 <Location /static>
422 SetHandler default-handler
423 </Location>
b1d96e3e 424
bc5b1283 425Using this approach Apache will bypass any handling of these directories
426through Catalyst. You can leave Static::Simple as part of your
427application, and it will continue to function on a development server,
428or using Catalyst's built-in server.
429
033a7581 430=head1 INTERNAL EXTENDED METHODS
431
432Static::Simple extends the following steps in the Catalyst process.
433
434=head2 prepare_action
435
bc5b1283 436C<prepare_action> is used to first check if the request path is a static
437file. If so, we skip all other C<prepare_action> steps to improve
438performance.
033a7581 439
440=head2 dispatch
441
bc5b1283 442C<dispatch> takes the file found during C<prepare_action> and writes it
443to the output.
033a7581 444
445=head2 finalize
446
bc5b1283 447C<finalize> serves up final header information and displays any log
448messages.
033a7581 449
450=head2 setup
451
bc5b1283 452C<setup> initializes all default values.
033a7581 453
d6d29b9b 454=head1 SEE ALSO
455
b1d96e3e 456L<Catalyst>, L<Catalyst::Plugin::Static>,
457L<http://www.iana.org/assignments/media-types/>
d6d29b9b 458
459=head1 AUTHOR
460
b1d96e3e 461Andy Grundman, <andy@hybridized.org>
d6d29b9b 462
fa43d6b5 463=head1 CONTRIBUTORS
464
465Marcus Ramberg, <mramberg@cpan.org>
bc5b1283 466Jesse Sheidlower, <jester@panix.com>
fa43d6b5 467
d6d29b9b 468=head1 THANKS
469
470The authors of Catalyst::Plugin::Static:
471
472 Sebastian Riedel
473 Christian Hansen
474 Marcus Ramberg
475
476For the include_path code from Template Toolkit:
477
478 Andy Wardley
479
480=head1 COPYRIGHT
481
482This program is free software, you can redistribute it and/or modify it under
483the same terms as Perl itself.
484
485=cut