A year or so ago I posted an entry explaining why, as a rule, managing session state on the server (as ColdFusion does) was not ideal in Flex applications. I still stand by that assertion. But, many have pointed out that it is sometimes necessary to access ColdFusion session data from within Flex applications. Indeed, I was asked about this several times in the past week alone. And so, time to update the topic.
ColdFusion stores session variables on the server. In order to know which subset of session data belongs to a specific client, ColdFusion creates an identifier that is sent to the client (usually as cookies, but it could also be a URL parameter). The identifier must be sent back to ColdFusion on each and every subsequent request so that ColdFusion may make the correct server-side session data available. Without the identifier, ColdFusion will assume that there is no active session, and will the create a new session and attempt to send a new identifier to the client.
Flex applications run within the Flash Player, not within ColdFusion. As such, Flex applications have no direct access to ColdFusion session data. But, if you were to create a Flex application with a ColdFusion back-end, that Flex application could indeed gain access to ColdFusion session data as needed. Here's how it works.
The preferred way for Flex to connect to ColdFusion is via Flash Remoting - using AMF to connect to a ColdFusion Component. AMF actually communicates back to ColdFusion via HTTP and the web browser. As such, if ColdFusion's session identifier exists in the browser, it will be sent back with Flash Remoting requests. Similarly, if the browser receives identifier cookies along with AMF results, those cookies will be saved. In other words, when you use to connect to a ColdFusion Component, ColdFusion is fully aware of session state management, and the CFC being invoked automatically has access to the correct session data.
But, how can Flex get to that data? The answer is it can't, but you can create a CFC that exposes session variables.
Caution: Before you go further I will point out the obvious. If you create a public facing CFC that exposes methods that allow session data to be directly manipulated, well, you've now allowed public access to session data. Of course, callers will only have access to their own session data, but still, proceed with caution, and make sure you fully understand what you are doing.
Ok, here is the session.cfc file:
<cfcomponent>
<!--- Set a CF session variable --->
<cffunction name="set" access="remote">
<cfargument name="name" type="string" required="yes">
<cfargument name="value" type="string" required="yes">
<!--- Set SESSION var --->
<cfset SESSION[ARGUMENTS.name]=Trim(ARGUMENTS.value)>
</cffunction>
<!--- Get a CF session variable --->
<cffunction name="get" access="remote" returntype="any">
<cfargument name="name" type="string" required="yes">
<!--- Init local var --->
<cfset var result="">
<!--- Get SESSION var if it exists --->
<cfif StructKeyExists(SESSION, ARGUMENTS.name)>
<cfset result=SESSION[ARGUMENTS.name]>
</cfif>
<!--- Return it --->
<cfreturn result>
</cffunction>
<!--- List session variable names --->
<cffunction name="list" access="remote" returntype="array">
<!--- Return it --->
<cfreturn ListToArray(ListSort(StructKeyList(SESSION), "text"))>
</cffunction>
<!--- Keep alive --->
<cffunction name="keepAlive" access="remote">
<!--- Nothing to do here --->
</cffunction>
</cfcomponent>
As you can see, the code is pretty simple. SESSION is actually a ColdFusion structure, and so direct session access is possible using CFML structure functions. The "list" method returns an array of session variables (their names). The "get" method gets the contents of a specific session variable. And the "set" method sets a session variable, creating or updating as needed (this example only creates simple variables). The final method, named "keepAlive" does nothing, and it can be called as needed to keep the session alive (ensuring that it does not time out).
To help test the app (and to populate it with data) you can use a simple test page like this:
<!--- Init vars --->
<cfparam name="FORM.varName" default="">
<cfparam name="FORM.varValue" default="">
<!--- Process form if submitted --->
<cfif Trim(FORM.varName) NEQ "" AND Trim(FORM.varValue) NEQ "">
<cfinvoke component="session"
method="set"
name="#Trim(FORM.varName)#"
value="#Trim(FORM.varValue)#">
</cfif>
<!--- Dump SESSION scope --->
<cfdump var="#SESSION#">
<!--- Form --->
<hr>
<cfform action="#CGI.SCRIPT_NAME#">
Variable:
<cfinput type="text" name="varName" required="yes">
Value:
<cfinput type="text" name="varValue" required="yes">
<br />
<cfinput type="submit" name="sbmt" value="Save SESSION variable">
</cfform>
How to use session.cfc from within Flex? Here is a simple example application:
<?xml version=
"1.0" encoding=
"utf-8"?>
<mx:Application xmlns:mx="http://www.adobe.com/2006/mxml"
layout="vertical" width="100%" height="100%"
creationComplete="initApp()"> <mx:Script> <![CDATA[
import flash.events.TimerEvent;
import mx.utils.ObjectUtil;
// Timer thread handle private var timer:Timer;
// Initialize app private function initApp():void
{
// "Keep alive" interval var pingSeconds:int=60;
// Get session list session.list();
// Create "keep alive" timer timer=new Timer(pingSeconds*1000);
timer.addEventListener(
"timer", timerHandler);
timer.start();
}
// Timer handler private function timerHandler(event:TimerEvent):void
{
// Ping CF session to keep alive session.keepAlive();
}
// Get a CF session variable private function sessionGet(varName:String):void
{
// Get it session.get(varName);
}
// Set a CF session variable private function sessionSet(varName:String, varValue:String):void
{
// Save it session.set(varName, varValue);
// Update list session.list();
}
]]>
</mx:Script> <!-- Define CFC --> <mx:RemoteObject id="session"
destination="ColdFusion"
source="CFFlex.session"
showBusyCursor="true" /> <!-- GET panel --> <mx:Panel width="100%" height="100%"
title="ColdFusion SESSION Variables"> <mx:HBox width="100%" height="100%"> <!-- List fo session variables --> <mx:List id="sessionList"
dataProvider="{session.list.lastResult}"
click="session.get(sessionList.selectedItem)"
height="100%" width="200"/> <!-- Display value --> <mx:TextArea text="{ObjectUtil.toString(session.get.lastResult)}"
editable="false"
width="100%" height="100%" /> </mx:HBox> </mx:Panel> <!-- SET panel --> <mx:Panel width="100%" height="150"
title="Set ColdFusion SESSION Variable"> <!-- Variable create form --> <mx:Form width="100%" height="100%"> <mx:FormItem label="Variable:" width="100%"> <mx:TextInput id="varName" width="100%"/> </mx:FormItem> <mx:FormItem label="Value:" width="100%"> <mx:TextInput id="varValue" width="100%"/> </mx:FormItem> <mx:FormItem> <mx:Button label="Save"
click="sessionSet(varName.text, varValue.text)"/> </mx:FormItem> </mx:Form> </mx:Panel> </mx:Application>
The application features two panels. The first displays a list of ColdFusion session variables (by calling the session.list() method), allowing them to be selected to display their contents (by calling the session.get() method). The second contains a form that can be used to create new session variables (by calling the session.list() method). To ensure that the ColdFusion session does not time out, a Flash timer is defined, and it pings the keepAlive() method (once a minute in this example).
Now, I would still caution you by saying that, as a rule, if data needs to persist it should persist on the client, within the Flex application. But, when you do need access to ColdFusion session data, this technique will do the trick.
Updated 11/16/2007 to add timer and keepAlive logic.
Also, there are different ways to manage sessions in CF (cookies, database, J2EE, etc), will this method work with them all?
Cheers,
Davo
--- Ben
To clarify, the flex app running in the flash player does not refresh the browser, and so after about 20 minutes the session times out and is rendered useless. If it is not possible to increase the timeout value for sessions passed 20 minutes due to limitations imposed by the server administrators, you are %@&#.
Is there a way to use remoting to refresh the session and/or keep it alive? Or are we stuck with a sessionless solution?
Sure, all you need to do is ping a CF page or CFC method on the server ever n minutes. For example, add a CFC method called keepAlive() which does nothing, and use an ActionScript timer to invoke it every few minutes. Problem solved.
--- Ben
I just updated the example to include the keepAlive logic I was referring to. The example now pings the server once a minute, just to keep the session alive.
--- Ben
Instead we've been doing getAccountDetails(), (without passing in a user_id parameter) and then internally the getAccountDetails method always looks to Session.user_id.
I agree, there is persistent data and then there is persistent data. While the authenticated user id should definitely be tracked in the client, it must be validated on the server (although users can always change session identifiers, user tampering is a reality either way, and it must be dealt with). But, other data, like contents of a shopping cart, definitely belong on the client. I guess we need them both.
--- Ben
Your replies bring up great points - so if I may ask a follow up question (that may be slightly OT) - how secure is the persistent data stored in the Flash Player. So, if user_id is passed from your CFC to Flex, is it possible for the user to then manipulate the user_id to do some snooping in your system? I was always under the impression that the flash player was secure from such manipulation.
Thanks,
David
--- Ben
But one problem: when I use HttpService, such as when I call HttpService.send(http://...), also the server need an user identification. Than how we can send the session value the same time?
Hope this isn't too off topic...I'm using a combination of PureMVC in Flex and Coldbox on the CF side. Because the coldboxproxy exposes the Coldbox handlers and Coldbox provides session access plugins (along with a lot more) creating mixed XHTML/Flash apps with shared data and communication has been pretty easy. The Coldspring integration, interceptors, logging, caching and debugging features of Coldbox are also great and using the coldboxproxy they are easily applied to the AMF requests coming from Flex. Setting up a generic coldboxproxy Business Delegate in PureMVC with specific handlers called from Proxies was easy (one I got my head around all the OO / design patterns stuff). Hope some of this is useful to others, I'm still figuring a lot of it out.
Thanks to Luis (Coldbox) & Cliff (PureMVC), their frameworks rock and they are both very supportive and responsive to feedback and questions. Thanks again to you Ben, I got started with CF 2 and your books really helped along the way.
Chris
In your session.cfc, is it best practice to use cflock when using the set and get methods?
Thanks in advance!
Andrew
--- Ben
the CF testpage works fine, but the Flex App returns the following error:
Unable to invoke CFC - Variable SESSION is undefined.
1. When the user logs in and is verified I store a hashed (cftoken + username) on the client side.
2. I also update that into the my webuser table of the database.
3. Then everytime the user sends a request through flex I have a cfc w/ stored procedure that checks against the hashed(cftoken + username) and cftoken in the database.
4. OnSession end it clears it from the database table.
* I always check user permissions server-side against the database
Wouldn't this for the most part keep everything as secure as a standard coldfusion web app?
If this is basic I apologize... just started using Flex
Steve, yes, that will indeed keep things as secure. But the main reason to keep client state information on the client is less about security and more about performance, manageability, and the fact that that is where it belongs. The reason that we store client session state information on the server in web apps is not because that is where it belongs, it's because of a fundamental limitation of the wen in that it is stateless. Flex apps do not have this limitation. So it is less about doing things the way we do in web apps, and more about not having to resort to doing so. Of course, some things do belong on the server, like login timeouts. But other things belong on the client.
--- Ben
Can you tell me what you means with "...that probably means the session identifier is not being passed. You need to verify that...", please?
--- Ben