The State of WebObjects
Volume Number: 16
Issue Number: 7
Column Tag: WebObjects
Introduction to EOF and WebObjects: The State
of WebObjects
by Patrick Taylor and Sam Krishna
Before we talk about state management in WebObjects we need to digress a bit because
Steve Jobs has blasted one out the park...
Just before press time, Apple announced that the price of WebObjects was going to be
slashed. As we mentioned last month, WebObjects was designed for the kind of demands
that an Apple Store-like web site might bring to bear, this is not your father's web
application server. Because it is enterprise level software, it has had a
correspondingly enterprise level price - a shocking $50,000 for a single high-end
deployment license and another $1299 for the development tools. At least that was case
until the WWDC keynote where Steve Jobs announced that effective immediately a
combined development and deployment license was going to be sold for $699.
WebObjects is now within the means of practically any web developer - projects
which needed that class of middleware, but couldn't justify the price, are now very
possible. A WebObjects, Mac OS X Server and Frontbase or Openbase combo is now
price competitive with Windows 2000 Server (which includes IIS and does dynamic
publishing via ASP), Visual Studio and SQL Server even when you consider higher
comparable Apple hardware prices. The combination of Linux and one of the growing
number of open source dynamic serving solutions (like Zope) are probably still
cheaper but having the lowest initial price has never been the real reason for choosing
this environment.
Speaking of Reasons for Choosing WebObjects
With the rise of XML, Java Server Pages, Perl/Python/PHP and JDBC, it's quite
common to have people asking "If all of these standards are becoming more popular,
why should anyone bother with WebObjects (especially when it still had it's
transaction limitations based on the price you paid for the license)?" It certainly is
possible to do almost anything that WebObjects can do with some combination of
pre-existing tools and blood, sweat and tears, however, WebObjects manages many
things for you, things you get as part of the standard package.
And one of the most important things is State Management. State management allows the
server to be aware of the interactions by numerous clients each ensconced in their own
session. Since HTTP is normally stateless, both the browser and the server will forget
about client-server interaction as soon as a particular transaction is finished. One it
hands of data from a form, a browser will not remember any values or selections
made. This can be both a hassle and a security problem for e-commerce.
In the previous article when we discussed the request-response loop, we saw how
WebObjects normally uses the URL to manage state over the life of a user's session.
Since an HTTP server naturally assumes that every single request it receives from
remote users is a unique transaction, it becomes necessary to overcome this hurdle.
Normally, the protocol does not allow state to be preserved so it becomes nessary to
piggyback on pre-existing feature transmitted back and forth between the client and
server. Options include maintaining state via the URL, with a cookie, or more rarely a
hidden field in the HTML. As well you can store this session information in the database
to provide for long-term continuity for your users. Or if you have very demanding
applications, you can even use multiple state management strategies. One example
would be a sophisticated e-commerce site using a combination of cookies (to identify a
client), database entries (to maintain history) and hidden fields (to maintain session
integrity without littering the URL). As well, it is also possible to maintain state
without embedding session information using DirectActions.
State management has benefits beyond just providing a "history" between users and the
site. It also allows WebObjects to keep business logic on the server since session
information is preserved, thereby avoiding the security risk of unscrupolous
modification of client-side business logic (the Internet version of the "five digit
discount"). It also provides some security for the user by not embedding sensitive
information (like credit card numbers) in the HTML and through the complex
machine-readable session information (the complex gobbledigook on the URL) makes
it very difficult for anyone to hijack a session from a user.
But why would WebObjects need to piggyback on existing features? Because
they are there. Seriously ... rather than reinventing ways of interacting with
servers and browsers, WebObjects takes the path of least resistance by using
whatever already exists as a standard feature in the most effective way
possible. In a well designed WebObjects application, it is possible to use Lynx
(so long as it supports a recent vintage of the HTML standard) to browse a
page. Unlike Microsoft's "works best with Internet Explorer and/or Windows
approach, WebObjects is philosophically inclusive. Consider the case of
someone who didn't piggyback on existing standards: recently some wide-eyed
and bushy-tailed dot-com announced a new banner ad format that would give
web developers far more flexibility than is avalable with the everpresent
cgi-based animated gif banners. The deadly flaw is that it was based around a
new plug-in. Now maybe the developers had come up with a brilliant way to
get people to download an advertising specific plug-in, but while most users
accept banner ads as an acceptable annoyance or even a necessary evil, not one
person in a hundred is going to download such a plug-in. Take this story as a
cautionary tale, it does not pay to make users download plug-ins, especially if
you're trying to sell them something. So if a strategy that uses built-in
browser or server features is at all practical, stick to it.
Applications, Sessions, Components, Oh My!
The WebObjects engineering team solved the state management problem in a very clean
way. Intrinsically there are three levels a programmer will ever want to store state
within a web application: the global level, the user level, and the page level. Within
WebObjects, this is known as the Application/Session/Component state management
system.
Why is State Management good for developers? WebObjects isn't unique in
providing state management. There are plug-ins for NSAPI and ISAPI can be
used to provide some degree of state management, WebObjects manages to
provide state management even on lowly CGI. By "filling in" the missing
features from one adaptor to the next, WebObjects manages to provide a
consistent target which developers can depend on so that developing an
application which uses CGI isn't different than an application which uses an
ISAPI connection. Besides consistency, this simplifies the process of
transferring applications from one platform to another. For example, you
could do your development on a PowerMac G4 running Mac OS X Server 1.2
with Apache 1.3.12 and then deploy on a SPARC Workstation running Solaris 8
using iPlanet Web Server. Short of changing some configuration settings and
recompiling, very little needs to be modified to make the transition. In future
articles, we'll see how a similar situation plays out with databases and their
adaptors.
For the Application layer, there's a class called WOApplication used to manage the
entire run-loop of a WebObjects application as well as define and access very specific
application-wide behavior. There is a programmer-accessible subclass of
WOApplication simply called Application where you can store, for example, an array
of the New York Times Bestseller List of books that every user can access when they
visit the site. You can also define the behavior for what happens when a user does
something illegal (like invalid form values) and redirect them to a page saying they
need to re-input their information. Because Session and Component (page classes)
derive from and fall under Application, they have access to all features added at the
Application level.
For the Session layer, there's a class called WOSession which lays out the basic
behavior of a user's session with the app. The session represents the complete
lifecycle of a user's interaction with a WebObjects application. The WOSession class
has a subclass called Session which provides programmatic hooks for a developer. This
subclass inherits a collection class called an NSMutableDictionary which can be used to
store information beyond just the instance variables that have already been defined.
For example, the Session can be used to contain a shopping cart with credit card
information, books selected, and other various and sundry items.
In the Component layer, there's a class called WOComponent which manages all of the
general interaction with the request-response loop and the actual hooks into a given
HTML page for direct access to instance variables. You can literally create a new page,
create instance variables for that page, bind the instance variables to the HTML
elements on that page, and, after all that, have a functional - though slightly homely -
web page without having to write much code.
The Component subclass is responsible for interacting with the WebObjects system to
generate all of the HTML on-the-fly for a particular page based on the state of the
information to be displayed and what the developer has instantiated in the Session and
Application objects. The component can ask the session and the application objects for
any particular state that the developer sees fit to access. A Component can be used to
display the detail information and a particular image to represent a book currently
browsed by the user.
What's so amazing about this system is that after you start using it for a while, you'll
wonder why anyone worries that the Web is a stateless system at all.
So many different ways of managing state
We have already have explained some of the mechanics of storing session information
in the previous article on the request/response loop. A WebObjects application by
default stores the session ID in the URL automatically along with the component ID.
Some developers (and users) don't care for the long URLs with machine readable
session data partly because they are ugly, but as the least invasive method to maintain
state.
Cookies are a useful way of tracking transactions but can be problematic. Not everyone
enables cookies in their browsers so you may turn people away if you make your site
cookie dependent. It is possible to request that the Session object store the Session ID
in a cookie instead of in the URL (which it does by default). You can send the message to
your Session object like this:
In Java:
session().setStoresIDsInCookies(true);
In Objective-C:
[[self session] setStoresIDsInCookies:YES];
This code will force the session to stuff all session ID and element ID information into a
pair of cookies named "wosid" and "woinst", respectively. These two cookies keep all
session ID and element ID information that normally would be in the URL and keeps too
much of the session "noise" from littering the URL.
The basic way to use a cookie for state management is by simply instantiating a
WOCookie object.
In Java:
WOCookie.cookieWithName("someName")
In Objective-C:
[WOCookie cookieWithName:@"someName"]
Then it is possible to set the name, domain information, expiration information, path
information (if applicable), and finally its value (the actual state information you
want to store).
To do this, you would probably want to override public void appendToResponse() in
your Component subclass. You would do this in Objective-C with
appendToResponse:inContext:
The sequence would look something like this:
In Java:
public void appendToResponse(WOResponse response, WOContext context) {
// Assume for sake of example that we're generating cookie for
first time
// Also assume that passwordValue is an instance variable prebound
to some secure text field that the Component knows about
// Set expiration date for one month from now
NSGregorianDate oneMonthFromToday = new NSGregorianDate();
oneMonthFromToday =
oneMonthFromToday.dateByAddingGregorianUnits(0, 1, 0, 0 , 0, 0);
WOCookie cookie = WOCookie.cookieWithName("password",
passwordValue, null, "www.mydomain.com", oneMonthFromToday, false);
response.addCookie(cookie);
return;
}
In Objective-C:
- (void)appendToResponse:(WOResponse *)response inContext:(WOContext
*)context
// Assume for sake of example that we're generating cookie for
first time
// Also assume that passwordValue is an instance variable prebound
to some secure text field that the Component knows about
// Set expiration date for one month from now
NSCalendarDate *oneMonthFromToday = [NSCalendarDate calendarDate];
WOCookie *cookie = nil;
oneMonthFromToday = [oneMonthFromToday dateByAddingYears:0
months:1 days:0 hours:0 minutes:0 seconds:0];
cookie = [WOCookie cookieWithName:@"password" value:passwordValue
path:nil domain:@"www.mydomain.com" expires:oneMonthFromToday
isSecure:NO];
[response.addCookie:cookie];
return;
}
Erratum In the historical list of WebObjects team members, we gave last
month some people were accidentally left out. The WebObjects 3.5 and 4.0
teams included Laurent Romontianu and Patrice Gautier. Charles Lloyd left the
team after WO 4.0 (and eventually ended up at Ariba with Craig Federighi).
Direct Actions and State Management without Session Information
There is another type of URL state management known as a Direct Action, a type of
WebObject class that can track information without a session, taking state from one
component and passing it to another component via the URL. For example, a Direct
Action object can be used to track the user ID and the password of a user in the URL. All
the state information will be passed through the URL. The advantage of this is that you
have the ability to bookmark pages within a WebObjects application (since version 4
of WebObjects). However, the downside of this is that a developer now has to think
about the implications of not having a session and having to manage state from
component to component.
All Direct Action methods should be defined in the WODirectAction subclass called
DirectAction. It is common convention that all the action methods end with the word
"Action" in order to be distinguished as direct action methods. As well , when using
Direct Actions, debugging is simplified if you name your WOTextFields and other input
elements with meangingful names. Otherwise, the WORequest object will assign
arbitrary names for your input elements and make it practically impossible for you to
access your input variables unless you choose to conform to their arbitrary naming
style (i.e. using latent ESP while programming direct actions and using the method
takeValuesFromRequest() or in Objective-C - takeValuesFromRequest:inContext: to
access form values from a WORequest.)
End of Session
Having received a taste of WebObjects in the last two articles, it's time to jump to
something different. Next month, we'll go back to EOF and look at EOModelling. Is it
relational or is it object-oriented? It's the entity-relational system that tastes great
and is less filling.