Update : Thanks to Paul Robertson for pointing out that as of AIR 2.0.2 released a couple of weeks back, there is native cross platform GZIP support in Air. However, if you need to achieve this in the 1.5 runtime for any reason, the below is still applicable!
To reduce bandwidth many servers compress HTTP responses using GZIP encoding. Pretty much all web browsers support GZIP decoding, so the actual data transfered is a fraction of the final unpacked response. Flex/Flash apps running in a web browser can take advantage of this as the browser handles the HTTP responses.
However, Flex AIR apps which do not run in a browser don’t have this luxury. By default, the headers sent in a request from AIR do not accept GZIP encoding, and even if they did, Flex / AIR runtime has no idea how to handle GZIP de-compression!!
Thanks to the Flex community however, it is possible!
To enable GZIP decoding you need to do 3 things:
- Set the header in the request to accept GZIP encoding – ‘Accept-Encoding’ – ‘gzip’.
- Set the data format of the response to expect binary data.
- Decode the binary data using H. Paul Robertson’s GZIPEncoder library classes.
The main problem is, depending on how you are loading your data (HTTPService, URLLoader), there are different ways of achieving this.
Below are 3 different approaches:
1) Wrap the URLLoader
First up we wrap the URLLoader in a simple class that will set the accept header to include gzip, set the dataFormat as Binary, decode the binary data with the GZIPBytesEncoder and finally format the data to the specified data format (Text/XML etc) before triggering a complete event
package com.skinkers.air.net
{
import com.probertson.utils.GZIPBytesEncoder;
import flash.errors.IllegalOperationError;
import flash.events.Event;
import flash.events.EventDispatcher;
import flash.net.URLLoader;
import flash.net.URLLoaderDataFormat;
import flash.net.URLRequest;
import flash.net.URLRequestHeader;
import flash.net.URLRequestMethod;
import flash.utils.ByteArray;
import flash.xml.XMLNode;
import mx.rpc.http.HTTPService;
public class GZIPLoader extends EventDispatcher
{
/** Format string used to set the value of <code>resultFormat</code>. Indicates the result should be e4x XML */
public static const FORMAT_E4X:String="e4x";
/** Format string used to set the value of <code>resultFormat</code>. Indicates the result should be AS XML Nodes*/
public static const FORMAT_XML:String="xml";
/** Format string used to set the value of <code>resultFormat</code>. Indicates the result should be simple text*/
public static const FORMAT_TEXT:String="text";
/** Format string used to set the value of <code>resultFormat</code>. Indicates the result should be a AS Object*/
public static const FORMAT_OBJECT:String="object";
/** The current format that we will decode the data too */
public var resultFormat:String = "";
/** The URL loader that we use to load the data */
private var _loader:URLLoader;
/**
* The URLLoader that GZIPLoader wraps
*/
public function get loader():URLLoader
{
return _loader;
}
/**
* Alias to the data property of the URLLoader that GZIPLoader wraps
*/
public function get data():Object
{
return loader.data;
}
/**
* GZIPLoader supports GZIP enconding by creating a URLLoader that will accept binary data and if it is GZipped will decode using probertson GZIPBytesDecoder
* @param url THe url of the data to load
* @param resultFormat The format of that data expected, once decoded.
*/
public function GZIPLoader( url:String, resultFormat:String="e4x" )
{
var request:URLRequest = new URLRequest(url);
request.contentType = HTTPService.CONTENT_TYPE_FORM;
request.method = URLRequestMethod.POST;
request.requestHeaders = [new URLRequestHeader('Accept-Encoding','gzip')];
this.resultFormat = resultFormat;
_loader = new URLLoader(request);
loader.dataFormat = URLLoaderDataFormat.BINARY;
loader.addEventListener(Event.COMPLETE, completeHandler);
}
/**
* Decode the response, re set the loader.data property and then dispatch our complete event
*/
private function completeHandler(event:Event):void
{
if ( loader.data is ByteArray )
{
var byteArray:ByteArray = loader.data as ByteArray;
try
{
var encoder:GZIPBytesEncoder = new GZIPBytesEncoder();
loader.data = encoder.uncompressToByteArray(byteArray).toString();
}
catch(error:IllegalOperationError)
{
//Not gzip compressed - assume utf8
byteArray.position = 0;//reset to start of bytearray
loader.data = byteArray.readUTFBytes(byteArray.length);
}
}
//Format the decoded data
switch(resultFormat)
{
case FORMAT_XML :
loader.data = new XMLNode(1, loader.data);
break;
case FORMAT_E4X :
loader.data = new XML(loader.data);
break;
case FORMAT_TEXT :
loader.data = String(loader.data);
break;
default :
loader.data = loader.data;
break;
}
dispatchEvent(new Event(Event.COMPLETE));
}
}
}
Then we can simply use this in our app as follows
<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication
xmlns:mx="http://www.adobe.com/2006/mxml"
layout="absolute"
creationComplete="creationCompleteHandler(event)"
>
<mx:Script>
<![CDATA[
import com.skinkers.net.GZIPLoader;
import mx.events.FlexEvent;
protected function creationCompleteHandler(event:FlexEvent):void
{
var gZipLoader:GZIPLoader = new GZIPLoader("http://www.myDomain.com/myService");
gZipLoader.addEventListener(Event.COMPLETE, gZIPLoaderCompleteHandler);
}
private function gZIPLoaderCompleteHandler(event:Event):void
{
trace( (event.target as GZIPLoader).data );
}
]]>
</mx:Script>
</mx:WindowedApplication>
2) Configure the HTTPService component
If you use the HTTPService component, as well as setting the header, you need to set the channelSet so it will accept binary data, and then define a custom serializationFilter so we can decode the response before the component fires it’s complete event. Here we need to use Anirudh Sasikumar‘s DirectHTTPBinaryChannel class to create a Binary channel for the HTTPService component.
Firstly we need to write a serialization filter, so we can decode the data. This has 2 static methods, one returns the instance of the filter to be used. The second returns the instance of the binary channel set to be used.
package com.skinkers.air.serializers
{
import com.probertson.utils.GZIPBytesEncoder;
import flash.errors.IllegalOperationError;
import flash.utils.ByteArray;
import mx.messaging.ChannelSet;
import mx.rpc.http.AbstractOperation;
import mx.rpc.http.SerializationFilter;
import com.anirudh.rpc.DirectHTTPBinaryChannel;
/**
* A serialisation filter to be used with HTTPSerivce to decode GZIP http resonse
*/
public class GZIPSerializationFilter extends SerializationFilter
{
private static var serializationFilter:GZIPSerializationFilter;
private static var channelSet:ChannelSet;
/**
* Returns the instance of the GZIPSerializationFilter
*/
public static function getInstance():GZIPSerializationFilter
{
if(!serializationFilter)
serializationFilter = new GZIPSerializationFilter();
return serializationFilter;
}
/**
* Returns the instance of the DirectHTTPBinaryChannel
*/
public static function getChannelSet():ChannelSet
{
if (!channelSet)
{
channelSet = new ChannelSet();
channelSet.addChannel( new DirectHTTPBinaryChannel("direct_http_binary_channel") );
}
return channelSet
}
/**
* Decode the response using GZIPBytesEncoder. If this fails then assume UTF8 data and return UTF8 string.
*/
override public function deserializeResult(operation:AbstractOperation, result:Object):Object
{
if ( result is ByteArray )
{
var barr:ByteArray = result as ByteArray;
try
{
var encoder:GZIPBytesEncoder = new GZIPBytesEncoder();
/* decode the gzip encoded result */
result = encoder.uncompressToByteArray(barr).toString();
}
catch(error:IllegalOperationError)
{
//Not gzip compressed - assume utf8
barr.position = 0;//reset to start of bytearray
result = barr.readUTFBytes(barr.length);
}
}
return result;
}
}
}
Then we configure the HTTPService to use this serialiszer, use the channel set and set the header…
<?xml version="1.0" encoding="utf-8"?>
<mx:WindowedApplication
xmlns:mx="http://www.adobe.com/2006/mxml"
layout="absolute"
creationComplete="creationCompleteHandler(event)"
>
<mx:Script>
<![CDATA[
import com.skinkers.air.serializers.GZIPSerializationFilter;
import mx.events.FlexEvent;
protected function creationCompleteHandler(event:FlexEvent):void
{
gzipEnabledHttpService.channelSet = GZIPSerializationFilter.getChannelSet();
gzipEnabledHttpService.serializationFilter = GZIPSerializationFilter.getInstance();
gzipEnabledHttpService.headers={'Accept-Encoding':'gzip'};
gzipEnabledHttpService.url="http://www.myDomain.com/myService";
gzipEnabledHttpService.send();
}
]]>
</mx:Script>
<mx:HTTPService id="gzipEnabledHttpService" />
</mx:WindowedApplication>
3) Use the GzipHTTPService
Anirudh Sasikumar – has written a great extension to the HTTPService component - GzipHTTPService which can be used a direct replacement for the HTTPService. This also uses the GZIPBytesEncoder to decode, but instead of configuring the HTTPService it extends it and decodes the response internally. See GzipHTTPService for more on this one.
That’s it, 3 pretty simple approaches as the hard work has been done for us!
This is a well-written tutorial. I’m glad you found the GZip library useful for this, too =)
However, I thought I should point out that you don’t actually need to do this any more.
In AIR 2.0, they made it so on Mac and Linux GZipped HTTP traffic is automatically uncompressed. (Unfortunately, that actually causes problems because then you have to check whether the app is running on Mac/Linux because if so it will break the technique you describe here.) Fortunately in AIR 2.0.2, which came out about two weeks ago, they added support for automatically uncompressing GZip HTTP traffic on Windows as well. So now all three platforms are consistent, and you can send GZip responses from your server and don’t have to worry about manually uncompressing them in your app. Basically AIR works just like a browser now for using GZip content.
Paul
Thanks Paul, I’ve updated the post with your info. Classic case that Adobe release cross platform support just after we add it into one of our apps!
I’ll leave the post up for anyone who is interested, but good to know that its now native in the AIR runtime.
Matt.
[...] Gzip for HTTPService/URLLoader: Adding Gzip support for Flex/AIR HTTPService/URLLoader [...]