Welcome!

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?

Background
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.

Limitations
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.
Conclusion
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
    Every organization is facing their own Digital Transformation as they attempt to stay ahead of the competition, or worse, just keep up. Each new opportunity, whether embracing machine learning, IoT, or a cloud migration, seems to bring new development, deployment, and management models. The results are more diverse and federated computing models than any time in our history.
    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...
    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...
    Dion Hinchcliffe is an internationally recognized digital expert, bestselling book author, frequent keynote speaker, analyst, futurist, and transformation expert based in Washington, DC. He is currently Chief Strategy Officer at the industry-leading digital strategy and online community solutions firm, 7Summits.
    Digital Transformation is much more than a buzzword. The radical shift to digital mechanisms for almost every process is evident across all industries and verticals. This is often especially true in financial services, where the legacy environment is many times unable to keep up with the rapidly shifting demands of the consumer. The constant pressure to provide complete, omnichannel delivery of customer-facing solutions to meet both regulatory and customer demands is putting enormous pressure on...
    IoT is rapidly becoming mainstream as more and more investments are made into the platforms and technology. As this movement continues to expand and gain momentum it creates a massive wall of noise that can be difficult to sift through. Unfortunately, this inevitably makes IoT less approachable for people to get started with and can hamper efforts to integrate this key technology into your own portfolio. There are so many connected products already in place today with many hundreds more on the h...
    The standardization of container runtimes and images has sparked the creation of an almost overwhelming number of new open source projects that build on and otherwise work with these specifications. Of course, there's Kubernetes, which orchestrates and manages collections of containers. It was one of the first and best-known examples of projects that make containers truly useful for production use. However, more recently, the container ecosystem has truly exploded. A service mesh like Istio addr...
    Digital Transformation: Preparing Cloud & IoT Security for the Age of Artificial Intelligence. As automation and artificial intelligence (AI) power solution development and delivery, many businesses need to build backend cloud capabilities. Well-poised organizations, marketing smart devices with AI and BlockChain capabilities prepare to refine compliance and regulatory capabilities in 2018. Volumes of health, financial, technical and privacy data, along with tightening compliance requirements by...
    Charles Araujo is an industry analyst, internationally recognized authority on the Digital Enterprise and author of The Quantum Age of IT: Why Everything You Know About IT is About to Change. As Principal Analyst with Intellyx, he writes, speaks and advises organizations on how to navigate through this time of disruption. He is also the founder of The Institute for Digital Transformation and a sought after keynote speaker. He has been a regular contributor to both InformationWeek and CIO Insight...
    Andrew Keys is Co-Founder of ConsenSys Enterprise. He comes to ConsenSys Enterprise with capital markets, technology and entrepreneurial experience. Previously, he worked for UBS investment bank in equities analysis. Later, he was responsible for the creation and distribution of life settlement products to hedge funds and investment banks. After, he co-founded a revenue cycle management company where he learned about Bitcoin and eventually Ethereal. Andrew's role at ConsenSys Enterprise is a mul...