Welcome to Flexoland. First of all I am not evangelist or big "fan" of ActionScript/Flex, althought it implements interesting ideas. My fields of interest are C++/C# Win32/WinFX system/IE/application development, sometime with interruptions for Java and misc scripting. I'd be interested in robot-building, AI systems, high performance servers but this is out of scope of real work now. Latest RIA projects I was involved in were based on Ajax/Flex platforms. This page is place for strange and interesting things found in this environment (Flex 1.5/2/3, ActionScript 2/3).
P.S.: NO WARRANTIES ARE EXTENDED. USE ALL DESCRIBED IDEAS AT YOUR OWN RISK. Some ideas might be obsolete, as Adobe periodically updates Flex. Also please forgive me my bad English and be happy.
ActionScript 2.0 doesn't provide API like flash.utils.getClassByName. There is also no built-in "Class" type, and only [ClassReference] attribute used to mark property/field as class reference member. Good news is that getClassByName can be simulated using "eval" function:
Intersting enough, that AS2 stores all class references as fields of _global object, replacing '.' width '_' (I'd also be happy to know if AS3 somehow provides some special storage for all loaded classes, and the way to access it, in my understanding it's going to be somewhere inside ApplicationDomain):
Flex 2.0 provides API to parse date string like Date.parse, but I was unable to find something similar in AS2. So I've used mx.formatters.DateFormatter.parseDateString method.
Note: the method imlementation is very buggy, doesn't provide localization support and also is not public in Flex 2 anymore.
Under some circumstances Flex wraps object with special mx.utils.ObjectProxy wrapper class. Documentation describes special object_proxy "object" property containing reference to real object. But accessing this property at runtime always returns "null", althought this property is visible in debuger watch window with "package" icon. Code below demonstrates the problem, and shows "Real object: null" message:
Matt Chotin pointed me to the solution. It's special namespaced property and need in special code handling. So updated example listed below:
Sometimes you need to dynamically invoke certain object method in Flex environment. With Flex 1.5 we used the following line to call remote object method at runtime:
With Flex 2.0 (ActionScript 3) this solution doesn't work anymore. Our sample will throw exception. It's caused be the fact mx.rpc.remoting.mxml.RemoteObject is derrived from flash.utils.Proxy class and uses it to handle dynamic method invocation tasks, like old Object._resolve/addProperty APIs in ActionScript 2.0. (strictly speaking it's implemented inside mx.rpc.AbstractService class as callProperty/ getProperty/ nextName/ nextNameIndex/ nextValue/ setProperty members). Code fails because ro["foo1"] statement will invoke AbstractService:getProperty() API, althought real ro.foo1(...) statement under the hood invokes callProperty method. And AbstractService:callProperty implementation differs from AbstractService:getProperty one, by calling Operation:send. Actually send() method call need to be moved to the Operation implementation, and possibly ??? it's bug in RPC (for someone interested ("inter esse") in please see disassembled ActionScript byte code (ABC) for these methods). Fortunately we can force callProperty call for proxied objects, as shown below:
Now we can w/o problem implement 1-line "call" method like:
When you only need in dynamic RemoteObject object method call, and don't want to use applyMethod API, the following code will work:
There is no API to walk stack in AS3. Sometime this information is useful when spelunking in Flex code. Yes, it's possible to see call stack under debugger. But it could be a problem for complex applications, or possibly this code is hard to catch in debugger at all (like drag and drop, etc.). Below is a simple approach to trace current stack, but it's only limited to debug Flash player version (flash.system.Capabilities.isDebugger is true):
See also Logger @ XPanel tool.
AS3 Function object provides "call" and "apply" APIs for special function treatment. Both methods accept "thisObject" argument. Documentation says something like: thisObject:Object � The object to which myFunction is applied. It doesn't describe well that thisObject parameter now ignored almost in all cases, in contrast to AS2. By default when you access object method or global function in AS3 code you always get "thunked" object (something like delegate in .NET world or thunks in C++, in other words real function pointer+"this" context pointer). [I'd also glad to know how to extract real function pointer from this pointer? Is it somewhere inside Object.prototype?] e.g. consider the following example:
The same situation with global function, thisObject is always ignored!!! At this moment I know only one possible case when it works - anonymous function, or function "as is":
Just FYI.
AS2 constructor was plain function object, and it was possible to call it using the same technique as other methods. e.g.:
Now it doesn't work anymore in Flex2. AS3 defines new Class type, but forgets to add something like Class.create(args:Array) API, which is equivalent of Function.apply method. It's pity. "Object.prototype.constructor" points to constructor function, but sounds like it accepts parameter with raw uninitialized class (just my assumption). But in case when you still need in this functionality, the trick listed below could be used. Thanks to Function.apply! To demonstrate this technique arguments count are limited to 2, but you are free to extend it to as many as you want (actually it's limited by maximum arguments count to function supported by mxmlc compiler or maximum generated block size or SWF size or something else).
This problem also exists with "super" constructor call. It doesn't provide apply functionality, and you will get the same problem invoking superclass constructor, e.g. when superclass constructor accepts ... rest parameter. Probably the similar approach will work, in combination with #include. E.g. create code snip with if/else or case/switch statement, checking arguments count and invoking appropriate super() constructor, then just include the code snip using #include directive!
ActionScript 3 runtime provides interesting undocumented callback to intercept E4X XML events: XML.setNotification(callback:Function). Internally it's used by XMLListCollection class to hook up change events. Also it's unclear for me why this function is undocumented in "XML era".
Callback notification function has the following format:
function callback(targetCurrent:Object, command:String, target:Object, value:Object, detail:Object):void;
Possible "command" parameter values based on my exepriments and Tamarin source code are:
BTW XMLListCollection Adobe Flex 2.0.1 implementation doesn't intercept all possible events, so it's potentially buggy. More detailed information about the callback parameters can be obtained from my sample listed below and traces.
Simple program to test:
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" creationComplete="doTest()"> <mx:Script><![CDATA[ public function doTest():void { var x:XML = <foo/>; x.setNotification(xml_notifier); x.@a1 = "halo"; x.@a1 = "yelo"; delete x.@a1; x.appendChild(<child1/>); x.child1 = "some text"; x.child1[0].setName("child2"); x.child2 = <child3/>; delete x.child3[0]; x.setNamespace(new Namespace("dummy")); } private var m_seq:int = 1; private function xml_notifier(targetCurrent:Object, command:String, target:Object, value:Object, detail:Object):void { trace("["+m_seq+"] command="+command+", targetCurrent="+targetCurrent+", target="+target+", value="+value+", detail="+detail); m_seq++; } ]]></mx:Script> </mx:Application>
Console output:
[1] command=attributeAdded, targetCurrent=, target=, value=a1, detail=halo [2] command=attributeChanged, targetCurrent=, target=, value=a1, detail=halo [3] command=attributeRemoved, targetCurrent=, target=, value=a1, detail=yelo [4] command=nodeAdded, targetCurrent=<foo> <child1/> </foo>, target=<foo> <child1/> </foo>, value=, detail=null [5] command=textSet, targetCurrent=<foo> <child1>some text</child1> </foo>, target=some text, value=some text, detail=null [6] command=nameSet, targetCurrent=<foo> <child2>some text</child2> </foo>, target=some text, value=child2, detail=child1 [7] command=nodeChanged, targetCurrent=<foo> <child3/> </foo>, target=<foo> <child3/> </foo>, value=, detail=some text [8] command=nodeRemoved, targetCurrent=, target=, value=, detail=null [9] command=namespaceSet, targetCurrent=, target=, value=dummy, detail=null
Note: this code
works only for Flex 2.0 prior Flex 2.0.1 Hotfix 2/LCDS 2.5.1 with updated
RPC library.
Some interesting info for people
implementing custom web services ala Flex RPC style. Probably this tip will be
unnecessary after Adobe officially publish RPC source code, but meanwhile... RemoteObject
and WebService classes are derived from mx.rpc.AbstractService one. Actually
AbstractService class provides nice helpers to work with webservice methods.
There is "AbstractService.getOperation" API to retrieve certain operation, but
it's marked as final and is not allowed to be overridden. OK, it's possible to
override Proxy methods like callProperty/getProperty, etc. But.. wait, there is
AbstractService.mx_internal:operationClass undocumented property, it's class
reference for Operation class. In other words just provide own class there and
AbstractService will make all magic for you:
Service class:
package { import mx.rpc.AbstractService; import mx.rpc.AbstractOperation; import mx.core.mx_internal; use namespace mx_internal; public dynamic class MyWebService extends AbstractService { public function MyWebService():void { operationClass = MyOperation; } } }
Operation class:
package { import mx.rpc.AbstractService; import mx.rpc.AbstractOperation; public dynamic class MyOperation extends AbstractOperation { public function MyOperation(svc:AbstractService, name:String = null):void { super(svc, name); } override public function send(... args):AsyncToken { // Place to provide specific to MyWebService logic trace("call "+name+"(args="+args+")"); } } }
Service usage:
var ws:MyWebService = new MyWebService(); ws.someMethod(1, 2, 3); // <- will dump something like "call someMethod(args=[1,2,3])"
Note: this code
only works with Flex 2.0.1 Hotfix 2/LCDS 2.5.1 with updated
RPC library. Adobe developers removed "final" keyword from
"getOperation" method, and removed "operatonClass" member as well.
Thank you as it's first step to make the code more transparent and convenient for
inheritance/subclassing. I'd be happy if AbstractOperation.flash_proxy:getProperty
will be revised as well. As it makes a lot of evil in subclassing tasks.
As you might know AS3 proxy functionality (flash.utils.Proxy) is not designed well
by design and together with strange AbstractOperation.flash_proxy:getProperty implementation
introduces some weird problems. Frankly speaking "getProperty" is very optimistic
and supposes that requested property is always RPC operation, but under some circumstances
it can be any of RemoteObject members. Also for all functions getProperty should return
Function object instance, and this should be rule for all operation as well. And
AbstractService should provide clever getProperty implementation allowing if necessary
execute AbstractService.super.getProperty API. Make getOperation name parameter as QName, etc
Returning to operation overriding/subclassing: now it's very simple, just
override "getOperation" and return necessary Operation class, e.g.:
package { import mx.rpc.AbstractService; import mx.rpc.AbstractOperation; public dynamic class MyWebService extends AbstractService { override public function getOperation(name:String):AbstractOperation { var o:AbstractOperation = super.getOperation(name); if( o==null ) { o = new Operation(this, name); _operations[name] = o; o.asyncRequest = asyncRequest; } return o; } } }
Operation class:
package { import mx.rpc.AbstractService; import mx.rpc.AbstractOperation; public dynamic class MyOperation extends AbstractOperation { public function MyOperation(svc:AbstractService, name:String = null):void { super(svc, name); } override public function send(... args):AsyncToken { // Place to provide specific to MyWebService logic trace("call "+name+"(args="+args+")"); } } }
Service usage:
var ws:MyWebService = new MyWebService(); ws.someMethod(1, 2, 3); // <- will dump something like "call someMethod(args=[1,2,3])"
Just my implementation of dynamic events. Sometimes it's more convenient to use single event class with "dynamic" properties instead of creating custom Event based classes for each type of event. I'd be happy if Adobe includes some dynamic event implementation into framework to prevent each developer create own implementation (Note: in Flex 3, mx.events.DynamicEvent provides correct implementation for clone method). Source code listed below works well with events sent between different modules/application domains. Keypoint is "clone" method, as sometime event dispatcher send cloned version instead of original one (event redispatching). Note: current "copy" method implementation is not well designed for performance, but it works:
package common { import flash.events.Event; public dynamic class EventX extends Event { public function EventX(type:String, bubbles:Boolean = false, cancelable:Boolean = false):void { super(type, bubbles, cancelable); } public override function clone():Event { return Event(copy(type, bubbles, cancelable)); } public function copy(type:String, bubbles:Boolean = false, cancelable:Boolean = false):EventX { var evt:EventX = new EventX(type, bubbles, cancelable); // skip standard props var std:Object = {}; for(var p1:String in evt) std[p1] = true; for(var p2:String in this) { if( std[p2]!=null ) continue; evt[p2] = this[p2]; } return evt; } } }
package { [RemoteClass(alias="FooVO")] public class FooVO { // public field public var prop1:String; // hidden field [Transient] public var prop2:Array; } }Areas of applying - all places using AMF encoding, e.g. RemoteObject, NetConnection, ObjectUtil.copy, etc.
My free time (25th hour in day), created simple toy, useful in underwater digging. Program prototype in studio!:
Nemo 440 - advanced ActionScript 3/ABC2/Flex 2/Flex 3/AIR disassembler.
Flex/AS/Flash were always missing global hook for unhandled errors like SetUnhandledExceptionFilter in Win32, Application.ThreadException in .NET etc. But Flex 3 framework introduced new functionality to intercept some subset of these errors. Usage sample listed below, it's hardcoded to always invoke debugger in case of unhandled error:
<?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" initialize="constructor()" > <mx:Button label="Error" click="callLater(doError)"/> <mx:Button label="Still not supported" click="doError()"/> <mx:Script><![CDATA[ import mx.core.UIComponentGlobals; import mx.controls.Alert; private function constructor():void { UIComponentGlobals.catchCallLaterExceptions = true; Application.application.systemManager.addEventListener("callLaterError", this_callLaterError); } private function doError():void { throw new Error('Dummy error'); } private function this_callLaterError(evt:*):void { Alert.show("Unhandled error: "+evt.error); var bypassToDebugger:Boolean = true; if( bypassToDebugger ) throw evt.error; } ]]></mx:Script> </mx:Application>
Recently I needed to host Flex 2.0 SWF application movie inside Flex 3.0 one.
First attempt to use SWFLoader introduced many runtime errors.
So I created simple class for this purposes. Main idea was
to specify separate application domain for Flex 2 application to prevent
runtime/framework classes interference with Flex 3 implementation. May be this
problem is fixed or described already, but unfortunately I didn't find solution
at the moment of writing.
package { import flash.system.ApplicationDomain; import flash.system.LoaderContext; import mx.controls.SWFLoader; public class Flex2Loader extends SWFLoader { public function Flex2Loader() { loaderContext = new LoaderContext(false, new ApplicationDomain(null)); scaleContent = false; addEventListener("resize", updateSize); addEventListener("complete", this_complete); } private function updateSize(... rest):void { if( content!=null ) Object(content).setActualSize(width, height); } private function this_complete(evt:*):void { if( content!=null ) content.addEventListener("applicationComplete", updateSize); } } }
Usage sample:
<?xml version="1.0" encoding="utf-8"?> <mx:Application xmlns:local="*" xmlns:mx="http://www.adobe.com/2006/mxml" > <local:Flex2Loader width="100%" height="100%" autoLoad="true" source="foo.swf" /> </mx:Application>
Note: To prevent any cross-domain security problems, ensure that hosted application (in our example - foo.swf) listed container domain as trusted one. In other words executes "Security.allowDomain('*')" on early initialization stage ('*' - is bad example from security perspective, and essential for development).
Sometimes it's necessary to know Flash player container type used to execute current Flex code. This simple question was asked on Yahoo groups - How do you detect AIR vs Flash Player at runtime? Quick answer is to check "flash.system.Security.sandboxType" or "flash.system.Capabilities.playerType" properties depending on situation. More information is available in AS3 documentation.
All we know "is" AS3 operator. Recently found in Adobe source code one interesting way to check object type equality. I don't know if this is legal way, but at least it works with Flex 2/3:
public class A {} ... public class B extends A {} ... private function typeEquals(o:*, cls:Class):Boolean { return o==null?false:Object(o).constructor==cls; } private function test():void { var a:A = new A(); var b:B = new B(); var c:C = new C(); trace("typeEquals(a, A) "+typeEquals(a, A)); trace("typeEquals(a, B) "+typeEquals(a, B)); trace("typeEquals(b, A) "+typeEquals(b, A)); trace("typeEquals(b, B) "+typeEquals(b, B)); trace("a is A "+(a is A)); trace("a is B "+(a is B)); trace("b is A "+(b is A)); trace("b is B "+(b is A)); } /* Output: typeEquals(a, A) true typeEquals(a, B) false typeEquals(b, A) false typeEquals(b, B) true a is A true a is B false b is A true b is B true */
Just quick steps how to add client-side logging functionality for Flex Data Services in BlazeDS/LCDS. Complete documentation about measuring performance in BlazeDS/LCDS services described in Adobe Measuring Message Processing Performance document.
1) Enable MPI functionality in BlazeDS/LCDS. Edit channel definitions you want to trace in /WEB-INF/flex/services-config.xml file and add "record-message-times"/"record-message-sizes" options :
... <channel-definition id="my-amf" class="mx.messaging.channels.AMFChannel"> <endpoint url="http://{server.name}:{server.port}/{context.root}/messagebroker/amf" class="flex.messaging.endpoints.AMFEndpoint"/> <properties> <record-message-times>true</record-message-times> <record-message-sizes>true</record-message-sizes> </properties> </channel-definition> ...
2) Configure logger, e.g:
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml" preinitialize="constructor()"> <mx:Script><![CDATA[ import mx.logging.Log; import mx.logging.LogEventLevel; import mx.logging.targets.TraceTarget; private function constructor():void { var t:TraceTarget = new TraceTarget(); t.level = LogEventLevel.DEBUG; Log.addTarget(t); } ]]></mx:Script> </mx:Application>
3) See all traces in Eclipse console window, (not only MPI ones that listed below):
... Original message size(B): 654 Response message size(B): 566 Total time (s): 0.969 Network Roundtrip time (s): 0.953 Server processing time (s): 0.016 Server non-adapter time (s): 0.016 ...
4) Use "mx.messaging.messages.MessagePerformanceUtils" class for any custom preformance data processing:
private function messageHandler(message:IMessage):void { var m:MessagePerformanceUtils = new MessagePerformanceUtils(message); trace("[messageHandler] "+m.prettyPrint()); ... } /* Output: [messageHandler] Original message size(B): 469 Response message size(B): 965 Total time (s): 0.016 Server processing time (s): 0.031 Server non-adapter time (s): 0.031 PUSHED MESSAGE INFORMATION: Total push time (s): 1.688 Originating Message size (B): 521 Server poll delay (s): 1.641 */
With Flex Builder 3 Adobe team moved MXML designer in direction closer to real code. According to framework sources it was started in FB2, but somehow amount of user AS3 code executed in designer for Flex 2 was almost 0. Based on my results with FB3 now components AS3 code can be executed in IDE designer, but component has to be declared in separate project (e.g. library). Note: IDE often won't detect changes immediately, so you will need to completely rebuild project and restar Eclipse to see changes in designer. Also not all code executed at design time at all, (e.g. binding code usually ignored).
mx.core.UIComponentGlobals class provides designMode static property to control component behaviour. With help of this flag now it's possible to execute specific to designer code only at design-time and prevent real runtime code from execution in IDE. UIComponentGlobals.designMode declared as:
/** * A global flag that can be read by any component to determine * whether it is currently executing in the context of a design * tool such as Flex Builder's design view. Most components will * never need to check this flag, but if a component needs to * have different behavior at design time than at runtime, then it * can check this flag. */ public static function get designMode():Boolean { return mx_internal::designTime; }
Created simple code example that demonstrates how it's possible to restart AIR application on the fly. The same functionality is possible with help of well known browser API SWF from Adobe, but this sample is even simpler - no need for external SWF at all:
reboot.as:
package { import mx.core.Application; import mx.core.WindowedApplication; import adobe.utils.ProductManager; public function reboot():void { var app:WindowedApplication = WindowedApplication(Application.application); var mgr:ProductManager = new ProductManager("airappinstaller"); mgr.launch("-launch "+app.nativeApplication.applicationID+" "+app.nativeApplication.publisherID); app.close(); } }
lightReboot.mxml:
<?xml version="1.0" encoding="utf-8"?> <mx:WindowedApplication xmlns:mx="http://www.adobe.com/2006/mxml" layout="absolute"> <mx:Button label="Reboot me" click="reboot()"/> </mx:WindowedApplication>
Ensure "allowBrowserInvocation" option is turned on in AIR application descriptor template:
<allowBrowserInvocation>true</allowBrowserInvocation>
And finally compiled demo application: lightReboot.air
AIR Publisher ID is necessary when working with browser API. "nativeApplication.publisherID" property provides this information at runtime. Alternative way to discover this information in Windows without source code modification described below:
1) Install certain AIR application.
2) Go to installation folder and locate "\META-INF\AIR\publisherid" file. E.g. for previous app it's "C:\Program Files\lightReboot\META-INF\AIR\publisherid"
3) See content of "publisherid" text file, this is Publisher ID. E.g. lightReboot Publisher ID is "E15D8FFF9A3B1300D0966192A76DCB374DC2ACDE.1".
In documentation to OLAPDataGrid Adobe provides "OLAPDataGridExample.mxml" sample demonstrating base control usage. I've extended this code with ability to track selected item as a result of user interaction. Main features implemented in this example are:
Code snip responsible for this functionality:
<local:OLAPDataGrid2 id="myOLAPDG" width="100%" height="100%" change="myOLAPDG_change(event)" > <local:itemRendererProviders> <mx:OLAPDataGridItemRendererProvider uniqueName="[QuarterDim].[Quarter]" type="{OLAPDataGrid.OLAP_HIERARCHY}" formatter="{fmtNumber}"/> </local:itemRendererProviders> </local:OLAPDataGrid2> ... private function myOLAPDG_change(evt:ListEvent):void { var odg:OLAPDataGrid2 = OLAPDataGrid2(evt.target); var dp:IOLAPResult = odg.dataProvider as IOLAPResult; var posRow:IOLAPAxisPosition = odg.selectedItem as IOLAPAxisPosition; if( posRow==null ) return; if( posRow.members.length==0 ) return; // 1) calculate row value var mRow:IOLAPMember = posRow.members.getItemAt(0) as IOLAPMember; var valRow:* = mRow.name; // 2) calculate col value var columnAxis:IOLAPResultAxis = dp.getAxis(OLAPDataGrid.COLUMN_AXIS); var rowAxis:IOLAPResultAxis = dp.getAxis(OLAPDataGrid.ROW_AXIS); var numHeaderCols:int = 0; var firstRow:IList = IOLAPAxisPosition(rowAxis.positions.getItemAt(0)).members; numHeaderCols = firstRow.length; var colNum:int = evt.columnIndex-numHeaderCols; var posCol:IOLAPAxisPosition = null; var valCol:* = null; if( colNum>=0 ) { posCol = columnAxis.positions.getItemAt(colNum) as IOLAPAxisPosition; if( posCol.members.length!=0 ) { var mCol:IOLAPMember = posCol.members.getItemAt(0) as IOLAPMember; var valCol:* = mCol.name; } } // 3) calculate cell value var valCell:* = null; var valCellFmt:* = null; if( colNum>=0 ) { var rowNum:int = evt.rowIndex; if( rowNum>=odg.lockedRowCount ) rowNum+= Math.max(0, odg.verticalScrollPosition); if( rowNum>=0 ) { var cell:IOLAPCell = dp.getCell(rowNum, colNum); if( cell!=null ) { valCell = cell.value; valCellFmt = odg.getFormattedCellValue2(valCell, posRow, posCol); } } } var msg:String = "User click:\n\n [row]\t: "+valRow; msg += "\n [col]\t: "+valCol; msg += "\n [cell data]\t: "+valCell; msg += "\n [cell label]\t: "+valCellFmt; Alert.show(msg, "Dialog"); }
Screenshot for the sample:
Source code to download - OlapGridSample.zip .
Few words about Adobe bug "pinging endpoint Returns a HTTP: Status 200 in ie8".
With new IE8 release in some system configuration remoting with BlazeDS stopped to work. This problem is not always reproducable and not only related to Vista computers. It can be on Windows XP and Windows server 2003 platforms.
Bug behaviour is very strange, initially application works fine, but after some moment it stopped to work reporting following error:
[DEBUG] mx.messaging.Channel 'my-amf' channel got status. (Object)#0 code = "NetConnection.Call.Failed" description = "HTTP: Status 200" details = "http://localhost:7001/demo/messagebroker/amf" level = "error"
This error is cross-domain and may be even "cross-path". Once it happened with box A, it definitelly start reproducing with box B, C, localhost etc. Our systems use default /messagebroker/amf path for AMF channel endpoint. Internet cache is located in place like "C:\Documents and Settings\User1\Local Settings\Temporary Internet Files\Content.IE5\". Empirically we determined that internet cache folders contains many files starting with "amf", e.g. amf[1], amf[2]. etc. When these files are deleted manually (note: that standard explorer usually hides files, use command-line screen for these purposes or some script), system always restores state. On systems with IE8 problem number of "amf" cache files growth with each request. Usually fresh system allows 50 "NetConnection.call" requests, first is ping (command message 5) and 49 application ones. Also maximal file number I saw in cache was amf[11]. Systems without the problem usually create 4 amf[1] files - for one file in each cache subfolder. Probably for CF users these files will start with "flex2gateway" or in case of "flex2gateway//" fix will be just noname e.g. [1], [2], [10] etc.
Solution with add-no-cache-headers option set to false doesn't help. May be exist some configuration option in IE8 or Flash player or some custom HTTP header which can fix this issue, but at this moment I am not aware about this.
Hope Microsoft or Adobe will fix this issue with time. Charles proxy session reported in bug system won't help Adobe to fix this problem, as on socket and data level all is good, problem lies in Flash player NetConnection native code + IE8 HTTP asynchronous pluggable protocol implementation.
For people already taken to the Flex needle and suffering from problem with behaviour described above, following solutions might help:
A) Idea is simple, just use another communication channel, say AMFX, may be it's less efficient, but it will work:
Check that "services-config.xml" contains uncommented HTTPChannel/HTTPEndpoint configuration:
<channel-definition id="my-http" class="mx.messaging.channels.HTTPChannel"> <endpoint url="http://localhost:8080/demo/messagebroker/http" class="flex.messaging.endpoints.HTTPEndpoint"/> </channel-definition>
<default-channels> <channel ref="my-http"/> </default-channels>
B) Implement AMFChannel extension to use dynamic endpoint URL. Note: just to prevent someone from spending time - adding parameters to endpoint URL won't help, e.g. .../messagebroker/amf?uid=321. URL path should be differenet, e.g. /messagebroker/amf321, /messagebroker/amfsomeanother123, etc. It will require in modifications for both client and server code. On server by default endpoints are stored in hash map with strict key like .../messagebroker/amf etc. So create custom message broker that will somehow locate necessary endpoint based on dynamic path. Or create servlet/filter around standard message broker servlet which will prepare request to be acceptable by standard broker implementation.
On client side create custom AMFChannel that will periodically reconnect to server side with different URL (by creating new NetConnection object). It's possible to make reconnect not for every request, but with some periodicity, say one time per 10-40 requests.
C) Try to use if possible secured AMF communication - actually I didn't test it, but may be caching will work in good way there.
D) Try to find options in system which will make cache working in normal way. It can be some key in registry, internet explorer option, header, security constraint etc.
E) Stand aside of MS products and MS dependency now and in future, make your way more open and flexible.
Have a fun.