From 59d4c97ffc310df545814298e540385fb6031d78 Mon Sep 17 00:00:00 2001 From: Steve Clay Date: Tue, 28 Jun 2016 01:12:01 -0400 Subject: [PATCH] Minify now allows static file serving With slightly altered URLs, Minify can cache files so they're served directly from the filesystem instead of through PHP. A simple library helps create URLs and clearing the cache. See `static/README.md` for details. --- .gitignore | 1 + HISTORY.md | 1 + README.md | 4 ++ config.php | 6 ++ docs/FAQ.wiki.md | 4 +- lib/Minify/Config.php | 5 ++ static/.htaccess | 40 +++++++++++++ static/README.md | 85 ++++++++++++++++++++++++++++ static/gen.php | 127 ++++++++++++++++++++++++++++++++++++++++++ static/lib.php | 68 ++++++++++++++++++++++ 10 files changed, 340 insertions(+), 1 deletion(-) create mode 100644 static/.htaccess create mode 100644 static/README.md create mode 100644 static/gen.php create mode 100644 static/lib.php diff --git a/.gitignore b/.gitignore index ca2c1a9..3b38053 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ /composer.lock /vendor /.php_cs.cache +/static/[0-9]* diff --git a/HISTORY.md b/HISTORY.md index 641efe0..9153d96 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -1,5 +1,6 @@ ## Version 3.0.0 (unreleased) * The project root is now what is deployed as `min` +* Adds feature to serve static files directly * Installation requires use of Composer to install dependencies * Removes JSMin+ (unmaintained, high memory usage) * Removes DooDigestAuth diff --git a/README.md b/README.md index 83a22af..406b2c8 100644 --- a/README.md +++ b/README.md @@ -11,6 +11,10 @@ The stats above are from a [brief walkthrough](http://mrclay.org/index.php/2008/ Relative URLs in CSS files are rewritten to compensate for being served from a different directory. +## Static file serving + +Version 3 allows [serving files directly from the filesystem](static/README.md) for much better performance. We encourage you to try this feature. + ## Support Post to the [Google Group](http://groups.google.com/group/minify). diff --git a/config.php b/config.php index 1c1cd4f..02f01ca 100644 --- a/config.php +++ b/config.php @@ -7,6 +7,12 @@ */ +/** + * Enable the static serving feature + */ +$min_enableStatic = false; + + /** * Allow use of the Minify URI Builder app. Only set this to true while you need it. */ diff --git a/docs/FAQ.wiki.md b/docs/FAQ.wiki.md index 8142e6e..a091aab 100644 --- a/docs/FAQ.wiki.md +++ b/docs/FAQ.wiki.md @@ -4,7 +4,9 @@ The simple JSMin algorithm is the most reliable in PHP, but check the [CookBook] ## How fast is it? -Certainly not as fast as an HTTPd serving flat files. On a high-traffic site: +If you [serve static files](https://github.com/mrclay/minify/blob/master/static/README.md), it's as fast as your web server, and you should do this for high-traffic sites. + +The PHP-based server is not as fast, but still performs well thanks to an internal cache. Tips: * **Use a reverse proxy** to cache the Minify URLs. This is by far the most important tip. * Revision your Minify URIs (so far-off Expires headers will be sent). One way to do this is using [groups](UserGuide.wiki.md#using-groups-for-nicer-urls) and the [Minify_groupUri()](UserGuide.wiki.md#far-future-expires-headers) utility function. Without this, clients will re-request Minify URLs every 30 minutes to check for updates. diff --git a/lib/Minify/Config.php b/lib/Minify/Config.php index 9f1e8b8..ae5b58c 100644 --- a/lib/Minify/Config.php +++ b/lib/Minify/Config.php @@ -11,6 +11,11 @@ class Config */ public $enableBuilder = false; + /** + * @var bool + */ + public $enableStatic = false; + /** * @var bool */ diff --git a/static/.htaccess b/static/.htaccess new file mode 100644 index 0000000..f9cc303 --- /dev/null +++ b/static/.htaccess @@ -0,0 +1,40 @@ + + ExpiresActive On + ExpiresDefault "access plus 1 year" + + + + FileETag MTime Size + + + + mod_gzip_on yes + mod_gzip_dechunk yes + mod_gzip_keep_workfiles No + mod_gzip_minimum_file_size 1000 + mod_gzip_maximum_file_size 1000000 + mod_gzip_maximum_inmem_size 1000000 + mod_gzip_item_include mime ^text/.* + mod_gzip_item_include mime ^application/javascript$ + mod_gzip_item_include mime ^application/x-javascript$ + # Exclude old browsers and images since IE has trouble with this + mod_gzip_item_exclude reqheader "User-Agent: .*Mozilla/4\..*\[" + mod_gzip_item_exclude mime ^image/.* + + + + AddOutputFilterByType DEFLATE text/css text/javascript application/javascript application/x-javascript + BrowserMatch ^Mozilla/4 gzip-only-text/html + BrowserMatch ^Mozilla/4\.[0678] no-gzip + BrowserMatch \bMSIE !no-gzip + + + +RewriteEngine on + +# You may need RewriteBase on some servers +#RewriteBase /min/static + +RewriteCond %{REQUEST_FILENAME} !-f +RewriteRule ^(.*)$ gen.php [QSA,L] + diff --git a/static/README.md b/static/README.md new file mode 100644 index 0000000..9b84527 --- /dev/null +++ b/static/README.md @@ -0,0 +1,85 @@ + +# Static file serving + +**Note:** This feature is new and not extensively tested. + +Within this folder, Minify creates minified files on demand, serving them without the overhead of PHP at all. + +For example, when a visitor requests a URL like `/min/static/1467089473/f=js/my-script.js`, Minify creates the directories `1467089473/f=js`, and saves the minified file `my-script.js` in it. On following requests, the file is served directly. + +## Getting started + +1. Make sure the `static` directory is writable by your server. + +2. In `minify/config.php`, set `$min_enableStatic = true;` + +3. Request the test script http://example.org/min/static/0/f=min/quick-test.js + + This will create a new cache directory within `static` and redirect the browser to the new location, e.g. http://example.org/min/static/1467089473/f=min/quick-test.js. + + You should see the minified script and on the server the `static` directory should contain a new subdirectory tree with the static file. Following requests will serve the file directly. + +4. Delete the new subdirectory (e.g. `1467089473`) and refresh the browser. + +You should be redirected to the new location where the file and cache directory has been recreated. + +## Site integration + +You don't want to hardcode any URLs. Instead we'll use functions in `lib.php`: + +```php +require_once __DIR__ . '/path/to/static/lib.php'; + +$static_uri = "/min/static"; +$query = "b=scripts&f=1.js,2.js"; +$type = "js"; + +$uri = Minify\StaticService\build_uri($static_uri, $query, $type); +``` + +If you release a new build (or change any source file), you *must* clear the cache by deleting the entire directory: + +```php +require_once __DIR__ . '/path/to/static/lib.php'; + +Minify\StaticService\flush_cache(); +``` + +## URL rules + +As URLs result in files being created, they are more strictly formatted. + +* Arbitrary parameters (e.g. to bust a cache) are not permitted. +* URLs must end with `.js` or `.css`. + +If your URL does not end with `.js` or `.css`, you'll need to append `&z=.js` or `&z=.css` to the URL. E.g.: + +* http://example.org/min/static/1467089473/g=home-scripts&z=.js +* http://example.org/min/static/1467089473/f=styles.less&z=.css + +Note that `Minify\StaticService\build_uri` handles this automatically for you. + +URLs aren't canonical, so these URLs are all valid and will produce separate files: + +* http://example.org/min/static/1467089473/f=one/two/three.js +* http://example.org/min/static/1467089473/b=one/two&f=three.js +* http://example.org/min/static/1467089473/f=three.js&b=one/two&z=.js + +## Disable caching + +You can easily switch to use the regular `min/` endpoint during development: + +```php +config->enableStatic) { + die('Minify static serving is not enabled. Set $min_enableStatic = true; in config.php'); +} + +require __DIR__ . '/lib.php'; + +if (!is_writable(__DIR__)) { + http_response_code(500); + die('Directory is not writable.'); +} + +// parse request +// SCRIPT_NAME = /path/to/minify/static/gen.php +// REQUEST_URI = /path/to/minify/static/1467084520/b=path/to/minify&f=quick-test.js + +// "/path/to/minify/static" +$root_uri = dirname($_SERVER['SCRIPT_NAME']); + +// "/1467084520/b=path/to/minify&f=quick-test.js" +$uri = substr($_SERVER['REQUEST_URI'], strlen($root_uri)); + +if (!preg_match('~^/(\d+)/(.*)$~', $uri, $m)) { + http_response_code(404); + die('File not found'); +} + +// "1467084520" +$requested_cache_dir = $m[1]; + +// "b=path/to/minify&f=quick-test.js" +$query = $m[2]; + +// we basically want canonical querystrings because we make a file for each one. +// manual parsing is the only way to do this. The MinApp controller will validate +// these parameters anyway. +$get_params = array(); +foreach (explode('&', $query) as $piece) { + if (false === strpos($piece, '=')) { + $send_400(); + } + + list($key, $value) = explode('=', $piece, 2); + if (!in_array($key, array('f', 'g', 'b', 'z'))) { + $send_400(); + } + + if (isset($get_params[$key])) { + // already used + $send_400(); + } + + if ($key === 'z' && !preg_match('~^\.(css|js)$~', $value, $m)) { + $send_400(); + } + + $get_params[$key] = urldecode($value); +} + +$cache_time = Minify\StaticService\get_cache_time(); +if (!$cache_time) { + http_response_code(500); + die('Directory is not writable.'); +} + +$app->env = new Minify_Env(array( + 'get' => $get_params, +)); +$ctrl = $app->controller; +$options = $app->serveOptions; +$sources = $ctrl->createConfiguration($options)->getSources(); +if (!$sources) { + http_response_code(404); + die('File not found'); +} +if ($sources[0]->getId() === 'id::missingFile') { + $send_400("Bad URL: missing file"); +} + +// we need URL to end in appropriate extension +$type = $sources[0]->getContentType(); +$ext = ($type === Minify::TYPE_JS) ? '.js' : '.css'; +if (substr($query, - strlen($ext)) !== $ext) { + $send_301("$root_uri/$cache_time/{$query}&z=$ext"); +} + +// fix the cache dir in the URL +if ($cache_time !== $requested_cache_dir) { + $send_301("$root_uri/$cache_time/$query"); +} + +$content = $app->minify->combine($sources); + +// save and send file +$file = __DIR__ . "/$cache_time/$query"; +if (!is_dir(dirname($file))) { + mkdir(dirname($file), 0777, true); +} + +file_put_contents($file, $content); + +header("Content-Type: $type;charset=utf-8"); +header("Cache-Control: max-age=31536000"); +echo $content; diff --git a/static/lib.php b/static/lib.php new file mode 100644 index 0000000..665bc13 --- /dev/null +++ b/static/lib.php @@ -0,0 +1,68 @@ +