Maintaining Sessions Across Multiple ColdFusion CFHttp Requests

Posted May 24, 2007 at 5:14 PM

Tags: ColdFusion

I have never needed to maintain a user session across multiple ColdFusion CFHttp calls before; I don't do that much screen scraping. But, I can't help people debug their own code (an unaswered "Ask Ben" question) until I understand how this works, so I thought I would give it a go. In order to do this, I created an extremely simple ColdFusion application that did nothing more than keep track of the number of times a user hit a page.

Here is the Application.cfc ColdFusion component that defines this mini application:

 Launch code in new window » Download code as text file »

  • <cfcomponent
  • hint="Hanles the application events and setup.">
  •  
  • <!--- Define application. --->
  • <cfset THIS.ApplicationName = "Session Test" />
  • <cfset THIS.ApplicationTimeout = CreateTimeSpan( 0, 0, 5, 0 ) />
  • <cfset THIS.SessionManagement = true />
  • <cfset THIS.SessionTimeout = CreateTimeSpan( 0, 0, 5, 0 ) />
  • <cfset THIS.SetClientCookies = true />
  •  
  •  
  • <!--- Define request. --->
  • <cfsetting
  • showdebugoutput="false"
  • enablecfoutputonly="true"
  • />
  •  
  • </cfcomponent>

As you can see, this does nothing more than define the application and session management as well as the request settings. The only other page in that application, index.cfm, keeps track of the page requests for the give session:

 Launch code in new window » Download code as text file »

  • <!--- Param the session hit count. --->
  • <cfparam
  • name="SESSION.HitCount"
  • type="numeric"
  • default="0"
  • />
  •  
  •  
  • <!---
  • Add one to the hit count. This session is very simple.
  • We are using this purely to see if the hit count goes
  • up from page to page indicating that the session has held.
  • --->
  • <cfset SESSION.HitCount = (SESSION.HitCount + 1) />
  •  
  • <!--- Set the return value. --->
  • <cfset strOutput = ("Hit Count: " & SESSION.HitCount) />
  •  
  •  
  • <!---
  • Return the hit count. We are returning it via the
  • CFContent tag so that we can stream it to the browser
  • without ever using the CFOutput tags.
  • --->
  • <cfcontent
  • type="text/html"
  • variable="#ToBinary( ToBase64( strOutput ) )#"
  • />

As you can see, another really simple page; it builds the output text (the hit count string) and then streams it to the browser using ColdFusion's CFContent tag and variable attribute (the use of the Variable attribute is something I love, but it is not required).

Ok, so now, we need to set up a page outside of this application that performs a ColdFusion CFHttp call to grab that index.cfm page's output. For this demo, our test page exists in the directory above the sub application and belongs to its own application (whose definition is of no consequence for the experiment).

