Truth About HTTPService

The content below about the Flex HTTPService was copied from the verveguy, it was originally posted in 2008 but when I discovered it on 02/08/2010, I wanted to make sure it wasn't lost one day, hence the duplication. All information/credit belongs to the verveguy.

The Truth about Flex HTTPService

So I have unraveled the Truth. (I think) It's more tortured than one would imagine

1/ All HTTP GET requests are stripped of headers. It's not in the Flex stack so it's probably the underlying Flash player runtime

2/ All HTTP GET requests that have content type other than "application/x-www-form-urlencoded" are turned into POST requests

3/ All HTTP POST requests that have no actual posted data are turned into GET requests. See 1/ and 2/

4/ All HTTP PUT and HTTP DELETE requests are turned into POST requests. This appears to be a browser limitation that the Flash player is stuck with.

What this boils down to in practical terms is that if you want to pass headers in all requests, you should always use POST and you should find another way to communicate the semantics of the operation you "really wanted". The Rails community have settled on passing ?_method=PUT/DELETE as a work around for the browser problems underlying 4/

Since Flash adds the wonderful header stripping pain on GET, I'm also using ?_method=GET as a workaround for that. However, since this trips up on 3/, I am passing a dummy object as the encoded POST data. Which means my service needs to ignore dummy posted data on a ?_method=GET request.

Crucial at this point to know about 2/. That wasted a bunch of my time.

I've built all of this handling into a new RESTService class with MXML markup support so it's possible to pretend this doesn't exist on the client side.

Dump SWF Info to XML

Assumes you are in the FLEX_HOME/lib directory

java -cp asc.jar;swfkit.jar;swfutils.jar flash.swf.tools.SwfxPrinter <FILE_NAME>.swf > swfdump.xml

Display Flex SDK Version

This will display the version of the Flex SDK that was used to compile the SWF

