NAME

Plack::App::MCCS - Minify, Compress, Cache-control and Serve static files from Plack applications

EXTENDS

Plack::Component

SYNOPSIS

    # in your app.psgi:
    use Plack::Builder;
    use Plack::App::MCCS;

    my $app = sub { ... };

    # be happy with the defaults:
    builder {
            mount '/static' => Plack::App::MCCS->new(root => '/path/to/static_files')->to_app;
            mount '/' => $app;
    };

    # or tweak the app to suit your needs:
    builder {
            mount '/static' => Plack::App::MCCS->new(
                    root => '/path/to/static_files',
                    min_cache_dir => 'min_cache',
                    defaults => {
                            valid_for => 86400,
                            cache_control => ['private'],
                    },
                    types => {
                            '.htc' => {
                                    content_type => 'text/x-component',
                                    valid_for => 360,
                                    cache_control => ['no-cache', 'must-revalidate'],
                            },
                    },
            )->to_app;
            mount '/' => $app;
    };

    # or use the supplied middleware
    builder {
            enable 'Plack::Middleware::MCCS',
                    path => qr{^/static/},
                    root => '/path/to/static_files'; # all other options are supported
            $app;
    };

DESCRIPTION

Plack::App::MCCS is a Plack application that serves static files from a directory. It will prefer serving precompressed versions of files if they exist and the client supports it, and also prefer minified versions of CSS/JS files if they exist.

If IO::Compress::Gzip is installed, MCCS will also automatically compress files that do not have a precompressed version and save the compressed versions to disk (so it only happens once and not on every request to the same file).

If CSS::Minifier::XS and/or JavaScript::Minifier::XS are installed, it will also automatically minify CSS/JS files that do not have a preminified version and save them to disk (once again, will only happen once per file).

This means MCCS needs to have write privileges to the static files directory. It would be better if files are preminified and precompressed, say automatically in your build process (if such a process exists). However, at some projects where you don't have an automatic build process, it is not uncommon to forget to minify/precompress. That's where automatic minification/compression is useful.

Most importantly, MCCS will generate proper Cache Control headers for every file served, including Last-Modified, Expires, Cache-Control and even ETag (ETags are created automatically, once per file, and saved to disk for future requests). It will appropriately respond with 304 Not Modified for requests with headers If-Modified-Since or If-None-Match when these cache validations are fulfilled, without actually having to read the files' contents again.

MCCS is active by default, which means that if there are some things you don't want it to do, you have to tell it not to. This is on purpose, because doing these actions is the whole point of MCCS.

HOW DOES MCCS HANDLE REQUESTS?

When a request is handed to Plack::App::MCCS, the following process is performed:

HOW DO WEB CACHES WORK ANYWAY?

If you need more information on how caches work and cache control headers, read this great article.

COMPARISON WITH OTHER MODULES

NOTE: this section is probably out of date.

Similar functionalities can be added to an application by using one or more of the following Plack middlewares (among others):

Plack::Middleware::Static or Plack::App::File will serve static files, but lack all the features of this module.

Plack::Middleware::Static::Minifier will minify CSS and JS on every request, even to the same file.

Plack::Middleware::Precompressed will serve precompressed .gz files, but it relies on appending .gz to every request and sending it to the app. If the app returns 404 Not Found, it sends the request again without the .gz part. This might pollute your logs and I guess two requests to get one file is not better than one request. You can circumvent that with regex matching, but that isn't very comfortable

Plack::Middleware::Deflater will compress representations with gzip/deflate algorithms on every request, even to the same file.

Plack::Middleware::ETag - will create ETags for files, but will calculate them again on every request.

Plack::Middleware::ConditionalGET will handle If-None-Match and If-Modified-Since, but it does not prevent the requested file from being opened for reading even if 304 Not Modified is to be returned, wasting system calls.

Plack::Middleware::Header will allow you to add cache control headers manually.

In any case, no possible combination of any of the aformentioned middlewares seems to return proper (and configurable) Cache Control headers, so you need to do that manually, possibly with Plack::Middleware::Header, which is not just annoying if different file types have different cache settings, but doesn't even seem to work.

Plack::App::MCCS attempts to perform all of this faster and better.

CLASS METHODS

new( %opts )

Creates a new instance of this module. %opts must have the following keys:

root - the path to the root directory where static files reside.

%opts may have the following keys:

encoding - the character set to append to content-type headers when text files are returned. Defaults to UTF-8.

defaults - a hash-ref with some global defaults, the following options are supported:

min_cache_dir - For unminified files, by default minified files are generated in the same directory as the original file. If this attribute is specified they are instead generated within root/$min_cache_dir, and minified files outside that directory are ignored, unless requested directly. This can make it easier to filter out generated files when validating a deployment.

Giving minify, compress and etag false values is useful during development, when you don't want your project to be "polluted" with all those .gz, .min and .etag files.

types - a hash-ref with file extensions that may be served (keys must begin with a dot, so give '.css' and not 'css'). Every extension takes a hash-ref that might have valid_for and cache_control as with the defaults option, but also content_type with the content type to return for files with this extension (useful when Plack::MIME doesn't know the content type of a file).

If you don't want something to be cached, you need to give the valid_for option (either in defaults or for a specific file type) a value of either zero, or preferably any number lower than zero, which will cause MCCS to set an Expires header way in the past. You should also pass the cache_control option no_store and probably no_cache. When MCCS encounteres the no_store option, it does not automatically add the max-age option to the Cache-Control header.

OBJECT METHODS

call( \%env )

Plack automatically calls this method to handle a request. This is where the magic (or disaster) happens.

CAVEATS AND THINGS TO CONSIDER

DIAGNOSTICS

This module doesn't throw any exceptions, instead returning HTTP errors for the client and possibly issuing some warns. The following list should help you to determine some potential problems with MCCS:

CONFIGURATION AND ENVIRONMENT

Plack::App::MCCS requires no configuration files or environment variables.

DEPENDENCIES

Plack::App::MCCS depends on the following CPAN modules:

Plack::App::MCCS will use the following modules if they exist, in order to minify files or compress with specific algorithms.

INCOMPATIBILITIES WITH OTHER MODULES

None reported.

BUGS AND LIMITATIONS

Please report any bugs or feature requests to bug-Plack-App-MCCS@rt.cpan.org, or through the web interface at http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Plack-App-MCCS.

SEE ALSO

Plack::Middleware::MCCS, Plack::Middleware::Static, Plack::App::File, Plack::Builder.

AUTHOR

Ido Perlmuter ido@ido50.net

ACKNOWLEDGMENTS

Some of this module's code is based on Plack::App::File by Tatsuhiko Miyagawa and Plack::Middleware::ETag by Franck Cuny.

Christian Walde contributed new features and fixes for the 1.0.0 release.

LICENSE AND COPYRIGHT

Copyright (c) 2011-2023, Ido Perlmuter ido@ido50.net.

Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.