Since, I am not 100% sure how multiple calls using CFHttp will react, I felt the first thing I needed to try was just that - calling CFHttp multiple times without any special actions or parameters:

 Launch code in new window » Download code as text file »

  • <!---
  • Get the URL of the application page that we want to
  • grab. This page lives under a different application
  • and will have different session management (that we
  • must account for in subsequent requests).
  • --->
  • <cfset strURL = (
  • CGI.server_name &
  • GetDirectoryFromPath( CGI.script_name ) &
  • "/app/index.cfm"
  • ) />
  •  
  •  
  • <!---
  • Store the user agent that we want to send (so CFHttp
  • doesn't send "ColdFusion" as the user agent). Since I
  • am testing this in a browser, I am just going to grab
  • the current user agent.
  • --->
  • <cfset strUserAgent = CGI.http_user_agent />
  •  
  •  
  • <!---
  • Grab the first page request. In this one, we are not
  • going to sent any CFHttpParams since we don't know
  • anything about the target application environment.
  • --->
  • <cfhttp
  • method="GET"
  • url="#strURL#"
  • useragent="#strUserAgent#"
  • result="objGet"
  • />
  •  
  • <!--- Dump out the CFHttp response. --->
  • <cfdump
  • var="#objGet#"
  • label="First CFHttp Request"
  • />
  •  
  •  
  • <!---
  • Now, let's make a second request, same as the first
  • to see what gets send back.
  • --->
  • <cfhttp
  • method="GET"
  • url="#strURL#"
  • useragent="#strUserAgent#"
  • result="objGet"
  • />
  •  
  • <!--- Dump out the CFHttp response. --->
  • <cfdump
  • var="#objGet#"
  • label="Second CFHttp Request"
  • />

When I running that code, I CFDump out the two result objects returned by the vanilla CFHttp calls.

First CFHttp Request:


 
 
 

 
ColdFusion CFHttp Request Result  
 
 
 

Second CFHttp Request:


 
 
 

 
ColdFusion CFHttp Request Result  
 
 
 

Notice that in both CFHttp result objects, the FileContent variable (highlighted in yellow) reports "Hit Count: 1". This happens because the session was not maintained across the two calls and hence, each request was given a new SESSION scope and therefore a new base line hit count. Furthermore, if you look at the cookies sent back from the browser, Set-Cookie (also highlighted in yellow), you will see that the two CFHttp requests receive two different CFID and CFTOKEN values. Since a session is represented by its CFID/CFTOKEN combination, these two requests clearly have different sessions.

In order to maintain session on the subsequent CFHttp requests, we must need to maintain the same cookies. Therefore, we need to be able to parse the cookies out of the response header of one CFHttp request result and use them as parameters in subsequent CFHttp requests.

To do this, I created a ColdFusion user defined function (UDF) that would take a CFHttp result and parse the returned cookie strings into some sort of easily usable structure:

 Launch code in new window » Download code as text file »

  • <cffunction
  • name="GetResponseCookies"
  • access="public"
  • returntype="struct"
  • output="false"
  • hint="This parses the response of a CFHttp call and puts the cookies into a struct.">
  •  
  • <!--- Define arguments. --->
  • <cfargument
  • name="Response"
  • type="struct"
  • required="true"
  • hint="The response of a CFHttp call."
  • />
  •  
  •  
  • <!--- Define the local scope. --->
  • <cfset var LOCAL = StructNew() />
  •  
  • <!---
  • Create the default struct in which we will hold
  • the response cookies. This struct will contain structs
  • and will be keyed on the name of the cookie to be set.
  • --->
  • <cfset LOCAL.Cookies = StructNew() />
  •  
  • <!---
  • Get a reference to the cookies that werew returned
  • from the page request. This will give us an numericly
  • indexed struct of cookie strings (which we will have
  • to parse out for values). BUT, check to make sure
  • that cookies were even sent in the response. If they
  • were not, then there is not work to be done.
  • --->
  • <cfif NOT StructKeyExists(
  • ARGUMENTS.Response.ResponseHeader,
  • "Set-Cookie"
  • )>
  •  
  • <!---
  • No cookies were send back in the response. Just
  • return the empty cookies structure.
  • --->
  • <cfreturn LOCAL.Cookies />
  •  
  • </cfif>
  •  
  •  
  • <!---
  • ASSERT: We know that cookie were returned in the page
  • response and that they are available at the key,
  • "Set-Cookie" of the reponse header.
  • --->
  •  
  •  
  • <!---
  • Now that we know that the cookies were returned, get
  • a reference to the struct as described above.
  • --->
  • <cfset LOCAL.ReturnedCookies = ARGUMENTS.Response.ResponseHeader[ "Set-Cookie" ] />
  •  
  •  
  • <!--- Loop over the returned cookies struct. --->
  • <cfloop
  • item="LOCAL.CookieIndex"
  • collection="#LOCAL.ReturnedCookies#">
  •  
  •  
  • <!---
  • As we loop through the cookie struct, get
  • the cookie string we want to parse.
  • --->
  • <cfset LOCAL.CookieString = LOCAL.ReturnedCookies[ LOCAL.CookieIndex ] />
  •  
  •  
  • <!---
  • For each of these cookie strings, we are going to
  • need to parse out the values. We can treate the
  • cookie string as a semi-colon delimited list.
  • --->
  • <cfloop
  • index="LOCAL.Index"
  • from="1"
  • to="#ListLen( LOCAL.CookieString, ';' )#"
  • step="1">
  •  
  • <!--- Get the name-value pair. --->
  • <cfset LOCAL.Pair = ListGetAt(
  • LOCAL.CookieString,
  • LOCAL.Index,
  • ";"
  • ) />
  •  
  •  
  • <!---
  • Get the name as the first part of the pair
  • sepparated by the equals sign.
  • --->
  • <cfset LOCAL.Name = ListFirst( LOCAL.Pair, "=" ) />
  •  
  • <!---
  • Check to see if we have a value part. Not all
  • cookies are going to send values of length,
  • which can throw off ColdFusion.
  • --->
  • <cfif (ListLen( LOCAL.Pair, "=" ) GT 1)>
  •  
  • <!--- Grab the rest of the list. --->
  • <cfset LOCAL.Value = ListRest( LOCAL.Pair, "=" ) />
  •  
  • <cfelse>
  •  
  • <!---
  • Since ColdFusion did not find more than one
  • value in the list, just get the empty string
  • as the value.
  • --->
  • <cfset LOCAL.Value = "" />
  •  
  • </cfif>
  •  
  •  
  • <!---
  • Now that we have the name-value data values,
  • we have to store them in the struct. If we are
  • looking at the first part of the cookie string,
  • this is going to be the name of the cookie and
  • it's struct index.
  • --->
  • <cfif (LOCAL.Index EQ 1)>
  •  
  • <!---
  • Create a new struct with this cookie's name
  • as the key in the return cookie struct.
  • --->
  • <cfset LOCAL.Cookies[ LOCAL.Name ] = StructNew() />
  •  
  • <!---
  • Now that we have the struct in place, lets
  • get a reference to it so that we can refer
  • to it in subseqent loops.
  • --->
  • <cfset LOCAL.Cookie = LOCAL.Cookies[ LOCAL.Name ] />
  •  
  •  
  • <!--- Store the value of this cookie. --->
  • <cfset LOCAL.Cookie.Value = LOCAL.Value />
  •  
  •  
  • <!---
  • Now, this cookie might have more than just
  • the first name-value pair. Let's create an
  • additional attributes struct to hold those
  • values.
  • --->
  • <cfset LOCAL.Cookie.Attributes = StructNew() />
  •  
  • <cfelse>
  •  
  • <!---
  • For all subseqent calls, just store the
  • name-value pair into the established
  • cookie's attributes strcut.
  • --->
  • <cfset LOCAL.Cookie.Attributes[ LOCAL.Name ] = LOCAL.Value />
  •  
  • </cfif>
  •  
  • </cfloop>
  •  
  •  
  • </cfloop>
  •  
  •  
  • <!--- Return the cookies. --->
  • <cfreturn LOCAL.Cookies />
  • </cffunction>

If we pass a CFHttp result to the above UDF and then CFDump out the structure, we get something like this:


 
 
 

 
ColdFusion CFHttp Cookies Parsed Into ColdFusion Struct  
 
 
 

Once, we have our cookies parsed into a ColdFusion struct, we can send them as ColdFusion CFHttpParam values in subsequent CFHttp requests. For our next request, CFHttp request three, we are going to assume that request one and two have already been done and that the objGet from the second request is available for cookie parsing:

 Launch code in new window » Download code as text file »

  • <!---
  • At this point, we have established that multiple requests
  • with CFHttp do not inherently keep the session. Now, let's
  • figure out what happens when we send across the returned
  • cookie values.
  • --->
  •  
  •  
  • <!---
  • Get the cookies from the last response object. These are
  • the cookies that we are going to echo back in subsequent
  • CFHttp requests.
  • --->
  • <cfset objCookies = GetResponseCookies( objGet ) />
  •  
  •  
  • <!---
  • Now, let's make a subsequent CFHttp call, but this
  • time, we are going to pass in the cookies that were
  • returned in the last call to see if we can maintain
  • the same session.
  • --->
  • <cfhttp
  • method="GET"
  • url="#strURL#"
  • useragent="#strUserAgent#"
  • result="objGet">
  •  
  • <!--- Loop over the cookies we found. --->
  • <cfloop
  • item="strCookie"
  • collection="#objCookies#">
  •  
  • <!--- Send the cookie value with this request. --->
  • <cfhttpparam
  • type="COOKIE"
  • name="#strCookie#"
  • value="#objCookies[ strCookie ].Value#"
  • />
  •  
  • </cfloop>
  •  
  • </cfhttp>
  •  
  •  
  • <!--- Dump out the CFHttp response. --->
  • <cfdump
  • var="#objGet#"
  • label="Third CFHttp Request"
  • />

Running that code, we get the following CFHttp result object:


 
 
 

 
ColdFusion CFHttp Request Result With Same Session  
 
 
 

Notice that the FileContent of the third request (highlighted in yellow) is "Hit Count: 2". The only way that the second hit count could be returned is if the sub application maintained the session from the second CFHttp request to the third. Notice also that in the result of the third CFHttp request, no cookies were sent back. ColdFusion only sets the cookies when the user's session begins. Since the session is maintained across page requests, ColdFusion does not require any further cookies to be set and therefore sends no cookie requests in the response header.

Download Code Snippet ZIP File

Comments (18)  |  Post Comment  |  Ask Ben  |  Permalink  |  Other Searches  |  Print Page




Adobe ColdFusion 8.0.1 Update - Helping Programmers To Be Signifanctly Less Girlie - Download ColdFusion 8 Update 8.0.1 Now.

Reader Comments

Nice work

Posted by Shuns on May 24, 2007 at 6:10 PM


Thanks dude. Always fun to try something new out.

Posted by Ben Nadel on May 24, 2007 at 6:52 PM


Great... Great Article.. Ben...

Posted by Dav on May 25, 2007 at 1:51 AM


@Dav,

Thanks a lot. Please let me know if you can think of a good way I might expand upon this idea in a way that people might find useful.

Posted by Ben Nadel on May 25, 2007 at 7:03 AM


Interesting article. I hadn't ever thought about the idea that sessions wouldn't be maintained.

In response to expansion idea, how about a custom tag that is aware of it's own session and keeps it across requests. e.g. <cf_http session="mySession1">

Posted by Jeremy French on May 25, 2007 at 8:52 AM


@Jeremy,

That could be a cool idea. Let me play around with it a bit. Thanks for the suggestion.

Posted by Ben Nadel on May 25, 2007 at 8:56 AM


You shouldn't need to pass cookies, just pass the session info as a url parameter. You should be able to shorten your code to just one line:

<cfhttp
method="GET"
url="#urlSessionFormat(strURL)#"
useragent="#strUserAgent#"
result="objGet">

NOTE: If the urlSessionFormat() tag doesn't work, just manually append the required URL parameters (cfid/cftoken or jsessionid.)

Posted by Dan G. Switzer, II on May 25, 2007 at 9:41 AM


@Dan,

What you are saying is most definitely true, assuming we are talking about a ColdFusion application and that we already have the CFID / CFTOKEN values handy. But, what if this was calling an eBay site or something? Totally different technology and application scope. The returned page request will have nothing to do with the session of the code that is calling the CFHttp tag.

Posted by Ben Nadel on May 25, 2007 at 10:21 AM


I use cfhttp to communicate with another server through a web service, and I have to maintain the session. Since the web service is behind a single sign-on layer, I have to grab the cookies set by the SSO login, and pass it to the web service login. I have to keep sending the cookies with every call to the web service because cfhttp doesn't automatically keep track of the session.

I like the way you are handling the cookies. I am basically doing the same thing. I store my cookies in an array. Each array item is the name/value pair for the individual cookies. I send one value for the cookies in the cfhttp call like this:

<cfhttpparam type="header" name="Cookie" value="#ArraytoList(cookieArray';')#">

This way I don't have to loop through the structure collection every time.

Just adding my 2 cents.

Matthew

Posted by Matthew Abbott on May 25, 2007 at 11:59 AM


@Matt,

That is really cool. I didn't realize it would recognize the entire cookie string and parse it correctly (on the receiving end). This is MUCH easier than looping. Thanks!

Posted by Ben Nadel on May 30, 2007 at 5:57 PM


When ever I try to run your code I keep getting returned error: Variable GETRESPONSECOOKIES is undefined.

92 : CFHttp requests.
93 : --->
94 : <cfset objCookies = GetResponseCookies( objGet ) />
95 :
96 :

I created the GetResponseCookies.cfc file and I don't understand the error. I'm totally confused.

Posted by Jason on Dec 4, 2007 at 10:20 PM


@Jason,

GetResponseCookies() is a user defined function; it doesn't have to be in a CFC. The CFFunction tag just has to be included somewhere in your application the way any user defined function would be. It looks like you are just not including or defining the function before you try to utilize it.

Posted by Ben Nadel on Dec 5, 2007 at 7:05 AM


Hi Ben. One thing to note - this code chokes when a single cookie is set by the server (in which case, it comes back as a single string rather than a struct). A quick check to see what type of data is in the Set-Cookie item will fix this.

Posted by James Holmes on Feb 18, 2008 at 10:33 PM


@James,

Really?? That is so strange. I figured ColdFusion would have kept the same data type no matter what. That seems really lame that they don't automatically create a struct regardless of number of cookies.

Thanks for the heads up.

Posted by Ben Nadel on Feb 19, 2008 at 7:16 AM


I get a loop error on line 84 of this UDF. I think I'm running into the issue James mentions, though I'm not sure how to fix this.

Posted by Gernot on Jul 1, 2008 at 11:34 PM


@Gernot,

Make sure you have the most up to date CFC for this project:

http://www.bennadel.com/projects/cfhttp-session.htm

Posted by Ben Nadel on Jul 2, 2008 at 8:17 AM


Ben, have you considered putting this up at riaforge?

Posted by James Holmes on Jul 2, 2008 at 8:35 AM


@James,

I have considered it. It's just a matter of finding the time.

Posted by Ben Nadel on Jul 2, 2008 at 8:37 AM


Post Comment  |  Ask Ben


Home   |   Web Log   |   ColdFusion   |   Projects   |   Resume   |   Job Form   |   Search   |   Contact
Epicenter Consulting - Custom Software Solutions for Business Evolution HostMySite.com - The Leader In ColdFusion Hosting