ColdFusion Scope Existence During Various Request Types And Events
I tend to hear a lot of uncertainty about the existence of various ColdFusion scopes during different types of page requests and application-level events. In particular, there seems to be a lot of confusion as to what ColdFusion scopes exist during remote web service calls (external calls made to local ColdFusion components with "remote" access). Just the other day, I heard someone say that the "APPLICATION" scope doesn't exist during a web service call. Clearly this is inaccurate as it would paralyze the entire web service.
To get us all on the same page, I thought I would throw this test together to clearly demonstrate which ColdFusion scopes exist during various request methods. The key to this demonstration is to accurately log the scope existence at the application-event level. To do this, I created a simple Application.cfc that tracks the primary global scopes in the pseudo constructor as well as the three "On Start" events:
<cfcomponent
output="false"
hint="I provide application settings and event wiring.">
<!--- Define application settings. --->
<cfset THIS.Name = "ScopeTesting" />
<cfset THIS.ApplicationTimeout= CreateTimeSpan( 0, 0, 0, 30 ) />
<cfset THIS.SessionManagement = true />
<cfset THIS.SessionTimeout = CreateTimeSpan( 0, 0, 0, 30 ) />
<cfset THIS.SetClientCookies = true />
<!--- Define the log file path. --->
<cfset THIS.LogFilePath = (
GetDirectoryFromPath( GetCurrentTemplatePath() ) &
"log.txt"
) />
<!--- Log pseudo constructor. --->
<cfset THIS.LogScopes( "PseudoConstruct" ) />
<cffunction name="OnApplicationStart">
<cfset THIS.LogScopes( "OnApplicationStart" ) />
<cfreturn true />
</cffunction>
<cffunction name="OnSessionStart">
<cfset THIS.LogScopes( "OnSessionStart" ) />
</cffunction>
<cffunction name="OnRequestStart">
<cfset THIS.LogScopes( "OnRequestStart" ) />
<cfreturn true />
</cffunction>
<cffunction
name="LogScopes"
access="public"
returntype="void"
output="false"
hint="I check to see if the given global scope exists.">
<!--- Define arguments. --->
<cfargument
name="Event"
type="string"
required="true"
hint="I am the name event in which we are testing."
/>
<!--- Define the local scope. --->
<cfset var LOCAL = {} />
<!--- Append selected scope name. --->
<cffile
action="append"
file="#THIS.LogFilePath#"
output="+--- #ARGUMENTS.Event# -----------------+"
addnewline="true"
/>
<!--- Loop over the scopes. --->
<cfloop
index="LOCAL.ScopeName"
list="APPLICATION, SESSION, CGI, REQUEST, FORM, URL, COOKIE"
delimiters=", ">
<!--- Check to see if scope exists. --->
<cfif IsDefined( LOCAL.ScopeName )>
<!--- Add a special suffix for testing. --->
<cfif (LOCAL.ScopeName EQ "SESSION" )>
<cfset LOCAL.Suffix = " :: #SESSION.CFID# :: #SESSION.CFTOKEN#" />
<cfelse>
<cfset LOCAL.Suffix = "" />
</cfif>
<!--- Log scope existence. --->
<cffile
action="append"
file="#THIS.LogFilePath#"
output="#LOCAL.ScopeName# :: Yes#LOCAL.Suffix#"
addnewline="true"
/>
<cfelse>
<!--- Log scope inexistence. --->
<cffile
action="append"
file="#THIS.LogFilePath#"
output="#LOCAL.ScopeName# :: No !!!!!!"
addnewline="true"
/>
</cfif>
</cfloop>
<!--- Add new line break. --->
<cffile
action="append"
file="#THIS.LogFilePath#"
output=""
addnewline="true"
/>
<!--- Return out. --->
<cfreturn />
</cffunction>
</cfcomponent>
Notice that all we are doing in the various application level events is logging the existence of the following scopes:
- APPLICATION
- SESSION
- CGI
- REQUEST
- FORM
- URL
- COOKIE
In addition to logging the existence, I am treating the SESSION scope as a special case; if we are checking the SESSION scope then I am also outputting the CFID and CFTOKEN values. This way, when I hit the page several times in a row, we can see if the session is maintained across requests.
The Application and Session timeouts are purposely small so that each group of successive requests will be executed in a "new" application environment. Each of these test will utilize 2 successive page requests.
ColdFusion Template Request
In this first test, we are simply going to request a ColdFusion template as we would with any application request. Here is what the log file reveals:
+--- PseudoConstruct -----------------+
APPLICATION :: No !!!!!!
SESSION :: No !!!!!!
CGI :: Yes
REQUEST :: Yes
FORM :: Yes
URL :: Yes
COOKIE :: Yes+--- OnApplicationStart -----------------+
APPLICATION :: Yes
SESSION :: No !!!!!!
CGI :: Yes
REQUEST :: Yes
FORM :: Yes
URL :: Yes
COOKIE :: Yes+--- OnSessionStart -----------------+
APPLICATION :: Yes
SESSION :: Yes :: 6909 :: 45904290
CGI :: Yes
REQUEST :: Yes
FORM :: Yes
URL :: Yes
COOKIE :: Yes+--- OnRequestStart -----------------+
APPLICATION :: Yes
SESSION :: Yes :: 6909 :: 45904290
CGI :: Yes
REQUEST :: Yes
FORM :: Yes
URL :: Yes
COOKIE :: Yes+--- PseudoConstruct -----------------+
APPLICATION :: No !!!!!!
SESSION :: No !!!!!!
CGI :: Yes
REQUEST :: Yes
FORM :: Yes
URL :: Yes
COOKIE :: Yes+--- OnRequestStart -----------------+
APPLICATION :: Yes
SESSION :: Yes :: 6909 :: 45904290
CGI :: Yes
REQUEST :: Yes
FORM :: Yes
URL :: Yes
COOKIE :: Yes
There's nothing special going on here. The APPLICATION and SESSION scopes never exist in the pseudo constructor, even on the second page request. Once the application has booted up (after the first request), the subsequent request only fires the pseudo constructor and the OnRequestStart() application event as expected. Notice also that the session information (CFID and CFTOKEN) are maintained across requests.
Remote Testing
For the following tests, I created an insanely simple remote-access ColdFusion component:
<cfcomponent
output="false"
hint="I provide remote methods.">
<cffunction
name="Test"
access="remote"
returntype="string"
returnformat="json"
output="false"
hint="I return a value.">
<cfreturn "Method Executed" />
</cffunction>
</cfcomponent>
As you can see, all it does is return a static string in JSON format.
ColdFusion Component URL-Based Request
In this test, we are going to access the remote method of the above ColdFusion component using a standard, browser-based URL:
/Test.cfc?Method=Test
When we hit this URL twice, we get the following Log data:
+--- PseudoConstruct -----------------+
APPLICATION :: No !!!!!!
SESSION :: No !!!!!!
CGI :: Yes
REQUEST :: Yes
FORM :: Yes
URL :: Yes
COOKIE :: Yes+--- OnApplicationStart -----------------+
APPLICATION :: Yes
SESSION :: No !!!!!!
CGI :: Yes
REQUEST :: Yes
FORM :: Yes
URL :: Yes
COOKIE :: Yes+--- OnSessionStart -----------------+
APPLICATION :: Yes
SESSION :: Yes :: 6909 :: 45904290
CGI :: Yes
REQUEST :: Yes
FORM :: Yes
URL :: Yes
COOKIE :: Yes+--- OnRequestStart -----------------+
APPLICATION :: Yes
SESSION :: Yes :: 6909 :: 45904290
CGI :: Yes
REQUEST :: Yes
FORM :: Yes
URL :: Yes
COOKIE :: Yes+--- PseudoConstruct -----------------+
APPLICATION :: No !!!!!!
SESSION :: No !!!!!!
CGI :: Yes
REQUEST :: Yes
FORM :: Yes
URL :: Yes
COOKIE :: Yes+--- OnRequestStart -----------------+
APPLICATION :: Yes
SESSION :: Yes :: 6909 :: 45904290
CGI :: Yes
REQUEST :: Yes
FORM :: Yes
URL :: Yes
COOKIE :: Yes
Here, we can see that the URL-based remote component requests act exactly the same as a URL-based ColdFusion template requests. Even the session information is maintained across both requests. This makes sense since both requests are being made by the client browser and therefore will properly pass along the user cookies with each request.
ColdFusion Component WSDL-Based Request
Now, things get a little more exciting; rather than accessing the ColdFusion component using the URL, we are going to create a ColdFusion template (in a separate application environment) to make a WSDL-based web-service request to the remote component:
<!--- Build the URL for the web service test. --->
<cfset strBaseURL = (
"http://" &
CGI.server_name &
REReplace(
GetDirectoryFromPath( CGI.script_name ),
"[^/]+/$",
"",
"one"
)
) />
<!--- Invoke web service --->
<cfinvoke
webservice="#strBaseURL#Test.cfc?wsdl"
method="Test"
returnvariable="strOutput"
/>
<cfoutput>
Return: #strOutput#
</cfoutput>
When we run this code twice, we get the following log data:
+--- PseudoConstruct -----------------+
APPLICATION :: No !!!!!!
SESSION :: No !!!!!!
CGI :: Yes
REQUEST :: Yes
FORM :: No !!!!!!
URL :: Yes
COOKIE :: Yes+--- OnApplicationStart -----------------+
APPLICATION :: Yes
SESSION :: No !!!!!!
CGI :: Yes
REQUEST :: Yes
FORM :: No !!!!!!
URL :: Yes
COOKIE :: Yes+--- OnSessionStart -----------------+
APPLICATION :: Yes
SESSION :: Yes :: 6917 :: 12709646
CGI :: Yes
REQUEST :: Yes
FORM :: No !!!!!!
URL :: Yes
COOKIE :: Yes+--- OnRequestStart -----------------+
APPLICATION :: Yes
SESSION :: Yes :: 6917 :: 12709646
CGI :: Yes
REQUEST :: Yes
FORM :: No !!!!!!
URL :: Yes
COOKIE :: Yes+--- PseudoConstruct -----------------+
APPLICATION :: No !!!!!!
SESSION :: No !!!!!!
CGI :: Yes
REQUEST :: Yes
FORM :: No !!!!!!
URL :: Yes
COOKIE :: Yes+--- OnSessionStart -----------------+
APPLICATION :: Yes
SESSION :: Yes :: 6918 :: 48466463
CGI :: Yes
REQUEST :: Yes
FORM :: No !!!!!!
URL :: Yes
COOKIE :: Yes+--- OnRequestStart -----------------+
APPLICATION :: Yes
SESSION :: Yes :: 6918 :: 48466463
CGI :: Yes
REQUEST :: Yes
FORM :: No !!!!!!
URL :: Yes
COOKIE :: Yes
Here's where we actually see some interesting activity: the FORM scope does not exist in a WSDL-based web service request. Additionally, the session IDs are not maintained across the request, but this is to be expected as they are being made by the ColdFusion server, not a traditional browser/client. Despite the lack of session stickiness, the APPLICATION scope does exist and acts as expected across the two web service requests.
I think that just about covers the majority of requests we are going to be making to a ColdFusion server. I hope this demonstration has helped to clear up any confusion about which ColdFusion scopes exist during the various request types. Really, the only thing of any shock-value here is the fact that the FORM scope doesn't exist during a WSDL-based request. Other than that, all requests to the ColdFusion server have very much the same behavior.
Want to use code from this post? Check out the license.
Reader Comments
It's a bit OT, but what's the deal with using this.X() to call methods in your CFC? You should just be using X() to call methods defined in the same CFC. When you do this.X(), it acts as an 'outside' call, so if X had access=private, you would actually be blocked.
@Ray,
I just love scoping stuff :) It's completely an emotional issue. It gets me into trouble sometimes because there is a bug in ColdFusion that does not allow me to use named-arguments in privately scoped method calls:
<cfset VARIABLES.Method( Name=Value ) />
... throws a bug, so I have to compromise sometimes if I need named arguments on private methods.
Scoping just makes me feel comfortable; and it makes a strong statement about the implementation / intent of my methods (some might say this is exactly the reason *not* to do it - so, like I said, its emotional).
I like scoping too as well - but this just seems like a mistake. It's like using your phone to call your wife when she is sitting next to you in the room. If you want to use name/val pairs for a local method and you can't do variables.x(....), then you could always use cfinvoke method="X". Its a lot more typing though.
This REALLY bugs me - talk about emotional though - and OT to this entry, which is pretty darn interesting.
@Ray,
Trust me, I see the insanity of it. I will work on getting a hold of myself :)
Thanks Ben, a really useful summary.
aw, you forgot to test flex2gateway and flashservices calls
I didn't even realize there was such a thing as 'flashservices' until my app started getting errors about Session scope not existing.
@Don,
I am not even sure how to test those :) Any suggestions?
It gets a little more complicated, its a request made from either Flex/Flash to domain.com/flex2gateway/
Or in my case, flash media server to /flashservices/gateway/
And with your test files, it was easy to test my situation:
(Sorry for the long post)
+--- PseudoConstruct -----------------+
APPLICATION :: No !!!!!!
SESSION :: No !!!!!!
CGI :: Yes
REQUEST :: Yes
FORM :: Yes
URL :: Yes
COOKIE :: Yes
+--- OnApplicationStart -----------------+
APPLICATION :: Yes
SESSION :: No !!!!!!
CGI :: Yes
REQUEST :: Yes
FORM :: Yes
URL :: Yes
COOKIE :: Yes
+--- OnSessionStart -----------------+
APPLICATION :: Yes
SESSION :: Yes :: 58000 :: 98049968
CGI :: Yes
REQUEST :: Yes
FORM :: Yes
URL :: Yes
COOKIE :: Yes
+--- OnRequestStart -----------------+
APPLICATION :: Yes
SESSION :: Yes :: 58000 :: 98049968
CGI :: Yes
REQUEST :: Yes
FORM :: Yes
URL :: Yes
COOKIE :: Yes
What other 'magic urls' does CF have?
@Don,
Thanks for testing that one for us :) Looks pretty standard. I don't know how FLEX interacts with cookies, but my guess is that sessions would not be maintained across calls.
Not sure if this is what you meant or not, but we pass Client variables to our Flex2Gateway to maintain session, and this seems to work like a charm.
Useful stuff as always, Ben. Since this may serve as a bit of a compendium for some, I would think a couple other rather common scenarios worth pointing out would be 1) a CFHTTP invocation of a CFC (or the equivalent in other languages calling into a CF page), and 2) an Ajax call to a CFC.
In case 1), I'm referring to how one might call a CFC like your first attempt (/Test.cfc?Method=Test) via a URL from a programmatic client like CFHTTP (versus a programmatic client calling via soap). In terms of variables present, I suspect it would act like your invocation of it as a soap web service, in that it too would not have the client/session id cookies passed in, unless one manually added them using CFHTTPPARAM (or the equivalent if calling from another language).
In case 2), I suspect it may act more like your first call (from a browser), since Ajax requests also come from a browser and therefore should present the cookies. I'll let others with more experience with different Ajax frameworks speak up on any variations they may have noticed.
I've not noticed how either respond with respect to FORM variables, so if you or anyone else runs a test, it would be good to drop a comment here. As it is, I was just checking in on some things quickly while on vacation. Happy holidays, all.
@Charlie,
Untested, but here are my theories:
For Case 1, I am pretty sure a CFHTTP call would act like a standard browser invocation. I think that's how we get screen-scraping to work. However, as you are saying, the session will probably restart with every CFHTTP request unless the cookies are manually passed-back.
For Case 2, I have field-experience that that works; I have some AJAX apps that are session-based and make AJAX calls that require sessions to work and I am not passing anything session-based back.
As for how CFHTTP and SOAP requests differ, I don't really understand that stuff. I do know that if you access the WSDL file directly in the browser:
URL: Test.cfc?wsdl
... the FORM scope does not exist. So, whatever mechanism handles the SOAP request seems to also handle the WSDL request as well (or maybe those are actually the same request - one with a SOAP body, one without).
What I think would be interesting is how the CGI.http_user_agent comes across in the various calls. I wonder if SOAP uses the browser agent, or if it goes through a ColdFusion-proxy which announces itself horizontally as the "ColdFusion bot".
Speaking of ColdFusion proxies, I attended a Simon Free presentation last week - he was talking about AMF web services; I wonder if AMF comes across as a ColdFusion-proxy user agent or if the browser user-agent gets passed-through.
Re: the difference between cfhttp and ajax requests. Ajax requests should have the cf cookies attached and so will have session (and client) scope variables.
To clarify: AJAX communication to a CFC (let's say with jQuery), would only have access to the application and session scopes if the CFC resides in the same application folder right?
Related question: Assuming the CFC is within the same application, is it a horrible idea to directly access the user's session scope from the CFC methods when handling an AJAX request?
Normally, to follow standard OO best practices, a CFC's methods should only handle arguments passed to it… but in the case of an AJAX request, the Javascript client request doesn't know about the user's Coldfusion session scope natively. may not be safe to have Coldfusion pass those vars to the client, just so they can be sent back to the CFC.
I would love to hear your thoughts on this topic if you get the opportunity.
@Dan:
"AJAX communication to a CFC (let's say with jQuery), would only have access to the application and session scopes if the CFC resides in the same application folder right?"
Please remember - the web server has no concept of AJAX. AJAX is just HTTP via JavaScript. Therefore, the 'rules' here are no different.
"Assuming the CFC is within the same application, is it a horrible idea to directly access the user's session scope from the CFC methods when handling an AJAX request?"
I've blogged on this before - in my opinion, there is nothing wrong with a "Service" CFC directly accessing these scopes. As we all know, CFC creation can be slow, and it can be complex. If you have gone through the trouble of using something like ColdSpring to set up the CFCs, it is silly not to provide access to them via the scopes and your service CFC. (In fact, ColdSpring supports generating these service objects for you.)
My 2 cents.
@Dan,
I agree with Ray on this matter. Because AJAX does not have access to cached components, there is a certain amount of rule bending that is necessary.
I am having trouble making a variable, which is part of a returned structure, available to another cffunction in the same component (in CF7).
If I refer to a cfset that creates a randomID, the call gets made twice. Once in the first cffunction and again in the second so the randomIDs will never match.
But then I can't figure out how to get the randomID out of the structure being returned by the first cffunction to compare it against the randomID being returned by the argument. RandomID is in the structure which is
<cfreturn returnStruct />
I have tried setting the scope to THIS, VAR and VARIABLES to no avail. Any help or pointers somewhere would be appreciated.
Sorry, I had some trouble posting. Here is an example of the code:
<cfcomponent>
<cfset myRandomID = CreateUUID()>
<cffunction>
<cfscript>
returnStruct = StructNew();
StructInsert(returnStruct, "message", "XXX");
StructInsert(returnStruct, "randomID", myRandomID);
</cfscript>
<cfreturn returnStruct />
</cffunction>
<cffunction>
<cfargument name="randomID" type="string" required="true">
<cfif #arguments.randomID# is #myRandomID#>
Yes
<cfelse>
No
</cfif>
</cffunction>
</cfcomponent>
Can you post the code that calls your CFC?
I suspect you are creating a new copy of the CFC each time you call it and so the creation of the random number happens each time.
You have to instantiate the cfc into a variable and use it twice so that it is only created once.
Since there is more than one way to skin this cat, post your calling code and I will try and indicate where you should change it.
Ah-ha. Thanks Kevin.
The calling code calls the two methods (though I didn't name the cffunctions in my code above).
So...
<cfinvoke
webservice="http:something.cfc?wsdl"
method="FirstMethod"
returnvariable="returnStruct"
>
<cfinvokeargument name="foo" value="#foo#" />
</cfinvoke>
Do stuff here.
<cfinvoke
webservice="http:something.cfc?wsdl"
method="SecondMethod"
returnvariable="success"
>
<cfinvokeargument name="randomID" value="#returnStruct["randomID"]#" />
</cfinvoke>
Thanks again for helping.
Ahh!
Are you really calling the functions from another machine? That makes it way more complex!
So first let assume you are calling the CFC from the same machine. You would have to write something like this:
-------------------------
<cfobject type="component" component="path.to.my.something" name="myComponent">
<cfinvoke
component="myComponent"
method="FirstMethod"
returnvariable="returnStruct"
>
<cfinvokeargument name="foo" value="#foo#" />
</cfinvoke>
Do stuff here.
<cfinvoke
component="myComponent"
method="SecondMethod"
returnvariable="success"
>
<cfinvokeargument name="randomID" value="#returnStruct["randomID"]#" />
</cfinvoke>
--------------------------
The first <cfobject> tag instantiates the component putting it into memory and then it is called twice using its name by the <cfinvoke> tags.
Ok that's all straightforward but when you call it as a webservice its much more difficult since you have to put it into memory on the remote machine and then call it twice remotely which you can't do directly.
To put it into memory you would normally write someting in the Application.cfc like:
--------------------------
<cfobject type="component" component="path.to.my.something" name="Application.myComponent">
--------------------------
Now the component is in application memory but you can't call it remotely, you have to write a new proxy component to call it and call the proxy remotely.
---------------------------
<cfcomponent>
<cffunction name="function1">
<cfinvoke component="Application.myComponent" method="function1">
</cfinvoke>
<cfreturn returnStruct />
</cffunction>
<cffunction name="function2">
<cfargument name="randomID" type="string" required="true">
<cfinvoke component="Application.myComponent" method="function2">
<cfinvokeargument name="randomID" value="arguments.randomID">
</cfinvoke>
</cffunction>
</cfcomponent>
---------------------------
Now you call the proxy as a webservice like you tried earlier....
Kevin
@Robert,
@Kevin is right on. You're going to need to cache the given component to get the IDs to remain consistent across web service calls.
"I see" said the blind man as he picked up his hammer and saw.
This is intended to be a web service that will be called by a vendor using .Net. I created the calling template for testing purposes.
These revelations lead me to believe I should change my approach and temporarily store that randomID in a database so I can refer to it in the second method with a query.
Thanks again for the help and the education!
@Robert,
No problem at all. Good luck. I think a database should work just as well; there's a bit of a performance overhead to hitting a database vs. getting something cached in the application scope. But, using a database is definitely more scalable.
Hi Ben,
When you heard the comment "the "APPLICATION" scope doesn't exist during a web service call", that probably wasn't synonymous with "Application.cfc methods aren't triggered during a web service call".
What the person most likely meant was that they couldn't reference the application scope from within the webservice.
For instance, if you were to change your remote Test() method to reference application.applicationName and run the same test, you should see "APPLICATIONNAME is undefined in APPLICATION".
Example:
<cffunction
name="Test"
access="remote"
returntype="string"
returnformat="json"
output="false"
hint="I return a value.">
<cfreturn application.applicationName />
</cffunction>
At least, that is what happens for me... if it isn't for you, please let me know. The ability to reference application.applicationName from within a CFC would solve a problem I am trying to figure out now.
As it turns out, someone put a rogue (empty) application.cfm a couple directories up from my web service CFC.
Obviously, that is why the app scope was not available to it.
Oops :-)
@Bobby,
Ah, I've been burned by the rogue Application.cfm file before! So frustrating. One time, I actually wrote a whole blog post about an issue I was having, cause I thought it was a bug; then, right before I posted it, the thought popped into my mind that I might have an App.cfc issue. Turned out, I didn't have one in my testing directory... but I had one like 4 directories up, that was messing with my request processing.
Arrrg - that was a few hours wasted :)
Yo, is your avatar from The Last Dragon? ... "Who's the master!!!"
@Ben,
"Yo, is your avatar from The Last Dragon? ... "Who's the master!!!""
Sho-nuff... and SHO-NUFF! :-p
@Bobby,
Ha ha ha, awesome :)
For the life of me, using CF8 at work, the Request Scope simply IS NOT available when I am calling a CFC via AJAX.
I've had to jerry-rig by having the Calling page send a variable which is the Request-scope variable I want to maintain all the way through the AJAX call and back. I really hate that I've yet to comprehend why the scope variables disappear on me.