Complete rewrite to use Plack Middleware.
[catagits/Catalyst-Plugin-Static-Simple.git] / lib / Catalyst / Plugin / Static / Simple.pm
CommitLineData
d6d29b9b 1package Catalyst::Plugin::Static::Simple;
d925e93e 2use Moose::Role;
aa5935f1 3use namespace::autoclean;
d6d29b9b 4
9c5fb712 5use Plack::App::File;
6use Catalyst::Utils;
b1d96e3e 7
9c5fb712 8use Catalyst::Plugin::Static::Simple::Middleware;
b108737b 9
9c5fb712 10our $VERSION = '0.32';
d6d29b9b 11
aa5935f1 12before setup_finalize => sub {
9c5fb712 13 my $app = shift;
b108737b 14
ee1e7faf 15 my $config
9c5fb712 16 = $app->config->{'Plugin::Static::Simple'}
17 = $app->config->{'static'}
ee1e7faf 18 = Catalyst::Utils::merge_hashes(
9c5fb712 19 $app->config->{'Plugin::Static::Simple'} || {},
20 $app->config->{static} || {}
ee1e7faf 21 );
b108737b 22
bdf5afa1 23 $config->{dirs} ||= [];
9c5fb712 24 $config->{include_path} ||= [ $app->config->{root} ];
bdf5afa1 25 $config->{ignore_extensions} ||= [ qw/tmpl tt tt2 html xhtml/ ];
26 $config->{ignore_dirs} ||= [];
9c5fb712 27 $config->{debug} ||= $app->debug;
b108737b 28
9c5fb712 29 my $static_middleware = Catalyst::Plugin::Static::Simple::Middleware->new({
30 config => $config,
31 cat_app => ref($app) || $app,
32 content_type => $app->_build_content_type_callback,
33 });
b108737b 34
9c5fb712 35 $app->setup_middleware( $static_middleware );
36};
b108737b 37
9c5fb712 38sub _build_content_type_callback {
39 my ( $c ) = @_;
d6d29b9b 40
ee1e7faf 41 my $config = $c->config->{'Plugin::Static::Simple'};
9c5fb712 42 return sub {
43 my $full_path = shift;
44 my $mime_type;
b108737b 45
9c5fb712 46 if ( $config->{mime_types} && $full_path =~ /.*\.(\S{1,})$/xms ) {
47 $mime_type = $config->{mime_types}->{ $1 };
48 }
2cb3d585 49
9c5fb712 50 return $mime_type || Plack::MIME->mime_type($full_path) || 'text/plain';
2cb3d585 51 }
b1d96e3e 52}
53
ab02ca0d 54sub serve_static_file {
55 my ( $c, $full_path ) = @_;
56
9c5fb712 57 my $res;
b108737b 58
9c5fb712 59 if(! -f $full_path ) {
60 $res = Catalyst::Plugin::Static::Simple::Middleware->return_404;
61 } else {
62 my $file_app = Plack::App::File->new( {
63 file => $full_path,
64 content_type => $c->_build_content_type_callback
65 } );
66 $res = $file_app->call($c->req->env);
b1d96e3e 67 }
b108737b 68
9c5fb712 69 $c->res->from_psgi_response($res);
d6d29b9b 70}
b1d96e3e 71
721;
9c5fb712 73
b1d96e3e 74__END__
75
76=head1 NAME
77
78Catalyst::Plugin::Static::Simple - Make serving static pages painless.
79
80=head1 SYNOPSIS
81
b6fdf01d 82 package MyApp;
83 use Catalyst qw/ Static::Simple /;
84 MyApp->setup;
b648683e 85 # that's it; static content is automatically served by Catalyst
86 # from the application's root directory, though you can configure
87 # things or bypass Catalyst entirely in a production environment
88 #
89 # one caveat: the files must be served from an absolute path
6e89d83c 90 # (i.e. /images/foo.png)
b1d96e3e 91
92=head1 DESCRIPTION
93
bc5b1283 94The Static::Simple plugin is designed to make serving static content in
95your application during development quick and easy, without requiring a
96single line of code from you.
b1d96e3e 97
bc5b1283 98This plugin detects static files by looking at the file extension in the
99URL (such as B<.css> or B<.png> or B<.js>). The plugin uses the
100lightweight L<MIME::Types> module to map file extensions to
101IANA-registered MIME types, and will serve your static files with the
102correct MIME type directly to the browser, without being processed
103through Catalyst.
b1d96e3e 104
105Note that actions mapped to paths using periods (.) will still operate
106properly.
107
200e206c 108If the plugin can not find the file, the request is dispatched to your
b108737b 109application instead. This means you are responsible for generating a
200e206c 110C<404> error if your applicaton can not process the request:
111
112 # handled by static::simple, not dispatched to your application
113 /images/exists.png
aa5935f1 114
200e206c 115 # static::simple will not find the file and let your application
116 # handle the request. You are responsible for generating a file
117 # or returning a 404 error
118 /images/does_not_exist.png
119
bc5b1283 120Though Static::Simple is designed to work out-of-the-box, you can tweak
121the operation by adding various configuration options. In a production
122environment, you will probably want to use your webserver to deliver
123static content; for an example see L<USING WITH APACHE>, below.
124
41cac5ef 125=head1 DEFAULT BEHAVIOUR
bc5b1283 126
127By default, Static::Simple will deliver all files having extensions
128(that is, bits of text following a period (C<.>)), I<except> files
129having the extensions C<tmpl>, C<tt>, C<tt2>, C<html>, and
130C<xhtml>. These files, and all files without extensions, will be
131processed through Catalyst. If L<MIME::Types> doesn't recognize an
132extension, it will be served as C<text/plain>.
133
134To restate: files having the extensions C<tmpl>, C<tt>, C<tt2>, C<html>,
135and C<xhtml> I<will not> be served statically by default, they will be
136processed by Catalyst. Thus if you want to use C<.html> files from
137within a Catalyst app as static files, you need to change the
138configuration of Static::Simple. Note also that files having any other
139extension I<will> be served statically, so if you're using any other
140extension for template files, you should also change the configuration.
141
142Logging of static files is turned off by default.
b1d96e3e 143
144=head1 ADVANCED CONFIGURATION
145
bc5b1283 146Configuration is completely optional and is specified within
fa25e422 147C<MyApp-E<gt>config-E<gt>{Plugin::Static::Simple}>. If you use any of these options,
86880b0d 148this module will probably feel less "simple" to you!
b1d96e3e 149
bc5b1283 150=head2 Enabling request logging
2de14076 151
bc5b1283 152Since Catalyst 5.50, logging of static requests is turned off by
153default; static requests tend to clutter the log output and rarely
154reveal anything useful. However, if you want to enable logging of static
155requests, you can do so by setting
fa25e422 156C<MyApp-E<gt>config-E<gt>{Plugin::Static::Simple}-E<gt>{logging}> to 1.
2de14076 157
2268e329 158=head2 Forcing directories into static mode
b1d96e3e 159
bc5b1283 160Define a list of top-level directories beneath your 'root' directory
161that should always be served in static mode. Regular expressions may be
162specified using C<qr//>.
b1d96e3e 163
a5d909f1 164 MyApp->config(
fa25e422 165 'Plugin::Static::Simple' => {
a5d909f1 166 dirs => [
167 'static',
168 qr/^(images|css)/,
169 ],
170 }
171 );
b1d96e3e 172
fa43d6b5 173=head2 Including additional directories
b1d96e3e 174
175You may specify a list of directories in which to search for your static
bc5b1283 176files. The directories will be searched in order and will return the
177first file found. Note that your root directory is B<not> automatically
178added to the search path when you specify an C<include_path>. You should
179use C<MyApp-E<gt>config-E<gt>{root}> to add it.
b1d96e3e 180
a5d909f1 181 MyApp->config(
fa25e422 182 'Plugin::Static::Simple' => {
a5d909f1 183 include_path => [
184 '/path/to/overlay',
185 \&incpath_generator,
186 MyApp->config->{root},
187 ],
188 },
189 );
b108737b 190
bc5b1283 191With the above setting, a request for the file C</images/logo.jpg> will search
b1d96e3e 192for the following files, returning the first one found:
193
194 /path/to/overlay/images/logo.jpg
195 /dynamic/path/images/logo.jpg
196 /your/app/home/root/images/logo.jpg
b108737b 197
b1d96e3e 198The include path can contain a subroutine reference to dynamically return a
bc5b1283 199list of available directories. This method will receive the C<$c> object as a
b1d96e3e 200parameter and should return a reference to a list of directories. Errors can
bc5b1283 201be reported using C<die()>. This method will be called every time a file is
b1d96e3e 202requested that appears to be a static file (i.e. it has an extension).
203
204For example:
205
206 sub incpath_generator {
207 my $c = shift;
a5d909f1 208
b1d96e3e 209 if ( $c->session->{customer_dir} ) {
210 return [ $c->session->{customer_dir} ];
211 } else {
212 die "No customer dir defined.";
213 }
214 }
b108737b 215
8cc672a2 216=head2 Ignoring certain types of files
217
bc5b1283 218There are some file types you may not wish to serve as static files.
219Most important in this category are your raw template files. By
220default, files with the extensions C<tmpl>, C<tt>, C<tt2>, C<html>, and
221C<xhtml> will be ignored by Static::Simple in the interest of security.
222If you wish to define your own extensions to ignore, use the
223C<ignore_extensions> option:
8cc672a2 224
a5d909f1 225 MyApp->config(
fa25e422 226 'Plugin::Static::Simple' => {
a5d909f1 227 ignore_extensions => [ qw/html asp php/ ],
228 },
229 );
b108737b 230
8cc672a2 231=head2 Ignoring entire directories
232
bc5b1283 233To prevent an entire directory from being served statically, you can use
234the C<ignore_dirs> option. This option contains a list of relative
235directory paths to ignore. If using C<include_path>, the path will be
236checked against every included path.
8cc672a2 237
a5d909f1 238 MyApp->config(
fa25e422 239 'Plugin::Static::Simple' => {
a5d909f1 240 ignore_dirs => [ qw/tmpl css/ ],
241 },
242 );
b108737b 243
bc5b1283 244For example, if combined with the above C<include_path> setting, this
245C<ignore_dirs> value will ignore the following directories if they exist:
8cc672a2 246
247 /path/to/overlay/tmpl
248 /path/to/overlay/css
249 /dynamic/path/tmpl
250 /dynamic/path/css
251 /your/app/home/root/tmpl
b108737b 252 /your/app/home/root/css
b1d96e3e 253
2268e329 254=head2 Custom MIME types
b1d96e3e 255
bc5b1283 256To override or add to the default MIME types set by the L<MIME::Types>
257module, you may enter your own extension to MIME type mapping.
b1d96e3e 258
a5d909f1 259 MyApp->config(
fa25e422 260 'Plugin::Static::Simple' => {
a5d909f1 261 mime_types => {
262 jpg => 'image/jpg',
263 png => 'image/png',
264 },
265 },
266 );
2268e329 267
41cac5ef 268=head2 Controlling caching with Expires header
269
270The files served by Static::Simple will have a Last-Modified header set,
271which allows some browsers to cache them for a while. However if you want
272to explicitly set an Expires header, such as to allow proxies to cache your
273static content, then you can do so by setting the "expires" config option.
274
275The value indicates the number of seconds after access time to allow caching.
276So a value of zero really means "don't cache at all", and any higher values
277will keep the file around for that long.
278
279 MyApp->config(
fa25e422 280 'Plugin::Static::Simple' => {
41cac5ef 281 expires => 3600, # Caching allowed for one hour.
282 },
283 );
284
d38d0ed6 285=head2 Compatibility with other plugins
b1d96e3e 286
d38d0ed6 287Since version 0.12, Static::Simple plays nice with other plugins. It no
bc5b1283 288longer short-circuits the C<prepare_action> stage as it was causing too
289many compatibility issues with other plugins.
b1d96e3e 290
2268e329 291=head2 Debugging information
b1d96e3e 292
293Enable additional debugging information printed in the Catalyst log. This
294is automatically enabled when running Catalyst in -Debug mode.
295
a5d909f1 296 MyApp->config(
fa25e422 297 'Plugin::Static::Simple' => {
a5d909f1 298 debug => 1,
299 },
300 );
b108737b 301
2cb3d585 302=head1 USING WITH APACHE
303
6e89d83c 304While Static::Simple will work just fine serving files through Catalyst
305in mod_perl, for increased performance you may wish to have Apache
306handle the serving of your static files directly. To do this, simply use
307a dedicated directory for your static files and configure an Apache
308Location block for that directory This approach is recommended for
309production installations.
2cb3d585 310
6e89d83c 311 <Location /myapp/static>
2cb3d585 312 SetHandler default-handler
313 </Location>
b1d96e3e 314
bc5b1283 315Using this approach Apache will bypass any handling of these directories
316through Catalyst. You can leave Static::Simple as part of your
317application, and it will continue to function on a development server,
318or using Catalyst's built-in server.
319
6e89d83c 320In practice, your Catalyst application is probably (i.e. should be)
321structured in the recommended way (i.e., that generated by bootstrapping
322the application with the C<catalyst.pl> script, with a main directory
323under which is a C<lib/> directory for module files and a C<root/>
324directory for templates and static files). Thus, unless you break up
325this structure when deploying your app by moving the static files to a
326different location in your filesystem, you will need to use an Alias
327directive in Apache to point to the right place. You will then need to
328add a Directory block to give permission for Apache to serve these
329files. The final configuration will look something like this:
330
331 Alias /myapp/static /filesystem/path/to/MyApp/root/static
332 <Directory /filesystem/path/to/MyApp/root/static>
333 allow from all
334 </Directory>
335 <Location /myapp/static>
336 SetHandler default-handler
337 </Location>
338
071c0042 339If you are running in a VirtualHost, you can just set the DocumentRoot
b108737b 340location to the location of your root directory; see
071c0042 341L<Catalyst::Engine::Apache2::MP20>.
342
ab02ca0d 343=head1 PUBLIC METHODS
344
345=head2 serve_static_file $file_path
346
347Will serve the file located in $file_path statically. This is useful when
348you need to autogenerate them if they don't exist, or they are stored in a model.
349
350 package MyApp::Controller::User;
351
352 sub curr_user_thumb : PathPart("my_thumbnail.png") {
353 my ( $self, $c ) = @_;
354 my $file_path = $c->user->picture_thumbnail_path;
355 $c->serve_static_file($file_path);
356 }
357
033a7581 358=head1 INTERNAL EXTENDED METHODS
359
360Static::Simple extends the following steps in the Catalyst process.
361
b108737b 362=head2 prepare_action
033a7581 363
bc5b1283 364C<prepare_action> is used to first check if the request path is a static
365file. If so, we skip all other C<prepare_action> steps to improve
366performance.
033a7581 367
368=head2 dispatch
369
bc5b1283 370C<dispatch> takes the file found during C<prepare_action> and writes it
371to the output.
033a7581 372
373=head2 finalize
374
bc5b1283 375C<finalize> serves up final header information and displays any log
376messages.
033a7581 377
378=head2 setup
379
bc5b1283 380C<setup> initializes all default values.
033a7581 381
d6d29b9b 382=head1 SEE ALSO
383
b108737b 384L<Catalyst>, L<Catalyst::Plugin::Static>,
b1d96e3e 385L<http://www.iana.org/assignments/media-types/>
d6d29b9b 386
387=head1 AUTHOR
388
b1d96e3e 389Andy Grundman, <andy@hybridized.org>
d6d29b9b 390
fa43d6b5 391=head1 CONTRIBUTORS
392
393Marcus Ramberg, <mramberg@cpan.org>
ab02ca0d 394
bc5b1283 395Jesse Sheidlower, <jester@panix.com>
fa43d6b5 396
ab02ca0d 397Guillermo Roditi, <groditi@cpan.org>
398
9936ddfa 399Florian Ragwitz, <rafl@debian.org>
400
401Tomas Doran, <bobtfish@bobtfish.net>
402
403Justin Wheeler (dnm)
b108737b 404
7c97dd21 405Matt S Trout, <mst@shadowcat.co.uk>
406
41cac5ef 407Toby Corkindale, <tjc@wintrmute.net>
408
9c5fb712 409Graeme Lawton <cpan@per.ly>
410
411Mark Ellis <markellis@cpan.org>
412
d6d29b9b 413=head1 THANKS
414
415The authors of Catalyst::Plugin::Static:
416
417 Sebastian Riedel
418 Christian Hansen
419 Marcus Ramberg
420
421For the include_path code from Template Toolkit:
422
423 Andy Wardley
424
425=head1 COPYRIGHT
426
41cac5ef 427Copyright (c) 2005 - 2011
1cc75f96 428the Catalyst::Plugin::Static::Simple L</AUTHOR> and L</CONTRIBUTORS>
429as listed above.
430
431=head1 LICENSE
432
d6d29b9b 433This program is free software, you can redistribute it and/or modify it under
434the same terms as Perl itself.
435
436=cut