Using continuations to build web applications
Client-server interaction in web applications is subject to the statelessness of HTTP. The user makes a request, for example by clicking a link or submitting a form, and the server responds with an HTML page. On another request from the same client, the server has no idea that they’ve communicated before. Imagine we want to place an order across several forms.
Klient Server
** Stranka 1 **
** Konec stranky 1 **
** Stranka 2 **
kontrola dat serverem (nevalidní data) návrat na stranku 1
** Konec stranky 2 **** Stranka 3 **
kontrola dat serverem (nevalidní data) návrat na stranku 2
<-- stránka s informací o odeslání objednávky k zpracování
** Konec stranky 3 **To implement this scenario we’d probably use an HTTP session. We’d split the form logic among several controllers, or use one controller with lots of checks for previous steps. The disadvantage of overcoming HTTP’s statelessness with a session is that on each request we have to reconstruct the state of the earlier conversation from session values before deciding what to do next (continue or go back one step).
Think what request processing would look like if every time the client sent data the server could pick up from the previous state of the conversation and carry on.
1: function novaObjednavka(request){
2: boolean success = false;
3: do{
4: sendPageAndWait("formular1");
5: success = validate(request);
6: } while (!success)
7:
8: success = false;
9:
10: do{
11: sendPageAndWait("formular2");
12: success = validate(request);
13: } while (!success)
14:
15: zpracujObjednavku();
16: sendPage("objednavkaPrijata");
17: }- The client asks the server to start a new order. The server determines, however it chooses to do so, that
novaObjednavkahandles it. Itsrequestparameter is the actual HTTP request. - Program execution stops at line 4. The server’s native
sendPageAndWaitfunction returns the first form and hands control to the client. Its argument tells the server which page to return. - After a while the client fills out and submits the form to the server, which takes over. The server knows it left off at line 4 last time, so it resumes at line 5. Line 5 validates the form, returning a boolean.
- If validation succeeds, execution goes through line 8 to line 11. Our familiar
sendPageAndWaitfunction hands control to the client again, along with the second form. - If validation fails, the loop condition sends execution back to line 4 and the server hands control back to the client with the first form.
- On the next transfer of control, the server again knows where the last interaction ended.
It resumes at line 5 or 12 accordingly. - The new-order interaction ends with order processing (saving to a database and so on) on line 15, and control passes back to the client on line 16.
At a glance, the code is very readable and we don’t have to spend much effort reconstructing the previous state and deciding how to proceed with every request. The problem is how to implement the interruption caused by sendPageAndWait, and especially how to lift it later and resume processing once the client provides data. The missing piece that lets us pause and resume is a continuation.What is a continuation?
Continuation is a term borrowed from Scheme. It describes a function
or object that represents a program’s current state (stack trace and variable values) while it runs. Imagine running a program in debug mode and stopping at a breakpoint. The stack trace and local variables you see in the debugger represent the continuation of the suspended program.
Looked at from the other side, the continuation represents the “rest” of the program’s work. Suppose line 5 in the program below is executing. A screenshot underneath shows the relevant details.
1: public class Foo{
2: public static void main(String args[]){
3: int foo = 1;
4: int hoo = 1;
5: int bar = foo + hoo;
6: System.out.println(bar);
7: }
8: }
A continuation tells us both the state of program execution and how it will carry on (in our example, line 6 runs next). Because a continuation tells us the current state and how to proceed from it, we can pause execution and resume it later from the pause point. We only have to obtain the continuation object or function.
Unfortunately, unlike some programming languages, Java offers no standard way to work with continuations. As far as I know, neither .NET nor PHP supports them either. Java still lets you use continuations even without standard support.
Among better-known frameworks, the RIFE web application framework supports continuations.
The article RIFE Web continuations describes that support. Last week the RIFE developers decided to split their continuation engine into a separate subproject, independent of the rest of RIFE.
See Announcing RIFE/Continuations, pure Java continuations for everyone.
Apache Cocoon also lets you write a flow script with continuations, both in Java and in JavaScript through the Rhino engine. I highly recommend Abhijit Belapurkar’s Use continuations to develop complex Web applications.
It covers extra details that didn’t fit here. Another interesting article about Cocoon is Rhino with Continuations.
If you read that the Jetty servlet container supports continuations,
that may not be true. At least what Greg Wilkins presented in Jetty 6.0 Continuations - AJAX Ready! is a form of request parking, not continuations. See the discussion on
TSS.
Related articles
Related CZ Podcast episodes
- #3 · Java 6 SE, Vývoj webových aplikací
Dagi and Filemon discuss Java 6 and web application frameworks. · Czech audio
- #1 · Vývojová prostředí v Javě
The first CZ Podcast episode looks at Java development environments. · Czech audio