Using AMF with flash.net.URLLoader

flash.net.NetConnection is a native Flash Player class that is the workhorse for AMF based communication. Flash and Flex applications make use of NetConnection to send AMF formatted requests over HTTP or HTTPS to servers such as Flash Remoting, ColdFusion, BlazeDS, etc. However, less HTTP features are exposed by NetConnection than those of flash.net.URLLoader.

This can be a pain when your network architecture has one or more firewalls between external Flash Player clients and your application server. If authentication or session management is performed at the firewall, session timeouts can introduce an annoying problem. If a client makes an AMF request after a session has timed out, the session management layer may challenge the client to re-authenticate. This challenge is unlikely to be an AMF formatted response and so NetConnection is unable to process the information. While the client knows the request failed, the exact cause of failure is not known.

So… I hacked together a prototype replacement for NetConnection based on flash.net.URLLoader. This new “AMFConnection” class creates binary flash.net.URLRequests and sends HTTP POST data as an AMF formatted request and processes the AMF formatted response returned to URLLoader. One could subclass AMFConnection and intercept the response data and try to detect that an HTML re-authentication page has been returned to the client.

Other benefits this approach include being able to set custom HTTP request headers (and even receive HTTP response headers for AMF when used in an AIR application), receive progress events for large AMF response data as it is streaming back to the client, being able to fully customize the batching behavior of multiple AMF requests made in quick succession, or even customize the AMF serialization or deserialization behavior as this is no longer exclusively native code.

I tried to follow the existing NetConnection behavior for AMF over HTTP where possible… even if it meant slightly slower code. One complication has to do with AMF packets in that they reset the by-reference serialization tables for each AMF header or AMF message body (as multiple headers and bodies may be batched in a single request, i.e. AMF packet). I had to use flash.net.ByteArray buffers for each header value or message body (in either a request or response) to ensure the reference indexes were reset to 0.

There are a few minor issues I still need to work on, such as only switching to AMF 3 on encountering a complex object (right now my class starts out in AMF 0 as normal, but it always switches to AMF 3 if the objectEncoding has been set to version 3 regardless of the data type), and exposing an API to control batching (right now each request is sent individually).

One annoying thing I ran into is flash.net.Responder’s constructor takes two Functions for the result and status handlers – but these are not exposed after construction in ActionScript. So instead I had to introduce a custom AMFResponder that must be passed to AMFConnection.call().

Connections in BlazeDS are abstracted by Channels. To test my replacement of NetConnection I created a copy of AMFChannel and replaced the usages of NetConnection with my new AMFConnection. I ran some initial tests and Flex RemoteObject worked smoothly.

I’ve uploaded an unsupported prototype amf.swc as a binary distribution for now. To test it out with RemoteObject you add it to your library path and create an instance of the temporary mx.messaging.channels.AMFChannel2 class and use it in a custom ChannelSet.

import mx.messaging.channels.AMFChannel2;import mx.messaging.channels.Channel;import mx.messaging.channels.ChannelSet;...var channelSet:ChannelSet = new ChannelSet();var channel:Channel = new AMFChannel2(null, "http://localhost:8400/team/messagebroker/amf");channelSet.addChannel(channel);ro.channelSet = channelSet;......

Alternatively, you may want to try out the raw flex.net.AMFConnection class – either directly or using a subclass and custom event listeners for the usual URLLoader events (which are simply redispatched by AMFConnection).

package flex.net{import flash.events.*;import flash.net.URLLoader;import flash.utils.ByteArray;public class AMFConnection extends EventDispatcher{public function AMFConnection(url:String=null);public function get client():Object;public function set client(value:Object):void;public function get connected():Boolean;public static function get defaultObjectEncoding():uint;public static function set defaultObjectEncoding(value:uint):void;public function get objectEncoding():uint;public function set objectEncoding(value:uint):void;public function get url():String;[Deprecated("Please use the 'url' property instead.")]public function get uri():String;protected var responseCounter:uint;protected var urlLoader:URLLoader;protected var pendingRequests:Object = {};public function addHeader(name:String, mustUnderstand:Boolean=false, data:*=undefined):void;public function call(command:String, responder:IAMFResponder, ...arguments:Array):void;public function close():void;public function connect(url:String):void;public function removeHeader(name:String):Boolean;protected function completeHandler(event:Event):void;protected function errorHandler(event:ErrorEvent):void;protected function httpStatusHandler(event:HTTPStatusEvent):void;protected function ioErrorHandler(event:IOErrorEvent):void;protected function progressHandler(event:ProgressEvent):void;protected function securityErrorHandler(event:SecurityErrorEvent):void;protected function getResponseURI():String;protected function response(bytes:ByteArray):void;protected function send(data:ByteArray):void;}}