markstory / asset_compress

An asset compression plugin for CakePHP. Provides file concatenation and a flexible filter system for preprocessing and minification.
MIT License
370 stars 125 forks source link

Check for gzip compression #184

Open gameiqinc opened 10 years ago

gameiqinc commented 10 years ago

Enhancement request to add compression option.

Most CDNs such as Amazon S3 can serve up gzip compressed files, but are unable to detect whether the browser supports gzip compression. To solve this issue we have to upload both compressed an uncompressed versions to the CDN and have the application decide which one to serve up, depending on what the browser supports.

Most often this can be determined by examining the HTTP_ACCEPT_ENCODING env variable.

It would be very useful if the AssetCompress plugin could automatically mange this. An option could be added to the ini file to govern this behavior. For example...

[global]
deflate=auto // Automatically manages gzip compression
deflate=true // Always compress
deflate=false // Never compress

If the deflate option is set a compression filter would be added to create an asset.type.gz file. The helper would then based on the browser's ability to support gzip compression automatically link to either the .gz file or the normal asset file.

markstory commented 10 years ago

What would the setting do? Would it generate .gz version of the minified assets?

gameiqinc commented 10 years ago

Two things:

  1. Generate a gzipped version of the minified asset.
  2. More importantly instruct the helper to link to the correct version based on the availability of compression in the browser.

2 is important because most of the time this is handled by the server. (such as the apache DEFLATE directive) But CDNs do not have this ability, thus it must be done by the application.

So $this->AssetCompress->css('style'); should link to style.css if gzip is not supported by the browser or style.css.gz if it is.

To determine if the browser supports gzip compression the helper could examine env('HTTP_ACCEPT_ENCODING') which usually contains the string 'gzip' if it is supported.

Something like this could do the trick.

<?php
if (substr_count($_SERVER['HTTP_ACCEPT_ENCODING'], ‘gzip’))
  $compress=true; 
else 
  $compress=false;
?>
gameiqinc commented 10 years ago

To illustrate the importance of this... My minified assets consist of 280.7 KB total in compressible text. With gzip compression this could be reduced to 88.9 KB. A potential savings of 191.7 KB. Very significant.

My only option right now is to not use asset compress because it can't manage this compression. Instead, I have to manually minify and compress my assets.It would be awesome if AssetCompress could handle this.

markstory commented 10 years ago

If you're dealing with a CDN is there any reason you wouldn't use non-gzipped assets? It sounds like generating gzipped files would be part of your build/deploy process. Replacing the existing files with gzipped files before pushing content to the CDN solves the problem at hand quite elegantly I think.

gameiqinc commented 10 years ago

I think you're missing a key point. Yes, the CDN is capable of serving up gzipped files and yes you can replace the originals. However, this would mean I'm serving gzipped files for EVERY request. What if the request comes from a browser that doesn't support gzip decompression?

Therefore, AssetCompress->css() and AssetCompress-js() should add the .gz extension ONLY if we know the client can support it.

Does that make sense?

markstory commented 10 years ago

Yeah, but I'm not so sure the helper should magically add the gz extension. Perhaps it could be an option to script/CSS or a helper level switch that can be enabled as necessary by the application. Having the helper automatically decide could lead to confusion and there may be cases where other conditions are required to toggle gzipped assets.

gameiqinc commented 10 years ago

I don't think there would be any confusion if the following options were added (globally or under each sub section as appropriate)...

[global|css|js]
deflate=true|false // Compress or not
compressed=gz // The extension of the compressed file

With these settings in place the compressed file could be created and included as appropriate.

AssetCompress already uses filters to manage minifying assets. Why not go a step further and compress them too. It's in the name after all. ;)

markstory commented 10 years ago

Sure but the config file is generally static and would lack the ability to toggle per request entirely. I would prefer to have the hot application tell the helper when to use a gzipped asset vs. guessing based on request headers, as the helper has no idea whether or not the webserver can also gzip the response.

gameiqinc commented 10 years ago

The config file doesn't need to toggle anything. The helper toggels the link based on the following criteria:

A) If 'deflate' is true add compressed ext B) If 'deflate' is false do not add compressed ext C) If 'deflate' is auto and browser supports compression add compressed ext

This functionality is needed in AssetCompress when we know the webserver can't handle this. Like in the case of a CDN.

Again, it is impossible to use compression with CDNs if AssetCompress does not offer this feature.

markstory commented 10 years ago

My issue is with criteria C. I don't think the helper should be inspecting the request and making its own decisions. Instead I would much rather have the following

echo $this->AssetCompress->css('styles', array('gzip' => true));

Where the gzip option could be enabled by application logic that did the header detection or applied the other required criteria.

gameiqinc commented 10 years ago

Thanks for giving this some thought.

AssetCompress already makes decisions based on a combination of settings and environmental factors. It includes files differently based on timestamps, filters, debug level, etc. Why is making a decision based on env('HTTP_ACCEPT_ENCODING') any different?

