June 6, 2011

Continuation-based web applications: just say no

Many people still seem to regard continuations as a possible or even preferable method for writing web applications. This blog post aims to dispel that notion and demonstrate that continuation-based web apps belong in the 90s.

Why are continuations good?

Anton van Straaten's excellent Continuations Continued argues that continuations are a good way to model server-side code, ergo they are a good way to implement server-side code. The modeling assertion is for some instances correct, the implementation assertion is not.

Continuations made sense for pre-JavaScript web applications, where each new functionality predicated on a possible user choice (ViaWeb's color picker palette being a good example) would lead to combinatorial growth in the complexity of the state machine behind the pages that that functionality would interface with. This is because the server was burdened with what is essentially transient client-side state. With AJAX, this is no longer the case.

Why are continuations bad for clients?

There are well-known problems with continuation-based web apps: bookmarks, history, and back/forward buttons don't work.

A web application session is a call-graph from the point of view of the browser, where the URLs are akin to procedures. HTTP interaction flows like a program, with the user making decisions of which procedure to invoke/URL to visit. By this analogy, using continuations is exactly like giving random names to all of the procedures in a program each time a procedure is called.

This is the core of the problem with continuation-based web applications. Everything that revolves around user control of accessing URLs (bookmarking, history, back/forward, etc.) breaks. This also makes it much harder to test continuation-based web applications programmatically and makes debugging harder.

One incidental advantage of this breakage is that some URLs do need to be unique and single-access to prevent cross-site attacks and duplicate form submissions. I argue that these mechanisms should be thought of as token-issuing state machines, and implemented explicitly. This leads to simpler code and manifest state.

Another strategy that has been used is storing continuations on the client side using cookies or URL query parameters. This approach is problematic for the amount of data it transmits on each request, and the possible security implications (the continuations need to be encrypted, and the keys frequently rotated and expired - however the expiry of continuations is exactly the problem that query-parameter serialized continuations were supposed to avoid - links that rely on continuations stored on the server cannot be bookmarked!).

Why are continuations bad for servers?

The essence of using continuations server-side is handing off control of inter-request state serialization to an implicit mechanism that is tied to the structure of application code.

Both data and logic are now intermingled and stored in opaque continuation structures. This makes the code hard to debug, state difficult to replicate for fail-over redundancy, problems difficult to reproduce, and control flow difficult to understand.

What should you do?

The ability of JavaScript to make HTTP requests without reloading the current page (AJAX) allows you to keep what is essentially client-side state on the client. The server is now responsible for a set of URIs, where each URI can be thought of as a separate service that can be modeled as a state machine. This keeps web application state and control flow manifest. Different parts of your web application (represented by different URIs) can now be completely isolated; any state interactions and dependencies between them become explicit.

7 comments:

Anonymous said...

Hello --

I don't do web programming, but I found it odd that you said that "back buttons don't work" with continuations.

I say this because I remember reading a paper a few years ago, one of the first, if not the first that proposed continuations for the web by Christian Queinnec that specifically addressed the back button problem. I think he was the first to say this.

Here's his page:
http://pagesperso-systeme.lip6.fr/Christian.Queinnec/WWW/Continuation.html

The paper I mention is the last one mentioning the CD-ROM (webcont.ps.gz). (PS: I can't confirm this is the paper because right now I can't open the PostScript document on this machine)

Cheers.
HL

Vladimir Sedach said...

Making the back button work involves either serializing the continuation as a URI parameter, or introducing special schemes where the server knows what to serve when it encounters a particular URI that points to a non-existing continuation. The former has problems as described in the post, and if you're going through the trouble of the latter (it is application-dependent), maybe it's easier not to use continuations in the first place.

Anonymous said...

This essay is pretty confused. Everyone: read the original literature on continuation-based servers instead. And then read some JS programs ... continuations are everywhere, there's no other way to get this form of concurrency into your world. And soon you will have this in JS too.

Anonymous said...

cf. https://groups.google.com/group/racket-users/browse_thread/thread/77dc0dd072281544?hl=en

Eli Barzilay said...

You're making a very bogus point. I'd try explaining why it's so wrong, but Shriram made most that.

Vladimir Sedach said...

I've read the Racket thread, and the only two technical points there are:

1. Web browser JavaScript doesn't have threads, so client-side code has to use callbacks to make asynchronous requests.

2. Shriram's point that "You could let the framework choose them for you. OR, you could choose
to give names to the entry points and have the framework calculate
just the arguments needed for each of those entry points."

Point 1 is valid, but threads would solve this problem better than continuations. In practice XMLHttpRequest callbacks should never get that complicated anyway if you structure your code right. The people building CPS transformers for JavaScript are doing it wrong.

Point 2 seems to make sense, but then why bother with the continuation frameworks at all? If you're choosing the names already, it's a small step to follow REST principles and get the advantages.

I would love to do a follow-up post from a guest blogger on this issue if you have more points and links to papers. I'd post to the Racket list but I'm not subscribed. Please get in touch if that's something you'd like to write.

Eli Barzilay said...

IIUC, you're mixing two separate reasons for continuation frameworks. One is inside JS itself, where continuations would have the same benefits they get in any other language. This is clearly something that's not going away any time soon, as node.js demonstrates so obviously (I can't wait for someone to discover that you can write "plain looking" code, and have your language translate it to the maze of callbacks). At the web server level, which is where your post started, the point is that even with AJAX, continuations are still important, since AJAX is itself carried over the same stateless protocol, therefore from the server's point of view you need to break the computation into the same kind of chunks. If anything, AJAX allows you to break interactions into *tiny* chunks, rather than old-school 90s whole-page forms, and that means that the automatic threading that you get from continuations is even more appealing. IOW, you could say in the past that it's fine to dump one form's data into hidden fields or a hashed handle for the data which is saved to disk, but now a single AJAX page can do a request for every cursor movement. (All of this is irrelevant of the mechanism that receives them -- single threaded callbacks or a thread, that's where the first point goes.)

(As for the mailing list, it's easy to subscribe even without getting the emails...)