Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best way to compress HTML, CSS & JS with mod_deflate and mod_gzip disabled

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.

I am currently placing the following in my .htaccess file:

php_value output_handler ob_gzhandler

However, this only compresses the HTML and leaves out the CSS and JS.

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.

Note, Method 2 in The Definitive Post on Gzipping your CSS looks like a good solution, but I couldn't get it working. Has anyone else succeeded using this method?

like image 747
Charles Roper Avatar asked Sep 07 '08 16:09

Charles Roper


People also ask

How do I compress HTML and CSS?

Go to minifycode.com and click the CSS minifier tab. Then paste the CSS code into the input box and click the Minify CSS button. After the new minified code is generated, copy the code. Then go back to the css file of your website and replace the code with the new minified version.

Can CSS be compressed?

The more complex and larger the CSS files are, the longer the visitor has to wait until the site has completely finished loading. By compressing the CSS, you can reduce the size of the CSS file and increase the performance of your website.

Is it good to minify HTML?

Minification means to minimize code (HTML, CSS, JS) and markup in your web pages and script files. This reduces load times and bandwidth usage on websites. Moreover, it improves site speed and accessibility. Additionally, a user can access your website even with a limited data plan.


3 Answers

Sorry about the delay - it's a busy week for me.

Assumptions:

  • .htaccess is in the same file as compress.php
  • static files to be compressed are in static subdirectory

I started my solution from setting the following directives in .htaccess:

RewriteEngine on
RewriteRule ^static/.+\.(js|ico|gif|jpg|jpeg|png|css|swf)$ compress.php [NC]

It's required that your provider allows you to override mod_rewrite options in .htaccess files. Then the compress.php file itself can look like this:

<?php

$basedir = realpath( dirname($_SERVER['SCRIPT_FILENAME']) );
$file = realpath( $basedir . $_SERVER["REQUEST_URI"] );

if( !file_exists($file) && strpos($file, $basedir) === 0 ) {
    header("HTTP/1.0 404 Not Found");
    print "File does not exist.";
    exit();
}

$components = split('\.', basename($file));
$extension = strtolower( array_pop($components) );

switch($extension)
{
    case 'css':
        $mime = "text/css";
        break;
    default:
        $mime = "text/plain";
}

header( "Content-Type: " . $mime );
readfile($file);

You should of course add more mime types to the switch statement. I didn't want to make the solution dependant on the pecl fileinfo extension or any other magical mime type detecting libraries - this is the simplest approach.

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.

That's the

$basedir = realpath( dirname($_SERVER['SCRIPT_FILENAME']) );
$file = realpath( $basedir . $_SERVER["REQUEST_URI"] );

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.

Also I check if the resulting path is inside the script's directory tree. Add the headers for cache control as pilif suggested and you should have a working solution to your problem.

like image 75
macbirdie Avatar answered Oct 04 '22 08:10

macbirdie


What I do:

  • I place scripts in a js and stylesheets in a css dir, respectively.
  • In the Apache configuration, I add directives like so:

    <Directory /data/www/path/to/some/site/js/>
        AddHandler application/x-httpd-php .js
        php_value auto_prepend_file gzip-js.php
        php_flag zlib.output_compression On
    </Directory>
    <Directory /data/www/path/to/some/site/css/>
        AddHandler application/x-httpd-php .css
        php_value auto_prepend_file gzip-css.php
        php_flag zlib.output_compression On
    </Directory>
    
  • gzip-js.php in the js directory looks like this:

    <?php
        header("Content-type: text/javascript; charset: UTF-8");
    ?>
    
  • …and gzip-cs.php in the css directory looks like this:

    <?php
        header("Content-type: text/css; charset: UTF-8");
    ?>
    

This may not be the most elegant solution, but it most certainly is a simple one that requires few changes and works well.

like image 28
Sören Kuklau Avatar answered Oct 04 '22 10:10

Sören Kuklau


what ever you do, be careful about caching on the client side:

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.

If you are not, then it's your responsibility.

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.

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 = 'path/to/css/file'
$i = stat($p);
if ($_SERVER['HTTP_IF_MODIFIED_SINCE']){
    $imd = strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']);
    if ( ($imd > 0) && ($imd >= $i['mtime'])){
        header('HTTP/1.0 304 Not Modified');
        header('Expires:');
        header('Cache-Control:');
        header('Last-Modified: '.date('r', $i['mtime']));
        exit;
    }
}
header('Last-Modified: '.date('r', $i['mtime']));
header('Content-Type: text/css');
header('Content-Length: '.filesize($p));
header('Cache-Control:');
header('Pragma:');
header('Expires:');
readfile($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).

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.

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.

Also keep in mind that certain versions of IE (or the JScript-Runtime it uses) still have problems with GZIP-transferred content.

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.

like image 30
pilif Avatar answered Oct 04 '22 09:10

pilif