Your suggested solution is not sufficient. Compression must be selected based on the browser's ability to support it. This decision is normally made by the web server based on the headers it receives from the browser. A CDN is incapable of making this decision therefore the responsibility falls on the application.

Since this must be an application based decision it is appropriate for AssetCompress to provide the functionality. Perhaps these articles can describe what I'm talking about better:

http://internetmarketingbyme.com/74/gzip-compression-using-amazon-s3-cloudfront-cdn/

http://docs.aws.amazon.com/AmazonCloudFront/latest/DeveloperGuide/ServingCompressedFiles.html

markstory commented 10 years ago

How is my solution not sufficient? Replacing the hard coded true with a variable generated by the application allows files to be conditionally loaded without the plugin needing to infer even more about what the developer wants.

None of the current decisions AssetCompress involve inspecting the request data. They are all a combination of configuration and filesystem contents. I'd like to refrain from adding more inference to the plugin. From my past experience every time I add more inference it causes problems for someone somewhere.

derekperkins commented 10 years ago

I'm with Mark on this one. I think your idea about getting this to work is a fantastic one, and having a gzip parameter to add to assetCompress is a great way to add the functionality without forcing a specific implementation on people.

On Sat, Nov 2, 2013 at 11:35 AM, Mark Story notifications@github.comwrote:

How is my solution not sufficient? Replacing the hard coded true with a variable generated by the application allows files to be conditionally loaded without the plugin needing to infer even more about what the developer wants.

None of the current decisions AssetCompress involve inspecting the request data. They are all a combination of configuration and filesystem contents. I'd like to refrain from adding more inference to the plugin. From my past experience every time I add more inference it causes problems for someone somewhere.

— Reply to this email directly or view it on GitHubhttps://github.com/markstory/asset_compress/issues/184#issuecomment-27626846 .

gameiqinc commented 10 years ago

Your solution is not sufficient because, unless I misunderstood, if the 'gzip' option is set to true compression would always be added. If the browser doesn't support compression it would not be able to load the compressed file and the site would break.

derekperkins commented 10 years ago

No, the logic about whether to gzip assets happens outside assetCompress, and you tell it per instance whether or not to gzip.

On Sat, Nov 2, 2013 at 11:38 AM, gunner1095 notifications@github.comwrote:

Your solution is not sufficient because, unless I misunderstood, if the 'gzip' option is set to true compression would always be added. If the browser doesn't support compression it would not be able to load the compressed file and the site would break.

— Reply to this email directly or view it on GitHubhttps://github.com/markstory/asset_compress/issues/184#issuecomment-27626916 .

gameiqinc commented 10 years ago

If 'gzip' => true will serve gzipped files to those browsers that can support it and uncompressed to those that can't than it will work.

gameiqinc commented 10 years ago

This is why I suggested gzip => auto or true or false to give the developer the flexibility to decide which way they want to go.

markstory commented 10 years ago

@gunner1095 No as a user you would set the gzip option to be what your application decides it should be. Meaning that the application needs to check the headers/apply any required logic. For example:

echo $this->AssetCompress->css('styles', array('gzip' => $useGzipAssets));

Would be more representative of how linking assets would look in an application. The $useGzipAssets value would need to be generated by the application.

gameiqinc commented 10 years ago

Ok, so something like this?

if (substr_count($_SERVER['HTTP_ACCEPT_ENCODING'], ‘gzip’)) {
   $gzip = true;
} else {
   $gzip = false;
}
echo $this->AssetCompress->css('styles', array('gzip' => $gzip));
gameiqinc commented 10 years ago

@markstory, yeah the lightbulb finally went on!

markstory commented 10 years ago

Sure if those are the conditions your application needs then that would be one way of doing it. My concerns with hardcoding environment checks into the helper are around applications behind load-balancers or reverse proxies. Not all proxies forward the headers in the same way. Hardcoding the environment value in the helper makes it harder to accommodate various hosting/deployment setups.

gameiqinc commented 10 years ago

Ok, got it. So, it looks to be fairly easy to add a filter to gzipcompress the output with something like this.

Filter/Gzip.php

App::uses('AssetFilter', 'AssetCompress.Lib');

class Gzip extends AssetFilter {

    protected $_settings = array(
            'compression_level' => 9
    );

    public function output($file, $contents) {
        return gzcompress($contents, $this->_settings['compression_level']);
    }

}

Although this would need to be modified to add the gz extension.

Then, the option to serve gzip compressed versions can be added to the helper.

dereuromark commented 10 years ago

@gunner1095 What would be the advantage of this custom Gzip filter over letting the apache/nginx setting apply browser-dependent auto-gzip-ing?

coryjthompson commented 9 years ago

@dereuromark I believe the advantage comes when you are offloading your static assets to something like S3 who don't offer auto-gzip-ing.

This would also be useful for me, so i'll look into it a bit more.