<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" layout="vertical">

        <mx:Script><![CDATA[
        import mx.controls.Alert;
        import mx.utils.ObjectUtil;
        import mx.utils.StringUtil;

        ///////////////////////////////////////////////////////////////////////////
        // Returns the version info
        ///////////////////////////////////////////////////////////////////////////
        public function readVersionInfo():String {
            var version_info:String = '';

            var serialNumber:ByteArray = readSerialNumber();
            if (serialNumber == null) {
                // SWF_SERIALNUMBER structure only exists in Flex SWFs, not in Flash
                return version_info;
            }

            serialNumber.endian = Endian.LITTLE_ENDIAN;
            var id, edition, major, minor, buildL, buildH;
            id = serialNumber.readUnsignedInt();
            edition = serialNumber.readUnsignedInt();
            major = serialNumber.readUnsignedByte();
            minor = serialNumber.readUnsignedByte();
            buildL = serialNumber.readUnsignedInt();
            buildH = serialNumber.readUnsignedInt();

            return StringUtil.substitute("{0}.{1}.{2}.{3}", major, minor, buildH, buildL);
        }

                ///////////////////////////////////////////////////////////////////////////
                // Returns compilation date of current module
        ///////////////////////////////////////////////////////////////////////////
                public function readCompilationDate(serialNumber:ByteArray = null):Date {
                        const compilationDate:Date = new Date;
                        const DATETIME_OFFSET:uint = 18;

                        if (serialNumber == null)
                                serialNumber = readSerialNumber();

                        /* example of filled SWF_SERIALNUMBER structure
                           struct SWF_SERIALNUMBER
                           {
                           UI32 Id;         // "3"
                           UI32 Edition;    // "6"
                           // "flex_sdk_4.0.0.3342"
                           UI8 Major;       // "4."
                           UI8 Minor;       // "0."
                           UI32 BuildL;     // "0."
                           UI32 BuildH;     // "3342"
                           UI32 TimestampL;
                           UI32 TimestampH;
                           };
                        */

                        // the SWF_SERIALNUMBER structure exists in FLEX swfs only, not FLASH
                        if (serialNumber == null)
                                return null;

            var id, edition, major, minor, buildL, buildH, foo;
            serialNumber.endian = Endian.LITTLE_ENDIAN;
            id = serialNumber.readUnsignedInt();
            edition = serialNumber.readUnsignedInt();
            major = serialNumber.readUnsignedByte();
            minor = serialNumber.readUnsignedByte();
            buildL = serialNumber.readUnsignedInt();
            buildH = serialNumber.readUnsignedInt();

                        // date stored as uint64
                        serialNumber.position = DATETIME_OFFSET;
                        serialNumber.endian = Endian.LITTLE_ENDIAN;
                        compilationDate.time = serialNumber.readUnsignedInt() + serialNumber.readUnsignedInt()* (uint.MAX_VALUE + 1);

                        return compilationDate;
                }

                ///////////////////////////////////////////////////////////////////////////
                // Returns contents of Adobe SerialNumber SWF tag
                public function readSerialNumber():ByteArray {
                        const TAG_SERIAL_NUMBER:uint = 0x29;
                        return findAndReadTagBody(TAG_SERIAL_NUMBER);
                }

                ///////////////////////////////////////////////////////////////////////////
                // Returns the tag body if it is possible
                public function findAndReadTagBody(theTagCode:uint):ByteArray {
                        // getting direst access to unpacked SWF file
                        // reported to cause security sandbox errors ->
                        //const src:ByteArray = LoaderInfo.getLoaderInfoByDefinition(SWF).bytes;
                        // erg this one throughs an error too TypeError: Error #1009: Cannot access a property or method of a null object reference.
                        //const src:ByteArray = Application(Application.application).loaderInfo.bytes;
                        var src:ByteArray = new ByteArray();
                        var loaderInfo:LoaderInfo = Application(Application.application).loaderInfo;

                        // the swf has not loaded yet - wait until application complete
                        if (loaderInfo.bytesLoaded!=loaderInfo.bytesTotal) {
                                return null;
                        }
                        const test:* = Application(Application.application).loaderInfo.bytes;
                        src = Application(Application.application).loaderInfo.bytes;

                        /*
                           SWF File Header
                           Field      Type  Offset   Comment
                           -----      ----  ------   -------
                           Signature  UI8   0        Signature byte: “F” indicates uncompressed, “C” indicates compressed (SWF 6 and later only)
                           Signature  UI8   1        Signature byte always “W”
                           Signature  UI8   2        Signature byte always “S”
                           Version    UI8   3        Single byte file version (for example, 0x06 for SWF 6)
                           FileLength UI32  4        Length of entire file in bytes
                           FrameSize  RECT  8        Frame size in twips
                           FrameRate  UI16  8+RECT   Frame delay in 8.8 fixed number of frames per second
                           FrameCount UI16  10+RECT  Total number of frames in file
                        */

                        // skip AVM2 SWF header
                        // skip Signature, Version & FileLength
                        src.position = 8;
                        // skip FrameSize
                        const RECT_UB_LENGTH:uint = 5;
                        const RECT_SB_LENGTH:uint = src.readUnsignedByte() >> (8 - RECT_UB_LENGTH);
                        const RECT_LENGTH:uint = Math.ceil((RECT_UB_LENGTH + RECT_SB_LENGTH* 4) / 8);
                        src.position += (RECT_LENGTH - 1);
                        // skip FrameRate & FrameCount
                        src.position += 4;

                        while(src.bytesAvailable > 0) {
                                with(readTag(src, theTagCode)) {
                                        if (tagCode == theTagCode)
                                                return tagBody;
                                }
                        }

                        return null;
                }

        private var tagCode:uint;
        private var tagBody:ByteArray;
                ///////////////////////////////////////////////////////////////////////////
                // Returns tag from current read position
                private function readTag(src:ByteArray, theTagCode:uint):Object {
                        src.endian = Endian.LITTLE_ENDIAN;

                        const tagCodeAndLength:uint = src.readUnsignedShort();
//                      const tagCode:uint = tagCodeAndLength >> 6;
            tagCode = tagCodeAndLength >> 6;
                        const tagLength:uint = function():uint {
                                        const MAX_SHORT_TAG_LENGTH:uint = 0x3F;
                                        const shortLength:uint = tagCodeAndLength & MAX_SHORT_TAG_LENGTH;
                                        return (shortLength == MAX_SHORT_TAG_LENGTH) ? src.readUnsignedInt() : shortLength;
                                }() as uint;

                        //const tagBody:ByteArray = new ByteArray;
            tagBody = new ByteArray();
                        if (tagLength > 0)
                                src.readBytes(tagBody, 0, tagLength);

                        return {tagCode:tagCode, tagBody:tagBody};
                }
]]></mx:Script>

    <mx:Button label="Go" click="{lbl.text += readVersionInfo();}" />
    <mx:Label id="lbl" text="Version: " />
</mx:Application>

Global Error Handling

This snippet of code provides the ability to globally handle errors in your Flex application. This code was taken from a blog post (on 01/05/2010), linked to below.

private function onPreinitialize():void {
  // setup global error handling
  UIComponentGlobals.catchCallLaterExceptions = true;
  systemManager.addEventListener("callLaterError", handleErrors);
}

public function handleErrors(event:Event):void {
  if (event is DynamicEvent && event.hasOwnProperty("error")) {
    // you could also send this back to your server
    var error:Error = DynamicEvent(event).error as Error;
    trace("Error!\n" + error.getStackTrace());
    Alert.show("There has been an error in the application", "", Alert.OK);

    if (ExternalInterface.available) {
      ExternalInterface.call("handleApplicationError");
    }
  }
}

From the original blog post: I add an event handler in my Main application, doing the following:

  • Print the stack trace to the console for debugging, since it will no longer print by default
  • Pop an alert up to the user. Depending on how the error is thrown, this may or may not make it to the screen, but it will ensure the screen glosses over to prevent any further interaction.
  • If JavaScript? is available, call a JavaScript? function. You can do anything you want here, but my preference is to redirect the user to an error page that tells them something went wrong, and links them to the correct page to restart where they left off.

Pass ... rest Argument To Another Function

The ... (rest) parameter allows a function to take a variable number of arguments. However, if you have a function that takes a rest paramter and needs to pass it on you have to use the apply method.

private var logger:ILogger;

public function log_debug(msg, ... rest):void {
  if (Log.isDebug()) {
    logger.debug.apply(this, [msg].concat(rest));
  }
}

ObjectUtil.copy() Serialization

Darron Schall has a great post explaining the use of ObjectUtil.copy(...) for creating a deep copy of an object. I've copied his blog post and saved it as a PDF (for future reference).