Skip to main content
Ben Nadel at CFUNITED 2010 (Landsdown, VA) with: Adam Tuttle and Laura Arguello
Ben Nadel at CFUNITED 2010 (Landsdown, VA) with: Adam Tuttle Laura Arguello

Managing ColdFusion Sessions In A ColdFusion Builder Extension

By
Published in Comments (19)

There's been a lot of confusion in my mind as to how exactly ColdFusion session management works in a ColdFusion Builder Extension; sometimes it appears to work and sometimes it doesn't. I've been told by some people that I should rely on the Application scope for persistence. But, at the same time, I've seen with my own eyes that it does work occassionally. At the end of the day, ColdFusion Builder Extensions are powered by ColdFusion Applications, which we all know and love; so, why is this so confusing?

This session-ambiguity exists because both points of view are actually correct - at least, some of the time. ColdFusion Builder Extensions can execute in two very different contexts. One context is a sort of XML-RPC (XML Remote Procedure Call) work flow in which XML packets are posted (by Builder) to your ColdFusion application. In this work flow, your ColdFusion application must return an XML response. That XML response may contain markup that defines a Builder form. Or, it may contain markup that defines a standard web page.

If your XML response contains Builder XML, then the XML-RPC work flow will be continued. If, however, your XML response contains HTML markup, then your ColdFusion Builder Extension window will create a browser panel to render that HTML. Once that HTML panel has been rendered, the XML-RPC work flow is terminated and any subsequent requests performed within that HTML panel execute in a standard Web HTTP request context (for lack of a better term).

ColdFusion Builder Extension Session Management Work Flows.

These two different contexts - XML-RPC and Web HTTP Request - have different behaviors. From what I can see, when ColdFusion Builder is performing XML-RPC calls, there is no additional information outside of the XML posted to your ColdFusion application; this means, no cookies, which means no sessions. Once you drop down into the browser panel and start making Web HTTP requests, the browser engine (probably WebKit based on its anti-aliasing) behaves like a normal browser, posting cookies along with each request. These cookie-containing posts ensure that ColdFusion session management is persisted across requests.

Now that we understand the difference between these two work flows, the question becomes, how do we cross the divide while maintaining session? It's actually not that difficult if you understand the ColdFusion request life cycle. When ColdFusion Builder makes an XML-RPC request to your application, your application generates a new session complete with CFID and CFTOKEN values. To maintain session across the two different contexts, all we have to do is pass those existing CFID and CFTOKEN values to the new context (the Web HTTP Request work flow) where we can start to use them as session tokens in the subsequent requests.

If you're going to be entering a web request context at any point in your ColdFusion Builder Extension execution, you're going to have one request that is a mish-mash of the two concepts - an XML-RPC response that contains HTML markup. To me, this feels like a very uncomfortable limbo and I try to skip over it as quickly as possible. To do so, the HTML contained within my XML-RPC response is nothing more than a META Refresh that forwards my browser panel to my core application. Of course, the most key aspect of this meta refresh is that it passes my existing session tokens onto the next context:

<!---
	Param the FORM value that will contain the data posted from
	the ColdFusion Builder extension. This will be in the form of
	the following XML file:

	<event>
		<ide>
			<projectview
				projectname="SomeThing"
				projectlocation="..." >

				<resource
					path="C:/....txt"
					type="file"
					/>

			</projectview>
		</ide>
		<user>
			<input name="message" value="..." />
			<input name="name" value="..." />
		</user>
	</event>
--->
<cfparam
	name="form.ideEventInfo"
	type="string"
	default=""
	/>


<!---
	Parse the posted XML string into a ColdFusion XML document
	so that we can access the nodes within it.
--->
<cfset requestXml = xmlParse( trim( form.ideEventInfo ) ) />

<!---
	Grab the resource node's PATH attribute from the XML post
	into the document we got from ColdFusion builder.
--->
<cfset resourceNodes = xmlSearch(
	requestXml,
	"//resource[ position() = 1 ]/@path"
	) />

<!---
	Get the file path - we are going to have to forward it onto
	the primary web application.
--->
<cfset filePath = resourceNodes[ 1 ].xmlValue />


<!---
	Let's store the file path in the current session to see
	if the session is maintained between this version of the
	ColdFusion Builder work flow (XML Responses) and the next
	work flow (HTTP Web Requests).
--->
<cfset session.filePath = filePath />


