I am experiencing a weird behavior with the application/session scope in Coldfusion (2016 UPDATE 5) when using Websockets and calling WSPublish.
Below is code that illustrates this anomaly:
<cfdump var="#application.Security.GetSession()#" label="1A application.Security.GetSession()">
<cfdump var="#session#" label="1B session">
<cfset wsPublish("notifications", "Test") />
<cfdump var="#application.Security.GetSession()#" label="2A application.Security.GetSession()">
<cfdump var="#session#" label="2B session">
In 1A and 1B, the session is returned as expcted.
Then I run the WSPublish() function.
In 2A, the session is empty, but in 2B it is still there as before.
So, after running WSPublish(), when I try and access the session by calling another CFC to retrieve it (2A), it does not exist. However, the session does exist when accessing it directly (2B).
All the application.Security.GetSession() method does is this:
<cfcomponent output="no">
<cffunction name="GetSession" returntype="any">
<cfif NOT IsDefined("session")>
<cfreturn StructNew() />
<cfelse>
<cfreturn session />
</cfif>
</cffunction>
</cfcomponent>
If I remove the WSPublish() call, then 1A, 1B, 2A, 2B all return the correct session data.
Why is WSPublish() causing this anomaly and how do I resolve it?
Thanks!
Related
We've migrated a ColdFusion application from ColdFusion 10 to ColdFusion 2016. After Migration, Application variables are not staying in its scope, it is refreshing on each and every request.
Consider the following example,
Application.cfm
<cfsetting enablecfoutputonly="true" />
<CFAPPLICATION NAME="Test App"
SETCLIENTCOOKIES="YES"
CLIENTMANAGEMENT="YES"
SESSIONMANAGEMENT="YES"
SESSIONTIMEOUT="#CREATETIMESPAN(0,8,0,0)#"
APPLICATIONTIMEOUT="#CREATETIMESPAN(1,0,0,0)#">
<cfdump var="#Application#" label="app">
<CFLOCK SCOPE="APPLICATION" TYPE="EXCLUSIVE" TIMEOUT="10">
<CFSET Application.Email = "test#test.com">
<CFSET Application.DataSource="test">
</cflock>
Test.cfm
<CFLOCK SCOPE="APPLICATION" TYPE="READONLY" TIMEOUT="10">
<cfset Application.one = 1>
<cfset Application.two = 2>
<cfset Application.three = 3>
</cflock>
OnRequestEnd.cfm
<cfsetting showdebugoutput="false" />
<cfdump var="#Application#" label="onRequestEnd">
So if we request /test.cfm
it'll throw the following output
Again refreshing the page also giving the same output
Not sure why the Application scoped variables are losing its persistence.
the following is the expected output..
Any idea of Why the application variables are lost and getting refreshed on each and every request ?
I haven't tested this code, but what you're seeing is the procedural order of operation performed by Application.cfm. You're essentially redefining the application on every request, which is why on the name exists in your initial dump and the rest exist on the dump in onRequestEnd.
If you update your code to use Application.cfc, you can ditch the cflock code, better organize your "triggers" and define your application variables once, when needed, using onApplicationStart.
<cfcomponent>
<cfset this.name = "Test App">
<cfset this.SETCLIENTCOOKIES="YES">
<cfset this.CLIENTMANAGEMENT="YES">
<cfset this.SESSIONMANAGEMENT="YES">
<cfset this.SESSIONTIMEOUT="#CREATETIMESPAN(0,8,0,0)#">
<cfset this.APPLICATIONTIMEOUT="#CREATETIMESPAN(1,0,0,0)#">
<cfsetting enablecfoutputonly="true" />
<cffunction name="onApplicationStart" access="public" returnType="void" output="false">
<cfset application.Email = "test#test.com">
<cfset application.DataSource="test">
</cffunction>
<cffunction name="onRequestStart" access="public" returntype="boolean" output="false">
<cfset application.one = 1>
<cfset application.two = 2>
<cfset application.three = 3>
<cfreturn true>
</cffunction>
<cffunction name="onRequestEnd" access="public" returntype="boolean" output="false">
<cfsetting showdebugoutput="false" />
<cfdump var="#application#" label="onRequestEnd">
<cfreturn true>
</cffunction>
</cfcomponent>
This should define email and datasource in the application scope one time, when the app first loads. The variables one, two and three will be created at the start of each request, but you can add a check to set them only if they don't already exist.
You can then use child Application.cfc files to help modularize your application using sub-folders and sub-application specific variables. They'll still exist in the scope of the larger application, but you'll be able to manage them from a location specific to a sub-app this way.
I've got the following code in a method:
<cffunction name="serviceTicketValidate" access="public" output="yes" returntype="void" hint="Validate the service ticket">
<cfargument name="service_ticket" type="string" required="yes" hint="The ST to validate" />
<!--- Contact the CAS server to validate the ticket --->
<cfhttp url="#Variables.cas_server#serviceValidate" method="get">
<cfhttpparam name="ticket" value="#Arguments.service_ticket#" type="url" />
<cfhttpparam name="service" value="#Variables.service#" type="url" />
</cfhttp>
<!--- Received a valid XML response --->
<cfif IsXML(cfhttp.FileContent)>
<cfset XMLobj = XmlParse(cfhttp.fileContent)>
<!--- Check for the cas:user tag --->
<cfset CASuser = XmlSearch(XMLobj, "cas:serviceResponse/cas:authenticationSuccess/cas:user")>
<!--- Set the username to the value --->
<cftry>
<cfif variables.username NEQ ''>
<cfdump var="#Variables.username#" /><cfreturn/>
</cfif>
<cfif ArrayLen(CASuser)>
<cfset Variables['username'] = CASuser[1].XmlText />
</cfif>
<cfcatch>
<cfdump var="#cfcatch#" /><cfabort/>
</cfcatch>
</cftry>
<!--- Search for cas:attributes --->
<cfset CASattributes = XmlSearch(XMLobj, "cas:serviceResponse/cas:authenticationSuccess/cas:attributes")>
<!--- Go through all the attributes and add them to the attributes struct --->
<cfif ArrayLen(CASattributes)>
<cfloop array=#CASattributes[1].XmlChildren# index="attribute">
<cfset StructInsert(Variables.attributes,RemoveChars(attribute.XmlName,1,Find(":",attribute.XmlName)),attribute.XmlText)/>
</cfloop>
</cfif>
</cfif>
Note I added the cftry and cfcatch to see what is going on exactly. I've also added the if username != blank to debug as well. This method is called in another method like so:
<cfinvoke method="serviceTicketValidate">
<cfinvokeargument name="service_ticket" value="#service_ticket#" />
</cfinvoke>
<cfdump var="test2" /><cfabort/>
Again I've added the dump and abort for testing. The variable.username is defied and set to an empty string when the component is initiated and the component is initiated into a session variable.
So get this... when the whole process runs the first time I get output on my screen test2 as expected. Then, the next time the same thing is run, the session exists, thus the variable.username is set to something. In the first code block I can dump variables.username and see the username. However if I try to use variables.username in a conditional expression (like in that if statement) or if I remove the if statement and let the script try to change the value of variable.username, there are no errors, it just breaks out of the script completely. It ends that method, and the method that called it and I don't see test2 like I would think. It all just ends for some reason.
If you need further details I can provide more code but I tried to trim out as much as I thought was relevant. All methods are in the same component, all methods are public. Why can't I change the value of variables.username and why is there no error?
EDIT:
I think it may have something to do with the cflock but I'm debugging some stuff right now. I had a redirect inside the code block that is inside the lock. So I guess it never unlocks. But I even waited after the timeout and it still remained locked. I thought the lock was supposed to expire after the timeout.
I'm a little confused but it seems like you're trying to use a cfc's variables scope to set caller variables. The variables scope is not available to the caller the way it seems you are trying to use it.
index.cfm
<cfoutput>
<cfset objTest = createObject("component", "testscope").init()><br><Br>
<cfset objTest.checkValue()><br><br>
Calling page is checking the existence of testvar: #isDefined("variables.testvar")#
</cfoutput>
testscope.cfm
<cfcomponent displayname="testscope">
<cffunction name="init" access="public">
init() just set variables.testvar.
<cfset variables.testvar = "Okay, set">
<cfreturn This>
</cffunction>
<!--- Set some more variables --->
<cffunction name="checkValue" access="public">=
<cfoutput>Checkvalue is checking the value of testvar: #variables.testvar#</cfoutput>
</cffunction>
</cfcomponent>
The output is
init() just set variables.testvar.
Checkvalue is checking the value of testvar: Okay, set
Calling page is checking the existence of testvar: false
Hi I'm very new with ColdFusion 10 especially with Application.cfc. It is very confusing.
I created Application.cfc and inside this cfc I created the following:
My questions are:
1. Why do I get session error? (see my codes below)
2. What should I put in sessionEnd function?
<!--- Application.cfc --->
<CFCOMPONENT displayname="Application" output="true">
<cfset THIS.Name = "MyTestApp" />
<cfset THIS.ApplicationTimeout = CreateTimeSpan(0,0,60,0) />
<cfset THIS.SessionManagement = true />
<cfset THIS.SessionTimeout = CreateTimeSpan( 0, 0, 30, 0 ) />
<cfset THIS.SetClientCookies = false />
<cfset THIS.SetClientCookies = false />
<cffunction name="OnApplicationStart" access="public"
returntype="boolean" output="false">
<cfset application.Main_DSN = "MyTestDB">
</cffunction>
<cffunction name="onApplicationEnd" output="false">
<cfargument name="applicationScope" required="true">
</cffunction>
<cffunction name="OnSessionStart" access="public" returntype="void" output="false"
hint="Fires when user session initializes">
<cfset session.loggedin = "NO">
<cfset session.username = "">
<cfset session.userrights = "">
<cfset session.usergroup = "">
</cffunction>
<!--- After user login, I have checklogin.cfm --->
<cfif mylogin NEQ true><!--- Login failed, go redirect to login page --->
<cflocation url="login.cfm">
<cfelse>
<cfset session.loggedin = "YES"><!--- ??? error: see below --->
<cfset session.username = "#Trim(Form.username)#">
<cfset session.userrights = "#Trim(Form.userrights )#">
<cfset session.usergroup = "#Trim(Form.usergroup)#">
</cfif>
<cffunction name="onSessionEnd">
<!--- Not sure what can I put in here????? --->
</cffunction>
Your login code is outside any of the event handlers in your Application.cfc file, so that code will run first... before any of the event handlers!
The execution sequence of code in Application.cfc is:
code outside of any event handler (irrespective of where it is in the file)
onApplicationStart()
onSessionStart()
onRequestStart()
etc
So you cannot have code referencing the session scope (or application scope for that matter) outside the other event handlers. You can only start using session variables once onSessionStart() has fired. Not before.
What is the error message that you are getting?
You can really put anything in the session end. Anything that you would need to run to clean up after a session.
Maybe you want to update a log or send an email, clear a cookie, or redirect to a certain page.
EDIT:
Is everything else in the Application.cfc working for you? It seems like the onSessionStart event is not firing.
EDIT 2:*
Sorry for doing this via an answer but I need more rep to leave comments.
The session should start as soon as you connect to the application, you do not need to wait to be "logged in" as in verify credentials.
If the session variables do not exist then the event is not firing for some reason. Make sure you have the file name correctly and it is in the root folder of the application.
EDIT 3:
Your login code is not in the onSessionStart function, not in any function. Unless I misunderstood your comment and you were saying that code section is in the "checklogin.cfm" file and not in the Application.cfc file.
EDIT 4:
I need to jump on a client call but I can help some more after. We might need to take a look at part of the checklogin.cfm file.
EDIT 5:
Any luck?
EDIT 6:
Sorry for the delay, volume is ramping up this afternoon.
At this point I think I would either need to see the code that is doing the validation checking or ask if you are sure the validation is working correctly.
I assume you are connecting to a database, if you take the query you are using to verify the login credentials and run it in SQL server or whatnot inserting the values you would type into the form, do you get any results?
EDIT 7:
Figure it out, OP?
I have a web form which uses the action attribute to call a CFC like this:
<form action="mycfc.cfc?method=registeruser">
The CFC processes the data in the form and then I want it to return a variable telling the user if their form submission has been successful or not.
So within my CFC, I put this:
<cffunction name="registeruser" access="remote" hint="registers a new user" returnformat="JSON">
... PROCESSES FORM HERE THEN...
<cfset Msg = 'Success'>
<cfreturn Msg>
<cflocation url = "/registrationpage.cfm">
</cffunction>
How do I display the Msg variable in the registrationpage.cfm page? My output is set to JSON so I guess I have to DeSerialize but I have no idea how to actually reference/access this output from the method.
My whole answer is for educationnal purposes only and I strongly advise you to use an existing framework rather than reinventing the Wheels. Have a look at Picking a ColdFusion MVC Framework
You can store the value in the session scope. A lot of frameworks does it using a flash memory concept, which is a temporary memory (implemented as a struct) that destroys members when accessed.
Have a look at http://cfwheels.org/docs/1-1/chapter/using-the-flash it's quite straight forward to implement an API that does this.
Client code could look like (depending on your implementation):
<cfset session.flash.set('importantMsg', 'some important msg')>
<cflocation ...>
Then from the other page:
<cfif session.flash.has('importantMsg')>
<!--- The following line should also destroy the 'importantMsg' key --->
#session.flash.get('importantMsg')#
</cfif>
Here's an implementation example (not that the implementation is not thread-safe):
FlashMemory.cfc
<cfcomponent>
<cffunction name="init" returntype="FlashMemory">
<cfset variables.instance = {flash = {}}>
</cffunction>
<cffunction name="set" returntype="void">
<cfargument name="key" type="string" required="true">
<cfargument name="value" type="any" required="true">
<cfset variables.instance.flash[arguments.key] = arguments.value>
</cffunction>
<cffunction name="get" returntype="any">
<cfargument name="key" type="string" required="true">
<cfset var v = variables.instance.flash[arguments.key]>
<cfset structDelete(variables.instance.flash, arguments.key)>
<cfreturn v>
</cffunction>
<cffunction name="has" returntype="boolean">
<cfargument name="key" type="string" required="true">
<cfreturn structKeyExists(variables.instance.flash, arguments.key)>
</cffunction>
</cfcomponent>
onSessionStart
<cfset session.flash = new FlashMemory()>
Also please note that in your case, your remote CFC methods shouldn't return anything. You will use the flash memory to pass data around instead. That means when the method has finished it's work you can simply redirect the client.
You probably shouldn't use remote CFC methods in this particular case:
I have never really used remote CFC methods as stateful web services. The various advantages of remote CFC methods like their ability to spit out data in multiple data-interchange formats (JSON, WDDX...) is lost with your implementation.
You could simply do something like:
registration.cfm
<cfset errors = session.flash.get('registrationErrors')>
<cfif arrayLen(errors)>
<!--- Display errors --->
</cfif>
<form method="post" action="register.cfm">
...
</form>
register.cfm
<cfset registration = new Registration(argumentcollection = form)>
<cfset validator = new RegistrationValidator(registration)>
<cfif validator.validate()>
<cfset application.userService.register(registration)>
<!--- You can also redirect to a page that represents the correct state --->
<cflocation url="registered.cfm" addtoken="no">
<cfelse>
<!--- Store the errors collection in the flash memory --->
<cfset session.flash.set('registrationErrors', validator.errors())>
<!--- Redirect to the page the user came from --->
<cflocation url="#cgi.http_referer#" addtoken="no">
</cfif>
Take the cflocation tag out of your function. It will not execute anyway because it's after the cfreturn tag.
Also, post your form to a .cfm page, not the .cfc. From that .cfm page, do this:
<cfset MyObject = CreateObject(stuff for your cfc goes here)>
<cfset MessageFromFunction = MyObject.registeruser()>
<cfoutput>#MessageFromFunction</cfoutput.
Here is my coldfusion web service. When I use soapUI tool to call 'test', i am receiving this
"<ns1:stackTrace xmlns:ns1="http://xml.apache.org/axis/">org.xml.sax.SAXParseException: Premature end of file."
error.
can anyone help me on this issue? I already seached online, but no luck. any code issue?
do I need to use <cfproperty> tag?
<cfcomponent output="false">
<!--- initialisation --->
<cffunction
name="init"
output="false"
hint="return an initialized object.">
<!--- Return THIS reference. --->
<cfreturn THIS />
</cffunction>
<!--- ping --->
<cffunction
name="test"
access="remote"
returntype="numeric"
output="false"
hint="return an true = 0.">
<!--- declare local variables --->
<cfset var local = 0 />
<!--- Return 0. --->
<cfreturn local />
</cffunction>
</cfcomponent>
Couple questions: are you on CF9? What happens when you invoke it straight up (not through SoapUI, but as a http call in a browser)?
Also, I would strongly recommend against using "Local" as a variable name in a function - CF9 introduced some changes in how variables are scoped within a function and uses that as the name for that constrained scope.