You will be redirected in 30 seconds or close now.

ColdFusion Authors: Yakov Fain, Jeremy Geelan, Maureen O'Gara, Nancy Y. Nee, Tad Anderson

Related Topics: ColdFusion

ColdFusion: Article

Controlling Page Sequencing with an Event-Driven State Machine

Write more robust, secure, and reusable code

One of the persistent headaches of Web application development is the developer's inability to control the sequence in which application "pages" are executed. The back button, refresh, bookmarks, double-clicks, and browsers' inconsistent page-caching behavior all create special problems that result in everything from "variable undefined" errors to duplicate data records.

To prevent these errors, developers have often resorted to passing around flag variables and status variables that make code hard to maintain over time. In this article, I explore a way to gain positive control of application page flow in server-side code, resulting in code that is more robust, more secure, and more reusable.

Controlling Page Sequencing with an Event-Driven State Machine
It has been said that the difference between an in-house application and commercial software is that for an in-house application, there has to be at least one right way to use the system. For commercial software, there can be no wrong ways to use it. The same can be said of intranet vs. Internet applications. This presents tremendous challenges in the Web environment, where the developer has little control over the sequence of user actions. Think of your application as consisting of all the code contained in individual pages (or fuseactions, if you're a Fusebox developer). Thanks to the back button, double-clicks, and bookmarks (not to mention spiders and URL scanning tools), users can run (request) all those little pieces of code in any order they choose. Is your code ready for this?

In the early days of the Web, much ado was made about the performance and scalability that result from the stateless nature of HTTP. Unlike complicated client-server programming, you don't have to worry about maintaining connections to a server and managing the flow of control through an application. Indeed, this model works very well for Web sites, where pages may contain some dynamic content but are designed to be accessed in any sequence. However, dynamic Web applications often require that "pages" be visited in a particular order, such as an order entry wizard, shopping cart checkout, or online funds transfer procedure. In these applications, the intended page sequencing is often enforced only by the various HREFs and form actions. Few applications explicitly enforce the intended page sequencing. Thus, although there is one right way to use the application using the links and buttons on each page, lots of unintended sequences are also possible. How can we control page sequencing when the user can enter an arbitrary URL in the address bar?

The fundamental problem is the URL. Its very name, Universal Resource Locator, suggests that a URL was designed to serve as an address to a particular object, such as a static Web page. Used this way, it does not matter in what order the user clicks on URLs. However, Web applications use URLs both to retrieve objects and to initiate actions via POST requests, such as placing an order or running a search. When used to initiate actions, sequencing is very important. Suppose, for example, that you are writing a banking application that allows a user to transfer funds between accounts. This may involve a wizard in which the user selects the accounts for the transfer, enters a transfer date and amount, and submits a final confirmation. You must make sure that the user progresses through the wizard pages in order and cannot go back in the sequence to alter the transaction in progress except via the paths you define. Unfortunately, neither HTTP nor HTML has a way to enforce the sequence in which users initiate actions through URLs. The back, refresh, and bookmark features make it all too easy for users to deviate from the intended sequencing.

There are various ways to handle these issues that arise with browser-based applications. You can open the application in a new window and disable browser features. You can write a Javascript event handler to disable the submit button after it has been clicked. You can look at HTTP_REFERER to ensure a form post comes from the expected page. But all these methods rely on browser features and are therefore not secure and/or are browser version specific. Some users disable Javascript, and proxy tools such as Paros make it all too easy to modify any part of a Web request including the HTTP_REFERER. Thus, you can't rely on Javascript or HTTP_REFERER to enforce page sequencing.

Ideally, we'd like a server-side solution to these problems that can be implemented in a modular fashion. We should not have to put an include file on every page or set custom flag variables for each restricted page sequence. In additon we'd like the ability to change in one place the way that the entire application handles the back button, and so forth. To do this, we need a framework that provides what the Web protocols left out: the ability to control the flow of pages. I'll call it application flow control.

Moving From Page Centric to Event Driven
To build a server-side flow control engine, we first need to rethink the page-centric model for Web applications. In the page-centric model, every form typically has its own action page that runs code to save the form data from the previous page as well as display the next page.

A Web application has more in common with a traditional client-server application than with a static Web site. A Web application, similar to a client-server application, typically consists of forms populated with database data. Forms have action buttons and familiar controls such as check boxes and text input fields. More important, a Web application, similar to a client-server app, is event driven. Each click on a submit button or hyperlink is an event that triggers some action code and possibly a transition to another form on the server. The address in the URL points to the event handler code.

However, here we come back to the key difference: In a client-server app, such as a Visual Basic desktop application, the user can only interact with the application's desktop user interface in the prescribed ways. The user cannot go directly to a different form in the application except by triggering a valid event on the current form. This is because the application is always aware of its state. A VB form only exposes itself to the user if, by interacting with the UI, the user triggers the events required to display the form. In a Web application, however, the user can bypass the UI and change the program state by entering a new URL (via the back button, a bookmark, etc.). To prevent a Web user from doing this, we must move the flow control logic away from the browser and back to the server. We would do this using a Web state machine.

(Re-) Introducing the Finite State Machine
Back in the marbled halls of your alma mater's computer science department, you probably learned about the finite state machine. In a nutshell, a state machine models program flow as a collection of states and state transitions that occur in response to events. At the heart of a state machine is a controller that is notified of events when they occur, executes some action code corresponding to the event (an event handler), and determines the next state based on the state transition map. This can be represented with a simple formula:

current state + event = action code + next state

In a Web application, we can think of each HTML page as a state. Each button click or hyperlink is an event that causes (a) some action code to run (the event handler) and (b) the browser to transition to another state. On the server, the Web state machine remembers what state (or view) the application is in so that when a new request (event) comes in, the state machine can determine whether the event is valid for the current state. If this is the case, the state machine runs the event handler and transitions to the next state defined for the event.

Designing the Web State Machine
The Model-View-Controller (MVC) and Front Controller design patterns provide a useful foundation for our state machine. In the Front Controller pattern, used by popular application frameworks such as Fusebox and Mach-II, all requests come through a single "page," such as index.cfm. This dispatches each request based on a URL parameter. Our state machine implements this idea with a "flow controller" module that keeps track of the user's current state (view) in the Session scope. It then uses the combination of current state and the event name in the URL to run the appropriate action code and include the view code for the next state.

When using a state machine, URLs no longer contain any information that can be used to go to a specific page. Instead, each URL contains only the address of the controller and an event name:

<A HREF="flowctl.cfm?event=addUser">

If no event is specified in the URL, the flow controller will simply show the view for the current state. If an event is not valid in the current state, the flow controller will send an error message and abort the request.

Our simple Web state machine consists of two files: a flow controller and a flow map. Flow_map.cfm (see Listing 1) maps each event to the corresponding action code and next state. The flow map is the heart of the state machine and will look very familiar to Fusebox developers. Unlike Fusebox, however, there are two layers of CFSWITCH/CFCASE statements. The outer switch selects the current state. For each valid state, a nested inner switch selects the current event. Each case in the inner switch includes the event handler (model) code for the event and assigns the name of the next state to Variables.nextState, which is used by the flow controller to update the state machine and include the corresponding view code.

Flowctl.cfm (see Listing 2) is the front controller that runs in response to each request and maintains the current state information. After setting up each request, the flow controller simply includes the flow map using <CFINCLUDE>. If the flow map sets a next state, the controller advances the state machine to the next state and includes the corresponding view code. This simple controller assumes a one-to-one correspondence between the name of each state and the name of the corresponding .cfm file to display the view.

How the Web State Machine Works
Let's follow a typical flow using the flow map for the order entry wizard shown in Listing 1. The example code uses the sample Northwind database that comes with Microsoft Access and SQL Server:

1.  The user enters the flow by going to the URL flowctl.cfm. The controller creates a structure "sFlowData" in the Session scope used to hold the state machine's internal variables. In addition, the controller creates Session.sUserData and a reference to it named "thisFlow", which is used by the model and view pages to hold data the user enters. The controller sets the start state to the default "init" and the current event to the default "startFlow". The controller includes the flow map, which runs the initialization code in model/initFlow.cfm. The flow map sets the next state to "choose_customer" so the flow controller advances the state machine and includes the view code from views/choose_customer.cfm (see Listing 3).

2.  On the "Choose Customer" view, the user chooses a customer from the select box and clicks the next button. Note that the form action in Listing 3 points to flowctl.cfm, and the form uses a submit button named "event". The text of the button is "next", so the flow controller gets a request with the variable Form.event = next. The controller includes the flow map, which in turn runs the action code model/act_saveCustomerSelection.cfm and sets the next state to "choose_products".

3.  On the product selection screen (see Listing 4), the user has three choices: changeCustomer, selectProduct, or finish. You can see this by either looking at the events defined in the submit buttons or looking at the flow map in the switch nested inside the "choose_products" case. In the flow map, note that no matter which button the user presses in the "choose_products" state, the form data will be saved first. This is because the flow map includes the model code to save the form before looking at the event. This is where the event-driven model really shines. In a page- or action-centric application, you would have to include the code to save the form in three different places. However, the event-driven state map neatly associates the code to save form data with the view that contains the form.

Rules for Using the Web State Machine
To implement this Web state machine example using the flow controller, your application must follow a few simple rules:

  1. All HREFs and form actions must point to flowctl.cfm?event=event_name. All form actions must point to flowctl.cfm and must specify the event name either in the form action URL or using a named submit button as in the example code. If an event is invalid for the current state, the controller writes an error message and aborts the request. If no event is present, the controller includes the view code for the current state.
  2. Put your initialization code in the "startFlow" event handler for the "init" state in the flow map. This will run once when the user first enters the flow by pointing to flowctl.cfm.
  3. To reinitialize a flow and start over, use the URL "flowctl.cfm?event=startFlow".
  4. Keep your action code separate from your view code. I like to think of it in terms of the default color-coding used by ColdFusion Studio. View code should contain HTML only, except for <CFOUTPUT> tags used to build the view and perhaps "view helper" queries to populate list boxes. Model code included for each event should contain CF tags only (red code).
  5. The example controller includes view code for each state based on the state name. You can change the naming convention on the last line of the flow controller.
  6. Use the variable "thisFlow" to persist form fields and other data used throughout a sequence of related pages (one page flow). This is especially powerful in more complicated controllers in which you have multiple flows. When you exit one flow and enter another, data from the previous flow is automatically cleared.
For more help using the example state machine, you can download all files in the example application from www.turbomanage.com.

How Is a Web State Machine Different?
The Web state machine has much in common with frameworks that make use of the MVC design pattern and a front controller. The key difference in the Web state machine is the use of a stateful flow controller to enforce the sequence in which actions (or events) may be executed. Compare the earlier formula for a state machine with this formula implemented by stateless front controllers:

Event (or fuseaction) = action code + next state (view)

This formula omits consideration of current state on the left side. This is why a Fusebox map has only one layer with a case for each fuseaction, whereas the state machine flow map has nested switch statements. This way, events are available only within certain states. One further note: The term "event driven" does not necessarily imply the use of a state machine. Mach-II is an event-driven framework, but this term is used to describe the way it uses event listeners to decouple events from the code that executes in response to events.

Seeing the Invisible: Benefits of Using a Web State Machine
I first deployed a Web state machine to secure an application in which there were many possible entry and exit paths from every page. Over time, this had led to a proliferation of flag variables getting passed around between pages. These were used to keep track of where the user had been to save the correct form data from the preceding page. In all such applications there is a sort of map that indicates which pages lead to where, but the map is hidden in the URLs and form actions buried in all the pages.

First, the primary benefit of using a flow controller such as the one presented here is that it provides a single place to see how every page leads to every other page. Implicit dependencies that are hidden throughout your pages become explicit and visible in the flow map. You can easily search here to find all the events that lead to a given state, as well as all the events that lead from it. This makes it much easier to maintain applications with complex user interfaces. By refactoring your application into model code, view code, and a flow controller, you eliminate all the flag variables and conditional logic to track page sequencing. Your pages become more loosely coupled, and this promotes readability and reuse.

Second, a stateful flow controller improves application security by preventing users from executing pages out of the intended sequence. This does not eliminate the need for parameter validation. However, it does eliminate a major development hassle by giving the developer positive control of the sequence in which pages are visited and actions are executed.

The flow controller presented here is intentionally simplified and suffers from the following three key limitations that will be addressed in future articles:

  1. It provides no way to enable the back button. When a user goes back to a previous "page," the browser retrieves the earlier page from cache unless you've turned off caching with one of the HTTP headers. Because the browser doesn't send a request to the server, your application is unaware that a transition to a previous state has taken place and will not be expecting the events from the previous state. Thus, use of the back button results in an error message. The refresh button suffers from the same problem. In a future article, we'll look at how to make the flow controller smart enough to handle these (and double submits) gracefully.
  2. For code brevity, the example code uses Session variables, ignoring for now any locking issues. These are important, especially in pre-MX versions and when considering double submits. The subject will have to be addressed in a future article.
  3. Likewise, Session variables don't work across multiple CF servers in a Web farm. If this is critical, you can run CFMX Enterprise using J2EE Session variables in a J2EE app server that supports automatic mirroring of the Session scope. Pre-MX users can download <CF_MirrorState> from www.turbomanage.com. This tag lets you choose between Session and Client variables at runtime and removes the need to lock variables throughout your code.
The event-driven state machine is a good fit for complex Web applications that require decoupled flow of control between views because it models the reality that modern Web applications are not stateless like the underlying Web protocols. Refactoring your code to utilize a Web state machine will help you to write more robust, secure, and reusable code.

For Further Reading

  • A State Machine Engine for Web MVC: www.uidesign.net/Articles/Papers/AStateMachineEngineforWeb.html
  • Open Source State Machines for User Interfaces Written in Java: www.manageability.org/blog/stuff/ open-source-statemachine-for-user-interfaces-written-in-java/view
  • More Stories By David Chandler

    David Chandler is a senior ColdFusion developer with Digital Insight in Atlanta, GA and the author of Running a Perfect Web Site (Que, 1995). Chandler is the creator of Turbomanage, a metadata-driven framework for ColdFusion applications, and holds a patent on a method of organizing hierarchical data in a relational database.

    Comments (1)

    Share your thoughts on this story.

    Add your comment
    You must be signed in to add a comment. Sign-in | Register

    In accordance with our Comment Policy, we encourage comments that are on topic, relevant and to-the-point. We will remove comments that include profanity, personal attacks, racial slurs, threats of violence, or other inappropriate material that violates our Terms and Conditions, and will block users who make repeated violations. We ask all readers to expect diversity of opinion and to treat one another with dignity and respect.

    IoT & Smart Cities Stories
    The platform combines the strengths of Singtel's extensive, intelligent network capabilities with Microsoft's cloud expertise to create a unique solution that sets new standards for IoT applications," said Mr Diomedes Kastanis, Head of IoT at Singtel. "Our solution provides speed, transparency and flexibility, paving the way for a more pervasive use of IoT to accelerate enterprises' digitalisation efforts. AI-powered intelligent connectivity over Microsoft Azure will be the fastest connected pat...
    There are many examples of disruption in consumer space – Uber disrupting the cab industry, Airbnb disrupting the hospitality industry and so on; but have you wondered who is disrupting support and operations? AISERA helps make businesses and customers successful by offering consumer-like user experience for support and operations. We have built the world’s first AI-driven IT / HR / Cloud / Customer Support and Operations solution.
    Codete accelerates their clients growth through technological expertise and experience. Codite team works with organizations to meet the challenges that digitalization presents. Their clients include digital start-ups as well as established enterprises in the IT industry. To stay competitive in a highly innovative IT industry, strong R&D departments and bold spin-off initiatives is a must. Codete Data Science and Software Architects teams help corporate clients to stay up to date with the mod...
    At CloudEXPO Silicon Valley, June 24-26, 2019, Digital Transformation (DX) is a major focus with expanded DevOpsSUMMIT and FinTechEXPO programs within the DXWorldEXPO agenda. Successful transformation requires a laser focus on being data-driven and on using all the tools available that enable transformation if they plan to survive over the long term. A total of 88% of Fortune 500 companies from a generation ago are now out of business. Only 12% still survive. Similar percentages are found throug...
    Druva is the global leader in Cloud Data Protection and Management, delivering the industry's first data management-as-a-service solution that aggregates data from endpoints, servers and cloud applications and leverages the public cloud to offer a single pane of glass to enable data protection, governance and intelligence-dramatically increasing the availability and visibility of business critical information, while reducing the risk, cost and complexity of managing and protecting it. Druva's...
    BMC has unmatched experience in IT management, supporting 92 of the Forbes Global 100, and earning recognition as an ITSM Gartner Magic Quadrant Leader for five years running. Our solutions offer speed, agility, and efficiency to tackle business challenges in the areas of service management, automation, operations, and the mainframe.
    The Jevons Paradox suggests that when technological advances increase efficiency of a resource, it results in an overall increase in consumption. Writing on the increased use of coal as a result of technological improvements, 19th-century economist William Stanley Jevons found that these improvements led to the development of new ways to utilize coal. In his session at 19th Cloud Expo, Mark Thiele, Chief Strategy Officer for Apcera, compared the Jevons Paradox to modern-day enterprise IT, examin...
    With 10 simultaneous tracks, keynotes, general sessions and targeted breakout classes, @CloudEXPO and DXWorldEXPO are two of the most important technology events of the year. Since its launch over eight years ago, @CloudEXPO and DXWorldEXPO have presented a rock star faculty as well as showcased hundreds of sponsors and exhibitors! In this blog post, we provide 7 tips on how, as part of our world-class faculty, you can deliver one of the most popular sessions at our events. But before reading...
    DSR is a supplier of project management, consultancy services and IT solutions that increase effectiveness of a company's operations in the production sector. The company combines in-depth knowledge of international companies with expert knowledge utilising IT tools that support manufacturing and distribution processes. DSR ensures optimization and integration of internal processes which is necessary for companies to grow rapidly. The rapid growth is possible thanks, to specialized services an...
    At CloudEXPO Silicon Valley, June 24-26, 2019, Digital Transformation (DX) is a major focus with expanded DevOpsSUMMIT and FinTechEXPO programs within the DXWorldEXPO agenda. Successful transformation requires a laser focus on being data-driven and on using all the tools available that enable transformation if they plan to survive over the long term. A total of 88% of Fortune 500 companies from a generation ago are now out of business. Only 12% still survive. Similar percentages are found throug...