There are several methods for managing client state with a clustered ColdFusion solution, including the following:
All of the solutions listed here work to some degree in a clustered solution, but require careful implementation to ensure that they function properly. A Little about Server-Side ColdFusion CLIENT, APPLICATION, SERVER, and SESSION VariablesApplying the old saying "What's past is prelude" is perhaps the best way to understand ColdFusion's implementation of SESSION variables. ColdFusion doesn't replace HTTP name-value pairs or cookies, but it does automate the process of identifying users and sessions; you can therefore concentrate on your session-dependent applications instead of the mechanics of maintaining a session. All server-side ColdFusion variable storage and retrieval depends on the existence of two variables, CFID and CFTOKEN. These two parameters define a unique identity for the user and reference variables stored in one of several places on the ColdFusion server. CFID and CFTOKEN are most commonly implemented as cookies, but you can use ColdFusion sessions without cookies by relying on HTTP name-value pairs. Again, you need to pay close attention to detail, making sure the URLs passed among pages in your application include these pairs. Uniquely identifying the user is only half the value. To leverage session management fully, you must be able to store information about the user on the server. Since version 4, ColdFusion has offered several methods for storing server-side variables. The various types shown here enable you to define layers of persistent variables:
If you have only one ColdFusion server, it doesn't matter that SERVER, APPLICATION, and SESSION variables are stored in RAM or that CLIENT variables are often stored in the server's Registry. But what happens if you have two ColdFusion servers? A SESSION variable that's stored in RAM on Server 1 isn't visible to a ColdFusion page on Server 2. You don't want the user to have to maintain a separate session for each of your servers; you want the user to have a single session with your entire site. How can you take advantage of SESSION and CLIENT variables in a scaled environment? Later in "Using a Central Client Variable Repository" and "Using Hardware Based Session Management" you will learn several ways to solve this problem. Embedding Parameters in a URL or a FORM PostThere are many reasons for passing session state information among Web pages using URL parameters or FORM variables. Passing these variables from page to page can offer cross-application supportWeb pages running on different servers or different application-server platforms. These methods can also eliminate the need to use SESSION variables or client cookies. Client variables, CFID and CFTOKEN, can be used to help maintain session state. You can append the variables to the URL on each page request, and ColdFusion will automatically recognize and use the variables. Listing 5.1 shows the most common way to do this by including the variables in the URL. Listing 5.1. addtokens.cfmAppending CFID and CFTOKEN to the URL String<cfapplication sessionmanagement="yes" name="chap5"> <cflock type="EXCLUSIVE" timeout="10" scope="SESSION"> <cfset SESSION.mySessionVar = "Advanced ColdFusion MX 7"> </cflock> <cflocation url="somepage.cfm?cfid=#cfid#&cfidtoken=#cftoken#"> What you would see in your browser URL when you appened the CFID and CFTOKEN varibles is something like this: http://localhost/somepage.cfm?cfid=300&cfidtoken=13296302 NOTE CFLOCATION allows you to do this automatically as well by using the ADDTOKEN attribute like <cflocation url="somepage.cfm" addtoken="Yes">. Another much easier method to embed URL session information specifically in situations where you whish to maintain state is by using the function URLSessionFormat(). This function checks to see if a user's client accepts cookies and if it does not it automatically appends all client identification information to the URL. To use it in the above listing all you would have to do is change the CFLOCATION tag URL like this: <cflocation url="#URLSessionFormat("MyActionPage.cfm")#"> Embedding information in URL strings can be a security risk. Aside from the issue of passing potentially sensitive information about the user (such as a password) in cleartext using a URL to pass CFID and CFTOKEN without another layer of user verification can allow a hacker to high jack, although unlikely, a users account. Furthermore appending and maintaining state information manually in a URL string is difficult. It is equally difficult and time consuming to pass information from page to page using FORM variables. You must expend painstaking effort to make sure all FORM elements and URL strings are sending the correct information to the CGI or script. TIP For more on best practices when passing information in URL's please refer to http://www.macromedia.com/go/tn_17255 as well as make sure to keep up on security threats to ColdFusion and Macromedia products in general by keeping a eye on the following Web page: http://www.macromedia.com/devnet/security/security_zone/ CookiesCookies are probably the most popular method for maintaining state and are one of the simpler methods to implement, as illustrated in Listing 5.2. Cookies are stored on the client, and therefore any server in the domain can use them. This allows state management in a clustered environment. Cookies can be persistent or session based. Persistent cookies exist beyond the user's session and typically have an expiration date. Session-based cookies automatically expire after the user closes the browser. Listing 5.2. login.cfmLogin Form to Authenticate Users and Return Them to the Originating Page<!--- Page Name: login.cfm Description: Authenticate the user and their password. Return successful logins to original page. ---> <cfparam name="URL.originURL" default="#CGI.script_name#?#CGI.query_string#"> <cfparam name="FORM.username" default=""> <cfparam name="errMsg" default=""> <cfif isDefined("FORM.submit")> <cfquery name="qryLogin" datasource="OWS"> SELECT contacts.FirstName,contacts.LastName, contacts.userRoleID, UserRoles.UserRoleName FROM Contacts , UserRoles WHERE Contacts.userRoleID = UserRoles.UserRoleID and Contacts.UserLogin = '#form.UserLogin#' AND Contacts.UserPassword='#form.UserPassword#' </cfquery> <cfif qryLogin.recordCount EQ 1> <cfcookie name="fullname" value="#qryLogin.FirstName# #qryLogin.LastName#"> <cfcookie name="userSecurity" value="#qryLogin.UserRoleName#"> <cflocation url="#FORM.originURL#"> <cfabort> <cfelse> <cfset errMsg = "Incorrect login information: Please try again"> </cfif> </cfif> <cfoutput> <form actoin="#CGI.script_name#" method="post" name="login"> <table width="250" cellpadding="3" cellspacing="0" border="1" align="center"> <tr bgcolor="navy"> <td> <font face="verdana" size="2" color="white"> <b>Login</b> </front> </td> </tr> <tr> <td> <font face="verdana" size="2" color="000000">#errMsg#</font> <br><b>UserName:</b><br> <input type="text" name="username" value="<cfoutput>#FORM.username#</cfoutput>" maxlength="25"> <br><b>Password:</b><br> <input type="password" name="userpassword" maxlength="25"> <br><br> <input type="submit" name="submit" value="submit"> <input type="hidden" name="originURL" value="#URL.originURL#"> </td> </tr> </table> </form> </cfoutput> </body> </html> Listing 5.2 shows a processing template for a login form. In this case, there are different classes of usersadministrators and normal users. The main distinguishing factor is what permissions they have to the system. In this code, the first time a user requests somepage.cfm, they are redirected to the login page (Figure 5.1). After a successful login, two cookies are set for the user's full name and security level (Figure 5.2). You can use these cookies throughout the site to interact with the user. Figure 5.1. A login form.Figure 5.2. After a user has successfully logged in, a welcome message greets the person and shows his or her security level.You can invoke security by applying the logic shown in Listing 5.3 in other Web pages. This example uses the somepage.cfm template to call the login form if the fullname cookie does not exist, to ensure that the user has logged in before seeing this page. Listing 5.3. somepage.cfmSnippet of Template to Call Login Form If Cookie Does Not Exist<!--- Check if the user has logged in ---> <cfif IsDefined("COOKIE.fullname")> <!--- proceeed ---> <b>Welcome back - <cfoutput>#COOKIE.fullname#</cfoutput></b><br> Your security level is - <cfoutput>#COOKIE.userSecurity#</cfoutput> <cfelse> <cfparam NAME="originURL" DEFAULT="#CGI.script_name#?#CGI.query_string#"> <cflocation URL="/login.cfm?originURL=#urlEncodedFormat(originURL)#"> <cfabort> </cfif> Consider the following issues with using cookies to store session state:
Because a user might access your site from more than one machine or browser (or might experience a system crash that wipes out cookies), it's usually best to store a minimal user identifier in a cookie and keep critical data on the server side. It is possible to track a user's state through an application by carrying the variables along on the client side, either in name-value pairs in the URL or in a client-side cookie. Information stored in cookies can be either name-value pairs or complex WDDX packets (see Chapter 16, "Using WDDX"), storing a structure of information about the user. Carrying this data around in the URL is a painstaking, difficult-to-maintain practice, and even the most intrepid Web developer should think twice before going down this road. The upside of this strategy is that it does not matter to the system whether a user is redirected to another machine. All the information the script needs is contained in the URL referencing it. Storing this information in cookies is easier to implement and allows storage of complex data structures in the form of WDDX packets. You can further simplify this scheme by specifying cookies as the default repository for CLIENT variable storage in ColdFusion Administrator. The downside of using cookies is that because they are maintained solely on the client side, an enterprising user can hack the application by modifying the cookies. The following sections examine ColdFusion-specific solutions for implementing session-state management. SESSION Variables versus CLIENT VariablesColdFusion offers two methods for developers to maintain session state when running on the traditional ColdFusion application server platform: CLIENT variables and SESSION variables. This section discusses the benefits and risks of using these two variables for implementing session state in a clustered environment. To use CLIENT or SESSION variables, ColdFusion sets two values for each user: CFID, a sequential client identifier, and CFTOKEN, a random-number client-security token. These two variables will uniquely identify a user to ColdFusion and help maintain state. SESSION variables exist in memory on the server that initiated the session with the user. This is an issue in a clustered Web site. The user's session will be lost upon transfer to another server in the cluster. The new server will not know about the prior session and will start a new session with the user. SESSION-aware load balancing can resolve this problem by keeping a user on the same server throughout the session (see the discussion on this topic in the section "Keeping the User on the Same Machine"). This server becomes a single point of failure, and you risk the server's crashing and losing the user session. CLIENT variables can exist in three ways: in the server's Registry, in a database, or in cookies. To use CLIENT variables in a clustered environment, you should store them either in a centrally located database or as cookies to share among all servers in the cluster. Keep in mind that there are serious problems with storing CLIENT variables in the Registry. On high-volume sites, storing too many persistent variables in the Registry will eventually overflow the Registry, causing instability and server crashes. If you must store CLIENT variables in the Registry, set the purge setting in ColdFusion Administrator to a low value (Figure 5.3). Figure 5.3. Setting the purge duration for unvisited clients using the Registry's CLIENT data store.NOTE Macromedia strongly discourages customers from storing CLIENT variables in the Registryeven in a single-server environment. If you're not careful, you'll end up adding large amounts of data to the Registry in the form of stored CLIENT variables. Because the Registry was not intended to work as a relational database, this data can overwhelm the Registry quickly and cause system instability or crashes. Storing CLIENT variables in a database is easy to administer and is outlined later in this chapter (see "Using a Central CLIENT Variable Repository"). This is the recommended method for maintaining CLIENT variables. It allows the Web site to scale and will let all servers in the cluster access the same CLIENT store. If the user will not accept cookies, maintaining state with CLIENT or SESSION variables will be difficult. Writing CFID and CFTOKEN as session-based cookies may appease users who are filtering cookies. Session-based cookies offer an alternative and are not persistent, existing only as long as the user session exists. Listing 5.4 illustrates how to code this workaround. By setting the client cookie attribute to No, ColdFusion does not automatically store the variables to cookies; you need to set them manually in code. For this example make the following client-management settings in the Application.cfm template. Listing 5.4. Application.cfmSettings for Client Management with Session-Based Cookies<cfapplication name="MXusers" clientmanagement="Yes" setclientcookies="No"> <!--Set the client cookies as session-based cookies --> <cfcookie name="cfid" value="#CLIENT.cfid#"> <cfcookie name="cftoken" value="#CLIENT.cftoken#"> You can use the CLIENT-management methods described above to manage SESSION variables as well, except that you can't store SESSION variables in a central database. Keeping the User on the Same MachineOne popular method for managing session state in a scaled environment is to direct a user to the server that's currently most available (least utilized) and to have the user continue to interact with the same server for the duration of the session. You can accomplish this approach through either a software-based solution such as ClusterCAT's SESSION-aware clustering, or hardware-based solutions. NOTE This solution is most prevalent for session-management solutions involving SESSION variables. Although this method is certainly valid, obvious limitations exist when you're trying to use your server resources to their fullest. For example, User 1 might make a quick stop at your site and only request three simple requests from Server 1 during his or her session. User 2 could be a seasoned user who requests 10 requests from Server 2, including a complex database transaction, during the session. As a result, Server 2 is far busier than Server 1, even though both servers have handled one session. You can't maintain complete balance. The advantage of SESSION-aware clustering is that you can accomplish it much more simply (and inexpensively) than truly session independent clustering. Using a Central CLIENT Variable RepositoryColdFusion has the capability to store client information in a central database. This feature creates an effective way to save state across scaled Web servers. If you store CLIENT variables in a central database, any of your ColdFusion servers with access to this database can use the same pool of CLIENT variables. See Figure 5.4 for this type of configuration. Figure 5.4. A diagram of client redirection from one session to another.After you establish your central database, you can set parameters on clients from any of your front-end ColdFusion servers. They remain accessible even if a user switches from one machine to another as long as you continue to pass CFID and CFTOKEN or some other unique identifier. Because CLIENT variables can persist from session to session, you now have a collection of information for each user that can be accessed whenever the user visits your site. Given the simplicity of such a setup, this is a good strategy for many applicationsit anticipates the need to scale across multiple servers, even if you don't need to do so right away. NOTE Client variables function much like Session variables and if your users refuse to enable cookies on their web browsers then you must pass the CFID and CFTOKEN using one of the methods described earlier in 'Embedding Parameters in a URL or Form post'. When you decide on this strategy, you must configure your ColdFusion servers to take advantage of the database. Assuming you've already set up a central database server, and you only need to configure your ColdFusion servers to use that database for client storage, here's how to get started:
Listing 5.5. getlastaccess.cfm<cfapplication name="MXusers" clientmanagement="Yes" clientstorage="cfMXvars"> <cfif isDefined("CLIENT.LastAccess")> <cfoutput>You were last here on #CLIENT.lastAccess#.</cfoutput> <cfelse> <cfset client.LastAccess = dateFormat(Now())> <cflocation url="somepage.cfm"> </cfif> All this simple code does is check the CFID and CFTOKEN cookies on a user's client and see if they match values in the DataBase. If they do then CLIENT.LastAccess will be defined and retrieved based on the CFID and CFTOKEN. After you complete these steps, all CLIENT variables are stored in the data source. As long as you've configured all your Web servers to use the central database, you don't need to worry about which server receives a given user's request. Even if your environment is not clustered, it is still best to store CLIENT variables in a central database because of the dangers of using the Registry Also For higher-traffic sites, you may wish to select 'Disable Global Client Variable Updates'. In general try leaving them enabled and load test your system then disable them and load test your system to see how much it actually affects your specific applications performance. You should also try to keep this in mind when designing your code if you plan to use client variables such as "LVISIT" (the last date/time the client with that CFID loaded a page) and "HITCOUNT" (the number of page impressions by a particular client). If you decide to check 'Disable Global Client Variable Updates' you may break your code. Java SessionsJava session management offers an alternative to traditional ColdFusion SESSION variables. J2EE session management uses a session-specific identifier called jsessionid. Using Java sessions in ColdFusion, you can share sessions between ColdFusion and other Java applications, including JavaBeans, Java Server Pages (JSPs), JSP custom tabs, and Java servlets. This offers many possibilities for extending your ColdFusion application with Java. There is another reason to use J2EE session variables instead of normal session variables: security. ColdFusion uses the same client identifiers for the Client scope and the standard Session scope. Since CFToken and CFID values are used to identify a user over a period of time, they are generally saved as cookies on the client browser. These cookies persist until the client's browser deletes them, which can amount to a considerable length of time. This creates a security risk because a hacker can get access to these variables over a period of time and then spoof or pretend to be a user and gain unauthorized access to sites or systems. Although this is very unlikely, it is still possible and thus a major security consideration. Using J2EE SESSION variables counters this risk. The J2EE session-management mechanism creates a new session identifier for each session and does not use either the CFToken or the CFID cookie value. Configuring a ColdFusion server to use Java sessions could not be simpler and only requires two steps. First, you need to modify the settings in ColdFusion Administrator. Figure 5.10 shows the Memory Variables settings page in ColdFusion Administrator. Check both the Use J2EE Session Variables and the Enable Session Variables check boxes. The ColdFusion server may require a restart after you make these changes. Figure 5.10. Memory settings for J2EE session management.Next, insert the following code into Application.cfm to enable session management in your application: <cfapplication name="MXusers" clientmanagement="Yes" sessionmanagement="Yes" setclientcookies="Yes"> ColdFusion will now set the SESSION.SESSIONID variable to jsessionid, as in Figure 5.11. Notice the absence of the SESSION CFID and CFTOKEN variables when J2EE session management is enabled. CFID and CFTOKEN are still present in the CLIENT variable scope. SESSION.SESSIONID now consists of jsessionid, and SESSION.URLTOKEN consists of a combination of CFID, CFTOKEN, and jsessionid. SESSIONID no longer utilizes the variable application name. Figure 5.11. J2EE session management and client management are turned on.Figure 5.12 shows how variables look when you use session management and client management, but not Java sessions. Notice how the SESSIONID is configured with a combination of the application name, CFID, and CFTOKEN. You can test this yourself by using the <cfdump> tag to dump the contents of both the client structure and the session structure. Figure 5.12. Session management and client management are used, but J2EE session management is turned off.Hardware-Based Session ManagementSome hardware load-balancing devices, such as Cisco's LocalDirector, offer sticky management of cookie states. The load balancer works in concert with the Web server to create session-based cookies. These cookies create a session for the user. Both the load balancer and the Web server can manipulate and read them. Some load balancers can operate in "Cookie-Rewrite," "Cookie-Passive," or "Cookie-Insert" modes. In the Cookie-Rewrite mode, the Web server creates the cookie, and the load balancer will rewrite it. Cookie-Passive mode looks for a cookie set by the Web server but will not create a cookie of its own. It attempts to learn the cookie to manage session state. If no cookie is present, Cookie-Passive mode will not depend on a cookie to maintain state. Cookie-Insert mode allows the load balancer to create a cookie and set it on the client. In this mode, the load balancer first looks for a cookie; if no cookie is present, this mode connects to the client and creates a cookie. Some load balancers offer other persistence modes to manage a user session, including Secure Socket Layer (SSL), preferred server, and source. These configurations maintain SESSION-aware sessions and provide secured connections to load-balanced servers. Talk to your network or system administrators about what options are available in your hardware solution in order to determine what makes the most sense for your specific application. Hybrid SolutionsToday's Web sites are complex applications, consisting of many pages and relying on sophisticated techniques to provide content and feature-rich user interfaces. Typically you cannot use one method for managing session state for the Web site, and so the viable solution becomes some combination of the techniques discussed in this chapter. This introduces complexities beyond the focus of this chapter, but I will offer some plausible solutions. Obviously one hybrid solution involves using cookies and CLIENT or SESSION variables in combination to manage session state. Two cookies are stored on the client to identify the user to the server. Other hybrid solutions include using cookies or SESSION variables to identify the user and storing all session information in a centrally located database. A cookie is polled for a user identifier that is used to query the database. This is practical for an e-commerce site, which creates a unique identifier for each user and stores all shopping cart and checkout information in a database. Each time the shopping cart information is requested, the database is queried to populate the information on the page. You can also use J2EE session management on the ColdFusion MX application server and utilize this sessionid to access user information, such as user name and password. Web sites can dynamically push content to users based on their preferences or characteristics, by associating a unique identifier stored in a SESSION variable and relating this to information residing in a database. The potential uses for session state are endless, and every developer will have a preferred method for managing and using state in Web applications. Optimal session-state management in a clustered environment complicates the issue, but you can overcome these difficulties by carefully structuring and applying these techniques in designing your Web site. |