views:

449

answers:

1

I am trying to simulate a 'HEAD' method using UrlLoader; essentially, I just want to check for the presence of a file without downloading the entire thing. I figured I would just use HttpStatusEvent, but the following code throws an exception (one that I can't wrap in a try/catch block) when you run in debug mode.

<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute" initialize="init()">
<mx:Script>
    <![CDATA[

       private static const BIG_FILE:String = "http://www.archive.org/download/gspmovvideotestIMG0021mov/IMG_0021.mov";

       private var _loader:URLLoader;

       private function init():void {
            _loader = new URLLoader();
            _loader.addEventListener(HTTPStatusEvent.HTTP_STATUS, statusHandler);
            _loader.addEventListener(IOErrorEvent.IO_ERROR, errorHandler);
            _loader.addEventListener(SecurityErrorEvent.SECURITY_ERROR, errorHandler);
            _loader.load(new URLRequest(BIG_FILE));   
       }

       public function unload():void { 
            try {
                _loader.close();
                _loader.removeEventListener(HTTPStatusEvent.HTTP_STATUS, statusHandler);
                _loader.removeEventListener(IOErrorEvent.IO_ERROR, errorHandler);
                _loader.removeEventListener(SecurityErrorEvent.SECURITY_ERROR, errorHandler);
            }
            catch(error:Error) {
                status.text = error.message;
            }
        }

        private function errorHandler(event:Event):void {
            status.text = "error";
            unload();
        }

        private function statusHandler(event:HTTPStatusEvent):void {
            if(event.status.toString().match(/^2/)) {
                status.text = "success";
                unload();
            }
            else {
                errorHandler(event);
            }
        }   
    ]]>
</mx:Script>

<mx:Label id="status" />

I tried using ProgressEvents instead, but it seems that some 404 pages return content, so the status event will correctly identify if the page exists.

Anyone have any ideas?

+1  A: 

It's a bug in the URLLoader class, I think.

If you read the error message (at least the one I got, you haven't pasted yorrs!) you will see it:

Error: Error #2029: This URLStream object does not have a stream opened at flash.net::URLStream/readBytes() at flash.net::URLLoader/onComplete()

This gives you some insight on what's happening. The URLLoader class internally uses a URLStream object, which provides low-level access to the downloaded data.

The error message indicates that an onComplete handler is being called on URLLoader. The file is big but it's probably cached so it loads rather fast. Now, if you add a listener for both progress and complete events, you'll see the order in which events are fired is this:

  • progress
  • status
  • complete

The docs confirm this:

Note that the httpStatus event (if any) is sent before (and in addition to) any complete or error event.

Now, you can see the problem is that from the status handler you're calling close(). This closes the stream. But apparently (and this is the bug, I think), the onComplete handler in the URLLoader class does not check whether the stream is open or not. (From Actionscript there's no way to check this, so you'd have to wrap code in try/catch). You cannot read data from a closed stream, so this is why it blows.

I can see 2 ways to fix this:

1) Defer execution of the function that calls close() (your unload method), so close() is called after the URLLoader internal onComplete method is called.

That is, do this:

setTimeout(unload,1);

instead of this:

unload();

2) Use a URLStream instead of a URLLoader. The first option strikes me as a bit of a hackish workaround, so I'd go with this last one in your situation. Using a URLStream means more work on your side, generally, but in this case you're not actually interested in reading any data, so it doesn't make much difference. Plus, you'd only have to change two lines in your current code:

This one:

private var _loader:URLStream;

And this one:

_loader = new URLStream(); 

And you're all set.

Juan Pablo Califano
Thanks for the detailed explanation Juan Pablo.I had suspected the same thing (that it is an Adobe bug), as I've encountered other weird behaviors like this with Adobe's libraries in the past.UrlStream did the trick for me (I had never heard of this classes before)...thanks!I don't quite understand how the setTimeout solution would work without having to download the entire file (this is an issue when the file is not cached). Waiting 1 MS won't necessarily fix this if the file is still being retrieved. Unless the internal onComplete function is called right after the status function.
gmoniey
No worries. The setTimeout works because the problem is that both the status and the complete event are fired within the same call stack. If you set a timeout, no matter how long, the function will be called asynchronously. In flash terms, it will wait (at least) one frame. By the time your function is called the internal onComplete handler would have executed. I think this problem only shows if you get the complete and status event at the same time (in the same call stack); if you get the status before download completes, I think you'll not have this problem (but I haven't tried it)
Juan Pablo Califano
To clarify my previous comment, if you get a status event while the file has not downloaded completely, calling close should not be a problem. The complete event should never be fired. Now, this is what logic tells me. But I've experienced some strange bugs with LocalConnection objects that fired Error events after being closed (and after, consecuently, their respective event handlers where removed, which caused an uncaught ErrorEvent), so I wouldn't bet money on that.
Juan Pablo Califano
Hi Juan,Seems like were not out of the woods yet. If you change the BIG_FILE to point to a file which doesn't exists, and use URLStream, you get a different exception which you cannot catch:Error #2044: Unhandled IOErrorEvent:. text=Error #2032: Stream Error. at LoaderTest/init()[/Users/arash/Documents/dev/fonzi/flash/src/LoaderTest.mxml:11]
gmoniey
@gmoniey. Mmm, that's weird. That's the kind of ErrorEvent that should be caught by listening to IOErrorEvent.IO_ERROR. I see you have the handler in place in the code pasted. Do you still have it in your current code? Just tried the above code, replacing URLLoader by URLStream and changed the path to a non existent file. I see the error being caught by the handler.
Juan Pablo Califano
Well, the error is being caught, but flash is still throwing up an exception dialog. Try using a different browser, and open up the SWF without the debugger. You will see that the an exception dialog is popped up, and after it is dismissed, the SWF will say 'error'
gmoniey
@gmoniey. If the dialog box pops up, I thik it' safe to assume that the error was not handled. Plus, the error message says `Unhandled IOErrorEvent`. Now, re reading the quote from the docs I pasted in my answer, I think this is basically the same problem you had with onComplete. The status event fires; you close the loader and remove the error handler. An IOError event is dispatched right after that (both events are dispatched in a batch, so they run in the same call stack). Now, since the handler was removed, the error is uncaugh. I think the setTimeout() workaround could help out here
Juan Pablo Califano