How to make an efficient image proxy
So the work I’m currently doing is centered around loading data from different domains. I’ve got a proxy setup to get around pesky crossdomain issues but I’ve been trying to make it a bit dynamic for efficiency’s sake. I only want to proxy if I know for sure that the data isn’t coming from our servers or a server with a crossdomain file in place.
Creating a “smart” proxy with the URLLoader for text content is a piece of cake. Simply listen for the SecurityErrorEvent then run the url through the proxy. Beautifully simple.
For the Loader class its a different story. The Loader class’s contentLoaderInfo doesn’t have a security error event, just an ioError event. Your supposed to use the childAllowsParent property of the contentLoaderInfo to figure out whether you have bitmapData level access. The only problem is you can’t access that property until the Event.COMPLETE event is dispatched despite the livedocs LoaderContext page stating this property is available on ProgressEvent.PROGRESS which will throw a Error #2099: The loading object is not sufficiently loaded to provide this information. at flash.display::LoaderInfo/get childAllowsParent()
I can finagle with the LoaderContext and try/catches all I want but there doesn’t seam to be a creative solution to this problem. The result: I have to load every image that doesn’t have a policy file TWICE. Once to check childAllowsParent, the next to proxy the image. This doesn’t make any sense considering on the same livedocs LoaderContext page it states:
When you call the Loader.load() method with LoaderContext.checkPolicyFile set to true, Flash Player does not begin downloading the specified object in URLRequest.url until it has either successfully downloaded a relevant cross-domain policy file or discovered that no such policy file exists.
I can see the “Error: Request for resource at http://… by requestor from http://… is denied due to lack of policy file permissions.” errors being outputted to the console window but I don’t seam to have a way to catch it in code. Is something as significant as this, really a bug? Can anyone give me a hand?
UPDATE: This does appear to be a bug with proper policy files checking with the Loader class. The solution: use UILoader for the check, then use Loader with or without proxy. Thanks Brian from picnik.com for the example below in comments.
I’m working on the same problem. It looks like you can first try to load the resource using URLLoader and wait for an open event or a security error, then cancel the load and proxy as necessary.
Thanks Brian,
Thats the only solution that we could come up with as well. I haven’t updated this entry as I haven’t gotten around to implementing it but it should theoretically work.
This code seems to work:
{
/** Try to load an image directly, if that fails, proxy it through our server
*/
import flash.display.Bitmap;
import flash.display.BitmapData;
import flash.events.Event;
import flash.events.IOErrorEvent;
import flash.events.ProgressEvent;
import flash.events.SecurityErrorEvent;
import flash.net.URLLoader;
import flash.net.URLRequest;
import flash.system.LoaderContext;
import mx.controls.Image;
import mx.core.mx_internal;
use namespace mx_internal;
public class ProxyImage extends Image
{
private var _fDoneChecking:Boolean = false;
public function ProxyImage()
{
super();
loaderContext = new LoaderContext(true);
// TEST: This is test code to see if it worked
addEventListener(Event.COMPLETE, OnComplete);
}
private var _urll:URLLoader = null;
private function SetSource(str:String): void {
super.source = str;
}
private function CreateProxyUrl(strUrl:String): String {
// Do some magic
strUrl = "/get?url=" + encodeURIComponent(strUrl);
return strUrl;
}
override public function set source(value:Object):void {
if (_urll) {
try {
_urll.close();
} catch (e:Error) {
}
_urll = null;
}
// UNDONE: Check for known sites, e.g. Picnik, Flickr, Picasa
if (value is String) {
var fnOnSuccess:Function = function(evt:Event): void {
// Found a cross domain. Just load it directly
SetSource(String(value));
try {
_urll.close();
} catch (e:Error) {
// ignore
}
_urll = null;
}
var fnOnError:Function = function(evt:Event): void {
// Found a cross domain. Just load it directly
SetSource(CreateProxyUrl(String(value)));
try {
_urll.close();
} catch (e:Error) {
// ignore
}
_urll = null;
}
var urll:URLLoader = new URLLoader();
_urll = urll;
urll.addEventListener(Event.COMPLETE, fnOnSuccess);
urll.addEventListener(IOErrorEvent.IO_ERROR, fnOnSuccess);
urll.addEventListener(Event.OPEN, fnOnSuccess);
urll.addEventListener(ProgressEvent.PROGRESS, fnOnSuccess);
urll.addEventListener(SecurityErrorEvent.SECURITY_ERROR, fnOnError);
urll.load(new URLRequest(String(value)));
} else {
// Not a string, no need to proxy
super.source = value;
}
//Security.loadPolicyFile("http://www.supercars.dk/crossdomain.xml");
}
// TEst code to make sure our proxy worked.
private function OnComplete(evt:Event): void {
var bmd:BitmapData = (content as Bitmap).bitmapData;
}
}
}
I have sample code if you’d like to take a look (feel free to add it to your post, too). Send me your email address and I’ll send it to you.
Well, if you just want to access bitmapData, maybe the best approach would be to draw a new bitmap (using BitmapData.draw) from the original. Doesn’t help much with SWFs, unfortunately.
A lot of interesting and useful information.
Thanks a lot!