<p>I have a few sites on a shared host that is running Apache 2. I would like to compress the HTML, CSS and Javascript that is delivered to the browser. The host has disabled mod_deflate and mod_gzip, so these options are out. I do have PHP 5 at my disposal, though, so I could use the gzip component of that.</p>

<p>I am currently placing the following in my .htaccess file:</p>

<blockquote>
  <p>php_value output_handler ob_gzhandler</p>
</blockquote>

<p>However, this only compresses the HTML and leaves out the CSS and JS.</p>

<p>Is there a reliable way of transparently compressing the output of the CSS and JS without having to change every page? I have searched Google and a number of solutions are presented, but I've yet to get one to work. If anyone could suggest a solution that they know to work, that would be very gratefully received.</p>

<p>Note, <strong>Method 2</strong> in <strong><a href="http://www.fiftyfoureleven.com/weblog/web-development/css/the-definitive-css-gzip-method" rel="noreferrer">The Definitive Post on Gzipping your CSS</a></strong> looks like a good solution, but I couldn't get it working. Has anyone else succeeded using this method?</p>

[{'answer_id': 48583, 'author': 'macbirdie', 'author_id': 5049, 'author_profile': 'https://Stackoverflow.com/users/5049', 'pm_score': 1, 'selected': False, 'text': '<p>You can try your luck with <a href="http://httpd.apache.org/docs/2.2/mod/mod_rewrite.html" rel="nofollow noreferrer">mod_rewrite</a>.</p>\n\n<p>Create a script that takes a local static file name as input, through e.g. <code>$_SERVER[\'QUERY_STRING\']</code> and outputs it in compressed form. Many providers don\'t allow configuring <code>mod_rewrite</code> with <code>.htaccess</code> files or have it completely disabled though.</p>\n\n<p>If you haven\'t used <em>rewrite</em> before, I recommend a good beginner\'s guide, like probably <a href="http://www.workingwith.me.uk/articles/scripting/mod_rewrite" rel="nofollow noreferrer">this one</a>.\nThis way you can make the apache redirect all requests for a static file to a php script. style.css will be redirected to <em>compress.php?style.css</em> for instance.</p>\n\n<p>As always be <strong>extremely</strong> cautious on the input you accept or you have an <code>XSS</code> exploit on your hands!</p>\n'}, {'answer_id': 48592, 'author': 'Sören Kuklau', 'author_id': 1600, 'author_profile': 'https://Stackoverflow.com/users/1600', 'pm_score': 2, 'selected': False, 'text': '<p>What I do:</p>\n\n<ul>\n<li>I place scripts in a <code>js</code> and stylesheets in a <code>css</code> dir, respectively.</li>\n<li><p>In the Apache configuration, I add directives like so:</p>\n\n<pre><code>&lt;Directory /data/www/path/to/some/site/js/&gt;\n    AddHandler application/x-httpd-php .js\n    php_value auto_prepend_file gzip-js.php\n    php_flag zlib.output_compression On\n&lt;/Directory&gt;\n&lt;Directory /data/www/path/to/some/site/css/&gt;\n    AddHandler application/x-httpd-php .css\n    php_value auto_prepend_file gzip-css.php\n    php_flag zlib.output_compression On\n&lt;/Directory&gt;\n</code></pre></li>\n<li><p>gzip-js.php in the <code>js</code> directory looks like this:</p>\n\n<pre><code>&lt;?php\n    header("Content-type: text/javascript; charset: UTF-8");\n?&gt;\n</code></pre></li>\n<li><p>…and gzip-cs.php in the <code>css</code> directory looks like this:</p>\n\n<pre><code>&lt;?php\n    header("Content-type: text/css; charset: UTF-8");\n?&gt;\n</code></pre></li>\n</ul>\n\n<p>This may not be the most elegant solution, but it most certainly is a simple one that requires few changes and works well.</p>\n'}, {'answer_id': 48761, 'author': 'pilif', 'author_id': 5083, 'author_profile': 'https://Stackoverflow.com/users/5083', 'pm_score': 1, 'selected': False, 'text': "<p>what ever you do, be careful about caching on the client side:</p>\n\n<p>Browsers do all sort of tricks to try and minimize the bandwith and there are many ways in the HTTP protocol to do that, all of which are dealt with by apache - if you are just serving a local file. </p>\n\n<p>If you are not, then <strong>it's your responsibility</strong>.</p>\n\n<p>Have a look at least at the ETag and the If-Modified-Since mechanics which are supported by all current browsers and seem to be the most robust way to query the server for updated content. </p>\n\n<p>A possible way to serve a CSS file to browsers using the If-Modified-Since-Header is something like this (the empty headers to turn off any non-caching headers PHP sends per default):</p>\n\n<pre><code>$p = 'path/to/css/file'\n$i = stat($p);\nif ($_SERVER['HTTP_IF_MODIFIED_SINCE']){\n    $imd = strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']);\n    if ( ($imd &gt; 0) &amp;&amp; ($imd &gt;= $i['mtime'])){\n        header('HTTP/1.0 304 Not Modified');\n        header('Expires:');\n        header('Cache-Control:');\n        header('Last-Modified: '.date('r', $i['mtime']));\n        exit;\n    }\n}\nheader('Last-Modified: '.date('r', $i['mtime']));\nheader('Content-Type: text/css');\nheader('Content-Length: '.filesize($p));\nheader('Cache-Control:');\nheader('Pragma:');\nheader('Expires:');\nreadfile($p);\n</code></pre>\n\n<p>The code will use the if-modified-since-header the browser sends to check if the actual file on the server has changed since the date the browser has given. If so, the file is sent, otherwise, a 304 Not Modified is returned and the browser does not have to re-download the whole content (and if it's intelligent enough, it keeps the parsed CSS around in memory too).</p>\n\n<p>There is another mechanic involving the server sending a unique ETag-Header for each piece of content. The Client will send that back using an  If-None-Match header allowing the server to decide not only on the date of last modification but also on the content itself.</p>\n\n<p>This just makes the code more complicated though, so I have left it out. FF, IE and Opera (probably Safari too) all send the If-Modified-Since header when they receive content with a Last-Modified header attached, so this works fine.</p>\n\n<p>Also keep in mind that certain versions of IE (or the JScript-Runtime it uses) <em>still</em> have problems with GZIP-transferred content.</p>\n\n<p>Oh. And I know that's not part of the question, but so does Acrobat in some versions. I've had cases and cases of white screens while serving PDFs with gzip transfer encoding.</p>\n"}, {'answer_id': 55092, 'author': 'macbirdie', 'author_id': 5049, 'author_profile': 'https://Stackoverflow.com/users/5049', 'pm_score': 3, 'selected': False, 'text': '<p>Sorry about the delay - it\'s a busy week for me.</p>\n\n<p>Assumptions:</p>\n\n<ul>\n<li><code>.htaccess</code> is in the same file as <code>compress.php</code></li>\n<li>static files to be compressed are in <code>static</code> subdirectory</li>\n</ul>\n\n<p>I started my solution from setting the following directives in .htaccess:</p>\n\n<pre><code>RewriteEngine on\nRewriteRule ^static/.+\\.(js|ico|gif|jpg|jpeg|png|css|swf)$ compress.php [NC]\n</code></pre>\n\n<p>It\'s required that your provider allows you to override <code>mod_rewrite</code> options in <code>.htaccess</code> files.\nThen the compress.php file itself can look like this:</p>\n\n<pre><code>&lt;?php\n\n$basedir = realpath( dirname($_SERVER[\'SCRIPT_FILENAME\']) );\n$file = realpath( $basedir . $_SERVER["REQUEST_URI"] );\n\nif( !file_exists($file) &amp;&amp; strpos($file, $basedir) === 0 ) {\n    header("HTTP/1.0 404 Not Found");\n    print "File does not exist.";\n    exit();\n}\n\n$components = split(\'\\.\', basename($file));\n$extension = strtolower( array_pop($components) );\n\nswitch($extension)\n{\n    case \'css\':\n        $mime = "text/css";\n        break;\n    default:\n        $mime = "text/plain";\n}\n\nheader( "Content-Type: " . $mime );\nreadfile($file);\n</code></pre>\n\n<p>You should of course add more mime types to the switch statement. I didn\'t want to make the solution dependant on the pecl <code>fileinfo</code> extension or any other magical mime type detecting libraries - this is the simplest approach.</p>\n\n<p>As for securing the script - I do a translation to a real path in the file system so no hacked \'../../../etc/passwd\' or other shellscript file paths don\'t go through.</p>\n\n<p>That\'s the</p>\n\n<pre><code>$basedir = realpath( dirname($_SERVER[\'SCRIPT_FILENAME\']) );\n$file = realpath( $basedir . $_SERVER["REQUEST_URI"] );\n</code></pre>\n\n<p>snippet. Although I\'m pretty sure most of the paths that are in other hierarchy than $basedir will get handled by the Apache before they even reach the script.</p>\n\n<p>Also I check if the resulting path is inside the script\'s directory tree.\nAdd the headers for cache control as pilif suggested and you should have a working solution to your problem.</p>\n'}, {'answer_id': 1937300, 'author': 'Paul D. Waite', 'author_id': 20578, 'author_profile': 'https://Stackoverflow.com/users/20578', 'pm_score': 1, 'selected': False, 'text': '<p>Instead of gzipping on the fly when users request the CSS and JavaScript files, you could gzip them ahead of time. As long as Apache serves them with the right headers, you’re golden.</p>\n\n<p>For example, on Mac OS X, gzipping a file on the command line is as easy as:</p>\n\n<pre><code>gzip -c styles.css &gt; styles-gzip.css\n</code></pre>\n\n<p>Might not be the sort of workflow that works for you though.</p>\n'}]
['https://Stackoverflow.com/questions/48555', 'https://Stackoverflow.com', 'https://Stackoverflow.com/users/1944/']