<!--- Store the response xml. --->
<cfsavecontent variable="responseXml">
	<cfoutput>

		<response showresponse="true">
			<ide>
				<dialog
					height="800"
					width="1000" title="Session Tester"
				/>

				<body>
					<![CDATA[

						<!---
							Our response will do nothing more than
							forward the user to the target URL.
							When building the forwarding URL, we
							need to pass the CFID / CFTOKEN created
							in this request. Because we are crossing
							from the XML RPC work flow into the web
							work flow, we need to transfer our
							session across contexts.
						--->
						<cfset targetUrl = (
							"#application.rootURL#index.cfm?" &
							"builderCFID=#session.cfid#&" &
							"builderCFTOKEN=#session.cftoken#"
							) />

						<!DOCTYPE HTML>
						<html>
						<head>
							<title>Loading....</title>
							<meta
								http-equiv="refresh"
								content="0;url=#targetUrl#">
							</meta>
						</head>
						<body>
							<em>Loading....</em>
						</body>
						</html>

					]]>
				</body>

			</ide>
		</response>

	</cfoutput>
</cfsavecontent>


<!---
	Now, convert the response XML to binary and stream it
	back to builder.
--->
<cfset responseBinary = toBinary(
	toBase64(
		trim( responseXml )
		)
	) />

<!---
	Set response content data. This will reset the output
	buffer, write the data, and then close the response. At
	this point, ColdFusion Builder relocate to a screen that
	will build the web portal for our application.
--->
<cfcontent
	type="text/xml"
	variable="#responseBinary#"
	/>

As you can see, this "handler" file parses the posted XML request and extracts the file name of the given resource (the file on which ColdFusion Builder has initiated your extension). This file path is then stored in the session and our XML response is defined. The body of the XML response contains a tiny HTML web page that simply forwards the browser panel to our core web application. Notice that the target URL of the refresh contains "builderCFID" and "builderCFTOKEN" - these are the session tokens associated with the current request that we are passing onto the next context.

These session tokens, that we are passing with the URL, become the persisted session cookies in the target context (the Web HTTP Request work flow). Once we persist them as cookies, the browser panel will take care of implicitly posting them along with every subsequent request. As such, our ColdFusion session is successfully maintained across contexts.

Application.cfc

<cfcomponent
	output="false"
	hint="I define the application settings and event handlers.">

	<!--- Set up application properties. --->
	<cfset this.name = hash( getCurrentTemplatePath() ) />
	<cfset this.applicationTimeout = createTimeSpan( 0, 0, 30, 0 ) />

	<!--- Turn on session management. --->
	<cfset this.sessionManagement = true />
	<cfset this.sessionTimeout = createTimeSpan( 0, 0, 10, 0 ) />

	<!--- Set up request level properties. --->
	<cfsetting showdebugoutput="false" />


	<!--- ------------------------------------------------- --->
	<!--- ------------------------------------------------- --->

	<!---
		Because we might be coming from another context (XML-RPC),
		we need to check if the previous context's session cookies
		have been passed in the URL. If so, we want to transfer
		those cookies to the new context for session maintenance.
	--->
	<cfif (
		!isNull( url.builderCFID ) &&
		!isNull( url.builderCFTOKEN )
		)>

		<!---
			Store the previous context's cookies. Notice that we
			are storing them as "session cookies" (ie. no
			expiration date); this will ensure that the session
			is ended when the console window is closed.
		--->
		<cfcookie
			name="CFID"
			value="#url.builderCFID#"
			/>

		<cfcookie
			name="CFTOKEN"
			value="#url.builderCFTOKEN#"
			/>

	</cfif>

	<!--- ------------------------------------------------- --->
	<!--- ------------------------------------------------- --->


	<cffunction
		name="onApplicationStart"
		access="public"
		returntype="boolean"
		output="false"
		hint="I initialize the application.">

		<!--- The root of the application. --->
		<cfset application.rootDirectory = getDirectoryFromPath(
			getCurrentTemplatePath()
			) />

		<!--- Get the base script for our application. --->
		<cfset application.rootScriptName = getDirectoryFromPath(
			cgi.script_name
			) />

		<!---
			Let's figure out how deep the current request is. We
			will need this to figure out the root URL.
		--->
		<cfset local.scriptDepth = (
			listLen( expandPath( application.rootScriptName ), "\/" ) -
			listLen( application.rootDirectory, "\/" )
			) />

		<!---
			Based on the depth, remove as many directories from
			the end of the root script as is needed to get to a
			script name that points to teh root directory.
		--->
		<cfset application.rootScriptName = reReplace(
			application.rootScriptName,
			"([^\\/]+[\\/]){#local.scriptDepth#}$",
			"",
			"one"
			) />

		<!---
			Now that we have the root script name, we can compile
			our root URL location.
		--->
		<cfset application.rootURL = (
			"http://" &
			cgi.server_name & ":" &
			cgi.server_port &
			application.rootScriptName
			) />

		<!--- Return true so the application can load. --->
		<cfreturn true />
	</cffunction>


	<cffunction
		name="onSessionStart"
		access="public"
		returntype="void"
		output="false"
		hint="I initialize the session.">

		<!---
			Override the session tokens without any expiration
			dates. This will create "session cookies" that will
			expire when the user closes the browser (which in
			our case is the ColdFusion Builder Exntension window).
		--->
		<cfcookie
			name="CFID"
			value="#session.cfid#"
			/>

		<cfcookie
			name="CFTOKEN"
			value="#session.cftoken#"
			/>

		<!--- Store a hit-count to test page requests. --->
		<cfset session.hitCount = 0 />

		<!--- Return out. --->
		<cfreturn />
	</cffunction>

