There’s a fundamental design principle with web-based applications that constantly amazes me people forget. It’s a simple one, too.
Web applications are inherently stateless
That’s it. Not hard to remember, is it? What this means is simple as well: in a web-based application, the server can not be certain of what state the client is in. Maybe it’s been closed. Maybe the user went back in the history to another page (and remember, even if you say that the page shouldn’t be cached, the browser can ignore that and cache it anyway). Maybe they jumped straight to the page somehow without going through your careful, intricately laid out page flow. Maybe they double-clicked on the link, thus resulting in two requests being sent off because the browser is too bloody stupid to realise when it’s already processing a request. Heck, there’s even a slight chance that they’re exactly where you thought they were and behaving nicely (though I’d believe that when I see it, thank you very much).
What this means is that a robust web-application needs to protect itself, in some fashion, from those annoying people called “users”. Remember: users are the source of all bugs; if software wasn’t used, we’d never notice any defects. Of course, we’d never get any value out of it, but that’s besides the point… 😉
Anyway, there’s three basic approaches to protecting your web apps. The first is the most common one: ignore it. It’s the user’s problem, after all; let them sort it out. This isn’t even a bad strategy a lot of the time, especially if you supplement it with a little amount of protection to send the user to some known state in the event of an error. If your web app is essentially read-only, there is little reason why you can’t go for this.
The second basic strategy is the next most common, and by far the most irritating. It’s the “lock down the browser” approach, where you disable a million and one features of the browser, particularly the navigation related ones. Why is this one so irritating? Well…
- Firstly it’s hard to do for multiple browsers. The code to lock down IE is very different from Mozilla or Opera. Not (normally) a problem for intranet apps, but a big one for externally facing programs. Even in an intranet, it can be awkward; IE 4 for example has very different capabilities to IE 6.
- It’s brittle; you have to do it in lots of places throughout the (generated) HTML.
- It’s hard to test; testing scripting is painful at the best of times
- It’s annoying. Dammit, I want my status bar and toolbar. I want to be able spawn a new window in the same session and work on two different things at once. I want to be able to see the comments on a customer at the same time I process their request (or at least I would if I worked with customers instead of writing software). Why the heck should I have to compromise the way I want to work just because a developer couldn’t be bothered to write their software better?
Phew, I’m glad I got that off my chest. Now, where was I? Oh yes, the third way to protect your application: manage state proactively. Identify what parts of your application are stateless or idempotent, and what parts have state issues. Then protect your stateful section by keeping track of what the user has done. One relatively easy way to do this, for example, is request caching; keep the request in memory, and when a new one comes in, see if you have an equivalent one being processed or recently processed, and serve that one instead. Another way to protect your application is to validate your required state, and redirect the user accordingly if they haven’t put things into the required state yet. Yet another is to treat the request as an expression of intent: eg: a request to create a customer should see if the customer already exists, and then mark the request filled if the customer exists.
Transient request IDs help with this, as does storing items in request scope. But there are lots of answers to this problem; laziness is just an excuse. The nice thing, however, about doing this sort of protection at the server is that it is dead simple to test, as anything that understands HTTP can be used. If nothing else, grab a copy of HttpUnit and go to town.
(Oh, and why am I so hot on this topic at the moment? I had to spend the better part of two weeks “rescuing” a really badly written web application, and the only way we could was via the “lock down the browser” option)