There are a number of important factors to consider when evaluating what sort of
state management to use for your application. Will the application need to be
distributed, or does it require bookmarkable URLs ?
Will there be multi-page interview processes ?
Can you require your users to have client side cookies turned on ? Moto provides
built in support for four state management systems :
Stateless : A stateless application requires no information to be
maintained in a user's session from page view to page view. This would be the case for static
web sites or web sites whose content does not change based upon who is viewing it.
Client-Side : An application that requires knowledge only of who is
logged in in order to display information is a good candidate for client side
state management. When a user connects to the application a client
side cookie is deposited on the user's browser identifying the Session. Subsequent page executions
may retrieve information from the Session identified by the cookie.
Client side state management is preferable for bookmarkable applications but
may suffer from time travel problems and may not be used by browsers that have
disabled cookies.
Server-Side : Instead of setting a cookie on the user's browser to identify the
Session a state id is included in every link or form submission by using
a special state variable sid. The value of this variable changes from page view
to page view and identifies not only the Session but the previous State. Through this
mechanism state variables are
automatically persisted across page views without sufferring
from time travel problems. However, since the sid variable is included in every link
bookmarking pages of the application is not recommended as the Session may
have expired by the time the bookmark is used.
Hybrid Server-Side - Client-Side : In the hybrid system both a
client-side cookie and the sid variable are used. A correctly written application can default to
Server-Side state management if client-side cookies are turned off. Conversly, only application
areas that require the power of parent state identification such as multi-page interview
processes need to use the sid. Other bookmarkable pages will be able to identify the correct
Session via the cookie.
The type of State management your application uses is determined through a combination of
httpd.conf configuration options and coding practices used in writing the application. The
following diagram provides a graphical overview of Moto's state management subsystem.
To identify your application as
Stateless include the following configuration
option in your httpd.conf:
<Application Name>Option Session.Disable TRUE
| |
Or when running unter the interpreter:
<Location /~dhakim/docs>
MotoOption ExamplesPath /Users/dhakim/Workarea/content/moto/examples
MotoOption Session.Disable TRUE
</Location>
| |
What this will do is turn off session creation and tracking within the application (or
application area) where the directive applies.
Stateless is somewhat of misnomer because States are still created with every pageview.
What Stateless applications really lack are Sessions. Calling getSession() on the current
state will always return null
Server-Side state management is available to an application whenever Sessions are enabled.
Using it involves coding in such a way that a special State variable sid is passed
in every hypertext link, location redirect, and form submission.
Every time a user visits a page when Sessions are enabled Moto attempts to discover whether
this user already
has a Session. A Session in moto doesn't mean that the user is necessarily logged
in, just that the user has been interacting with the application on previous page
views and has browsed to this page by following links on those pages.
When Moto cannot determine the 'current Session' a new Session is created.
States are created on every page view. If Sessions are enabled then when a
State is created it is bound to the current Session. The way moto discovers the current Session
under Server-Side state management is by looking at a State variable named 'sid'.
If it is present in the current State then
Moto can derive the current Session from it and bind the newly created State to that Session.
The 'sid' variable's value identifies a State. It is used to identify the parent State
of the currently executing page. If it is not present then Moto assumes there was no
'parent State'
and spawns a new Session. Otherwise the parent State is retrieved and the Session it was bound
to identified. The newly created State is then bound to that same Session. The process
perpetuates itself when, on the currently executing page, the new State's own id
(returned by the method getSID()) is
included in dynamically generated links.
<a href="http://www.webcodex.com/index.moto?sid=$(getState().getSID())>
| |
location redirects
$(sendRedirect("index.moto?sid="+getState().getSID()+))
| |
and form submission by passing the sid as a hidden variable
<form action="index.moto" method="post">
<input type="hidden" name="sid" value="$(getState().getSID())>
<input type="submit" value="click me">
</form>
| |
When the link is followed, the form submitted, or the browser redirected, Moto
recognizes the sid, discovers the State it refers to, and from that state discovers
the Session.
This mechanism is used to provide continuity to the user experience not only through Session
identification but by persisting the values of State variable between page views.
State variables in the parent state that do not begin with an underscore '_' are copied
into the current state. So if the state variable 'foo'
was present in the parent state than it is present in the current state with the
same value. It's value changes only when a new value is passed for it. Thus in the login page
example from the previous section
<form name="login form" method="post" action="do_login.moto">
User: <input type="input" name="user">
Password: <input type="password" name="password">
<input type="submit" name="_action" value="login">
</form>
| |
$switch(getValue('_action',"login"))
$case(login)
$if(getValue("user","") eq "bob" && getValue("password","") eq "bobpw")
$do(redirect("index.moto?sid="+getState().getSID()))
$else
$do(redirect("login.moto?sid="+getState().getSID()+"&_err=Password+rejected"))
$endif
$endswitch
| |
The value of the State variable "user" could be retrieved on all subsequent page views. This
is handy because it does not require the extra work of inserting it into the Session or
promoting its value. There are further benefits which are discussed in detail in subsequent
sections.
Continuing from the previous example of login and do_login pages, a user has just entered a username (bob) and password on the login page, pressed submit taking
him to the do_login page where his account was validated and his browser redirected to an index
page.
If this were an application that made use of Server-Side state management then on the index
page we could write
Welcome $(getState("user"))
| |
And it will print
We could not however print
You clicked $(getState("_action"))
| |
which would result in
Since '_action' began with an underscore it's value is not copied to subsequent
states and thus available to subsequent page views.
Form submissions account for the way Moto handles State variables with names that
begin with '_'.
In general:
- Most values submitted in forms are only needed immediately after the form submission
- Values from form submissions may be quite large and as such expensive to copy from page view to
page view.
Consider a web application that is used for maintaining an employee directory. One
could imagine a page containing a form that solicits data for a new hire which,
upon submission, is inserted into a database. Let's suppose this form prompts for the
employee name, address, and a photo that can be uploaded. All this data will be
submitted to moto as state variables. The page that handles this submission will
insert it into the database and then return to the index where upon the user can
choose to add another employee or browse through other application pages. Either
way this data is unnecessary, and, with regard to the employee photo, potentially
quite large. We would definitely not want it copied with every page view.
When the Session.Disable option is present, all state variables are free'd after page execution.
In Stateless applications the variable sid is meaningless and not prefixing state variables with
an underscore has no effect. They all go away at the end of the page view to which they were
submitted.
But why copy state variables, why not just keep one value around for every state variable
name used in a user's session? The answer to this question requires some
background...
When building web applications it is useful to diagram them out in Application Flow
Diagrams. These
diagrams map out the pages of a web application with arrows between them
illustrating the links that a user could follow to transition from page to page. A simple
application where users may view or modify a 'user profile' might be diagrammed as follows:
A user's activities in a web application can be viewed as a traversal of
the transition diagram.
State |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
Parent State |
none |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
Page |
1 |
2 |
1 |
2 |
3 |
4 |
5 |
4 |
For the traversal above the user forgot his password the first time and was sent
back to the login page to start again. This is clear by comparing the pages the
user visited to the transitions in the app flow diagram.
The user then went on to the index page, viewed his profile, and successfully edited it.
But what could we conclude happened if we then saw
State |
9 |
10 |
11 |
12 |
Parent State |
1 |
9 |
10 |
8 |
Page |
1 |
2 |
3 |
5 |
The user backed up to the login page, openned up a new browser window and logged in as
(potentially) a different user in
it. Then went back to the original window and followed the edit profile link! If the only mechanism
being used to determine what user profile to edit was based upon an identifier tied to the Session
then the user whose profile will be edited in the first window will
be the user who is logged in in the second. Another way to look at this is that the actions the
user took by opening up a second window and logging in as a different user affected the
behavior of a link that was generated previously. This is known as a time travel problem.
Implications of this sort of problem are
- Using the back button on applications that make extensive use of Session objects
or (or Client-Side State management) may be unsafe.
- Following links by openning them up in new windows may effect the ability to follow links in
other open windows.
- Session objects are tricky things that must be used with care.
Use of Session objects is the most common cause of unexpected time travel problems. If the 'current user'
in the application diagrammed above was stored as a persistent State variable there would be no
confusion as to which 'user' was being edited in which window. If you believe that the above example
is somewhat contrived consider the role of an HR administrator who may be responsible for updating
the profiles of multiple employees ... this sort of thing happens all the time.
One area where this comes up a lot is in relation to multi page interview processes. In these sorts
of cases users are asked to enter information through a series of interactions with a web based
application. In general the information is persisted in memory until it is ready to be stored. An
example of this might be web page building application where fields of a web page template get filled
in over successive page views
State |
1 |
2 |
3 |
4 |
5 |
6 |
7 |
Parent State |
none |
1 |
2 |
3 |
4 |
5 |
6 |
Page |
1 |
2 |
3 |
2 |
3 |
2 |
3 |
In the above traversal the user has selected a page to edit and is entering data into each of it's
fields
State |
8 |
9 |
10 |
Parent State |
1 |
7 |
9 |
Page |
2 |
2 |
1 |
It seems as though the user has gone back to the directory screen by using the back button on
his browser and selected a new page in a new browser window (perhaps to copy some information out
of that page into the page he's working on). Back in his working window he then alters the field and
then saves the page. But which page did he alter? If the data for a page is stored in a
Session variable named 'page' for instance then the very act of opening up a second page in another
window has undone all the users work.
So now that we are aware of the hazards of Session objects because of time travel problems, one might
ask why we should ever consider a State Management methodology where one is forced to use Sessions
to persist data between page views. The biggest reason may be bookmarkability. Including
the state variable sid in every URL means that bookmarking generated URLs may be useless or
potentially dangerous if the link is distributed to others.
With pure Client-Side state management a Cookie named ssid is deposited on a users browser after
his first page view against an application. This Cookie does nothing more than identifies the
Session. If on subsequent page views Moto retrieves this Cookie then Moto can discover the current Session.
If the cookie is not included within the HTTP GET or POST then Moto assumes there is no current Session
and spawns a new one.
You can get this behavior by including the option
<Application Name>Option Session.SetSSIDCookie TRUE
| |
Or if using the interpreter use the following moto option in the location you want it applied:
MotoOption Session.SetSSIDCookie TRUE
| |
The lifetime of the ssid cookie is the same as the lifetime of the Session which is specified in
seconds via the Moto option
MotoOption Session.Timeout 60
| |
The default value for this option is 1800.
The downsides of this system apart from the time travel phenomina we've already looked at are :
- Some users may have client side cookies disabled
- It becomes impossible to maintain multiple Sessions at once against the same application under
one browser (sometimes this makes testing difficult)
- State variables are not persisted meaning that all data for a users session must be promoted and stored
in the Session
The best State management systems for web applications are hybrid systems. They are also the most complex to
implement. The way a hybrid system is implemented in Moto is by including the Session.SetSSIDCookie option in
your httpd.conf file AND including the sid variable on all pages that could benefit from it and do not need to
be bookmarked OR in every generated URL in cases where client side cookies are turned off.
You can discover whether cookies are working or not by trying to getCookie("ssid") on a page that expects it.
If it is not found, include the State variable sid on all generated URLs.
Simply by enabling the SetSSIDCookie option on applications that are basically Server-Side State managed
adds an extra level of security. If the Session identified by the sid and the Session identified by the ssid do
not match then a new session is spawned.
Copyright © 2000 - 2003 David Hakim