</cfcomponent>

As you can see above, in the pseudo constructor of the Application.cfc, I am checking to see if "builderCFID" and "builderCFTOKEN" exist in the URL scope. If they do, I am using those values to override the session cookies in the current request. Because we are doing this in the pseudo constructor, we are acting before the current request has been associated with any given session. As such, by overriding the session cookies at this point, we will cause the current request to become associated with the previously created session (the ColdFusion application framework is so hot and sexy).

Notice that when I override the cookie values, I am defining them without any expiration dates. This creates them as "session cookies". "Session cookies", not to be confused with ColdFusion session cookies, are cookies that expire when the browser is closed. In our case, since the browser is really just a panel in a ColdFusion Builder Extension window, these session cookies will expire when the user closes the ColdFusion Builder modal window. This isn't required, but it felt like an appropriate life span for the type of applications used as ColdFusion Builder Extensions.

To test all of this out, I created a very simple Index.cfm page which outputs the Session structure as well as the URL values passed from the ColdFusion Builder Extension handler file:

index.cfm

<!--- Increment the session hit count. --->
<cfset session.hitCount++ />


<!DOCTYPE HTML>
<html>
<head>
	<title>ColdFusion Builder Extension Session Testing</title>
</head>
<body>

	<h1>
		ColdFusion Builder Extension Session Testing
	</h1>

	<h2>
		URL
	</h2>

	<!--- Display the URL. --->
	<cfdump
		var="#url#"
		label="URL"
		/>

	<h2>
		Session
	</h2>

	<!--- Output the session. --->
	<cfdump
		var="#session#"
		label="Session"
		/>

	<p>
		<a href="./index.cfm">Refresh Page</a>
	</p>

</body>
</html>

The ability to extend ColdFusion Builder is probably the single most exciting feature of Adobe's new ColdFusion IDE. Of course, to be able to extend it well, it's important that we understand how it works. Session management is a crucial part of any application; getting session management to work in a ColdFusion Builder Extension is tricky, but not impossible. Understanding the different contexts and how they cross over is the key to maintaining a single ColdFusion session per extension execution.

Want to use code from this post? Check out the license.

Reader Comments

357 Comments

Dumb question - if all you do in hit one is an auto push to get out of the XML-RPC 'nature', why bother passing the session values? Just forget about the session and let it start again on the second hit. In theory it means you have a 'wasted' session, but as it will take up next to no RAM, it shouldn't be a big deal.

357 Comments

Also - you could consider simply using urlSessionFormat. I've used that in my CFLib browser. It begins in XML-RPC mode for step 1 (pick a category). In step 2 we have a list of UDFs, paged. If you go to page 2, you've left XML-RPC mode (I don't think it's right to call it that - more on that in my next comment) - I just use urlSessionFormat and it seems to work fine. I -have- seen issues where urlSessionFormat refuses to add the url params.

Of course, to make things even easier, you could just add session.urltoken to your links. Folks don't normally do that in a web app, but for CFB extensions it wouldn't be hard to do, and would be simpler too, right?

357 Comments

XML-RPC:
I'm not sure I'd call it XML-RPC mode. XML-RPC implies quite a bit (in terms of the type of XML request/response) and I don't think it really matches here at all.

15,848 Comments

@Raymond,

I'll be honest with you - I am not sure what XML-RPC embodies; I probably should have explained that more clearly. I just mean that one work flow as defined by XML post/response data packets and that one was defined by "standard" web requests for HTML markup. Bad terminology on my part.

As far as why bother maintaining session across the two different types of work flows, my thought was that you could actually use the session in the first request more effectively. For example, I'm storing the "file path" of the resource in my file.

I could technically pass it through the URL in my redirect; but, I think there's something warm and fuzzy about using the existing session object.

15,848 Comments

@Raymond,

As far as the UrlSessionFormat(), I am not sure I follow what you mean? Are you saying that you are adding the session tokens into the target URLs in your response XML? Or in the HTML markup?

15,848 Comments

@Raymond,

Also, I should probably clarify that this really only works IF you intend to switch from XML to HTML after the first request. I am not sure how cross-XML requests (multi-step XML wizard) would work with session... or rather how one might maintain session.

Perhaps you can shed some light on that? I am sure you know way more on that aspect than I do.

357 Comments

URLSessionFormat does a check to see if cookies are enabled. If not, it auto-adds the url token to the end. This _should_ work all the time, but I've seen it fail.

Seriously though - if your extension has a few sets of links, wouldn't adding session.urltoken manually be _far_ simpler?

15,848 Comments

@Raymond,

Once you're in the "web" work flow, I don't think you should need to modify any links? At that point, I think the user is basically in a stand-alone browser type of situation (theory) where cookies are always enabled.

What I'd really like is to know more about how the web panel is rendered. I assume it's using webkit like AIR, but again, just another theory.

I guess the greater question is - once inside the web panel, can cookies even be disabled?

357 Comments

"Once you're in the "web" work flow, I don't think you should need to modify any links? At that point, I think the user is basically in a stand-alone browser type of situation (theory) where cookies are always enabled."

Sure - but my point is - in a CFB Extension, the # of links should be small, so why not just append session.urlformat? Looking over most of my extensions, I think that would be like 5-6 links. (FYI, I'm NOT doing this now for my extensions - I've used other hacks, so this is a matter of 'Do as I say, not as I do' ;)

"What I'd really like is to know more about how the web panel is rendered. I assume it's using webkit like AIR, but again, just another theory."

Do what I did - dump the CGI scope. ;) If I remember right the user agent was Javasomething or another.

15,848 Comments

@Raymond,

It's all just good conversation - I certainly am not that well versed in Builder yet; heck, I wrote this blog post AS I was exploring the concept ;)

I tried dumping out the CGI; you actually get two different things depending on the context. When you in the XML work flow, the user agent looks to be the "Jakarta Commons HTTP Client". Once you enter the web mode, the user agent gets reports as some "Mozilla" compatible client (I'll double check on what it actually says).

Building a one-page would be cool, no doubt. Whatever browser engine it's using seems to be quite capable.

357 Comments

"It's all just good conversation - I certainly am not that well versed in Builder yet; heck, I wrote this blog post AS I was exploring the concept ;)"

Heh true - didn't mean to imply you shouldn't play as that would certainly be the pot calling the kettle a PHP developer.

"I tried dumping out the CGI; you actually get two different things depending on the context. When you in the XML work flow, the user agent looks to be the "Jakarta Commons HTTP Client". Once you enter the web mode, the user agent gets reports as some "Mozilla" compatible client (I'll double check on what it actually says)."

Interesting - in your settings for Preview, is the browser set to Mozilla? If you switch to Safari or IE, does it impact the result? In other words, is the browser used for 'web' mode the same used for the full page preview.

15,848 Comments

@Raymond,

Uggg, I had a bear of a time getting the Meta Refresh to work again. For some reason, Builder just totally crapped out. Had to restart it twice before it started to work again.

I think perhaps booting Builder and the ColdFusion service at the same time crosses too many wires :)

Anyway, just got it up and running and the User Agent in the web work flow gets reported as:

Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; .NET CLR 1.1.4322; .NET CLR 2.0.50727; .NET CLR 3.0.04506.30; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; InfoPath.1)

I was not aware that there was a preview settings; I'll see if I can change that.

15,848 Comments

@Raymond,

I am not sure where the preview feature is? When I type "preview" into the preferences, there are checkboxes for FireFox and IE... but they are both checked? Is that a different preview?

357 Comments

Preview is shown at the bottom of the code editor. It lets you switch from code to a rendered view. Since both are checked, you should have 2 tabs. My question is - if you deselect Firefox and only have IE, would it then impact the UA of the Extension browser.

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel