Riding over the ledge for the third time, I swerved around the rock, my front wheel slipped, I gripped the brake, and flipped wildly over the handlebars yet again. I was frustrated, battered, and bloodied. It had been the third time this week that I crashed on the cluttered, twisting, dropping ledge. I needed to think about the problem in a different way. Eventually, I caught sight of a more experienced biker going over the ledge with more speed. He didn't even try to avoid the rocks. He just sprang up to lighten his bike and let the wheels go over what I had been going around. I felt like a fool for making it all so hard.
Web programming in the Java™ programming language is kind of like that. We make things harder than they have to be. You need to keep things stateless to make them scale better. You code many independent responses to user requests in the form of servlets or JavaServer Pages (JSPs). You're left with many independent request/response blocks forming no coherent whole. Each one needs to determine where it is in the context of the whole application. You have to swerve around each tiny rock in your path, remembering state with each request. Assume too much, and you'll find yourself flipping over the handlebars as the user clicks the Back button, or opens a second session, or tries to go directly to an older URL. You want to assume that the user will execute tasks in the right order, but you can't. Most Java frameworks just don't work that way.
In this article, I introduce you to a radically different kind of Web server: a continuation-based Web server. Of course, the Java programming language doesn't have native support for continuations (although other programming languages do). Here, I'll examine continuation-based servers. In "Secrets of lightweight development success, Part 8," I examined one such framework called Seaside, followed by a few Java technology frameworks that provide limited continuation support through various creative means.
As you learned in Part 7, a continuation object is like an object representing a frozen point in time. Call the continuation object, and you return to that point. Let's look at an example of continuations in the Ruby programming language because Ruby continuations are a little easier for most people to understand than continuations in some of the other languages. (Continuations in Smalltalk work in a similar way.) You'll see three distinct steps:
- Capture a continuation
- In Ruby, you use
callcc, which means call with continuation. Call with continuation gives you a code block, and the continuation (or a saved call stack, with instance variables), which you assign to a variable. Think of a continuation as all of the program that hasn't executed yet. - Continue processing
- The
callccstatement doesn't change the state of execution beyond introducing the continuation object. After thecallcc, you don't have to do anything else special. - Call the continuation
- At some later time, when you call a continuation, you return to that previous point in time. Think of the
callccas replacing the current call stack with the call stack in the continuation object. In Ruby, execution resumes at the line of code immediately after the originalcallccthat captured the continuation.
A continuation example in Ruby
The following example shows a loop that captures a continuation when it has a value of 2:
Listing 1. A loop that captures a continuation when it has a value of 2
def loop
cont=nil
for i in 1..4
puts i
callcc {|continuation| cont=continuation} if i==2
end
return cont
end
|
Ruby code is relatively easy to read, so even if you're unfamiliar with the language, you should be able to understand what's going on with a little help. The statement def loop defines a method. The next two lines define a variable, start a for loop, and print out the current value of i. The magic happens in the fifth line. I perform a call with continuation and pass in a code block, but only if i is 2. Inside the code block, I assign the new continuation to the cont variable. Each time I call loop, it returns a continuation. Ruby captures a call stack and instance variables (in this case, the values of cont and i.) In irb, the interpreter for Ruby, I load the loop program and call it with the code:
Listing 2. Load the loop and call it with the code
prompt< c=loop 1 2 3 4 |
Now I can call the continuation and pick up the loop after i is set to 2 and printed:
prompt< c.call 3 4 |
Amazingly enough, it goes back in time, to when I captured the continuation. This interesting abstraction is very useful for certain problems.
Continuations are particularly useful when you want to suspend processing for some reason and later pick up where you left off. But isn't that exactly what happens with Web applications? You process a user's request, store your state, ask the user for more information, and wait. When the user calls the Web server again (it might be some minutes later), you need to go to the state dictionary, read the user's response, and then continue processing. Lather, rinse, repeat.
If you had a continuation, you could create a continuation object, store it away with a unique ID generator, and ask the user for more information. Trivial application server code would look something like this:
Listing 3. Application server code
def ask_user(webpage_id) id=Id_genrator.new_id continuation=callcc... continuations[id]=continuation load_user_webpage(id, screen_id) end |
(Note that this is not a real framework -- just some code that shows you what the main details might look like in a continuations-based application server.)
Then the Web server could work on other Web requests. When the user submitted your page and specified the ID you passed him, you could just restore the continuation and continue. The Web server code would work something like this:
Listing 4. Web server code
while(True) do
request=get_latest_request()
if request.is_a_new_request do
start_new_app(request)
else
continuation=continuations[request.id]
continuation.call(request)
end
end
|
New requests are simple: You simply start a new Web application. Otherwise, you just load the continuation from a dictionary, using the ID that you stored when you made the user request.
Look at it as a shift in perspective. With traditional approaches, you're thinking from the browser's perspective, with many individual requests. With the continuation approach, you're looking at things as one big application, with several calls back to the user. It may be hard to get your head around what I've shown you so far, but that's OK. What's important is the usage model. A shopping cart starts to look something like this:
Listing 5. Shopping cart
def checkout(cart) items=cart.items shipping_address=get_shipping_address() billing_address=get_billing_address if billing_address.is_separate_address() credit_card_info=get_credit_card_info() process_checkout(items, shipping_address, billing_address, credit_card_info) end |
Though each get call goes back to the user, you don't have to save state, or restore state when control comes back to you. The continuation that gets captured by the framework takes care of that for you.
The advantages of continuations
You can see the primary benefit of continuations immediately. You don't have to preserve and load state every time you return to the user for more information. That's an incredible advantage, and if you need to build a Web site with complex flows, this kind of server can be a huge time-saver. Your code is easier to write and much easier to maintain.
With all the sophisticated Web frameworks available in Java technology, it's amazing to me that so many of them handle the Back button so poorly. With continuations, your users can navigate back not just one page but to any previous page in the browser history. You don't have to build in custom support because the Web server has the complete call stack for that point in time. The Back button works seamlessly. If you need to invalidate the Back button at any point, it's easy. You just take past continuations for the user out of the continuation dictionary. Threading, too, becomes much easier because each continuation has its own private set of data.
It's amazing that this single higher abstraction can make such a tremendous difference in your Web programming, but it really does. If the Java language had native support for continuations, we'd all be coding this way. In fact, some Java frameworks such as Apache Cocoon, RIFE, and Spring Web flow try to use this model with varying degrees of success using techniques such as state machines (in RIFE and Spring Web flow), other languages (such as the JavaScript engine in Apache Cocoon) and byte-code enhancement (in RIFE). They do improve on typical Java solutions, but they don't compare to the user experience with the best servers in other languages.
The disadvantages of continuations
Of course, there's no such thing as a free lunch. You typically see three or four arguments against using continuations:
- Session state
- In some ways, you're making it easy to store more data in the session by automating session management. Simply declaring an instance variable commits it to the session. When you make it easy to store session data, developers will probably store more state data, limiting scalability. Of course, that's also the price of higher-level languages.
- Scalability
- Saving a call stack can be expensive. Saving many of them may be prohibitive. Interestingly, if you store only the parts of the stack that are different from one user to the next, you can limit the amount of stack data that you store to something reasonable. This approach, called partial continuations, has promise.
- Ugly URLs
- If you store an ugly identifier with each user request, your URLs will not be beautiful or predictable. You can mitigate this problem some by exposing a pretty part of the URL, then appending an ugly identifier. This is the approach Amazon.com takes, and it seems to work well. However, this limitation is relatively serious.
There may be no such thing as a free lunch, but continuation servers seem to give you the next best thing: excellent value for your money. I believe we'll all be using continuation servers in some form in five years, or maybe even sooner. We might see one of the Java derivatives catch on, or we might see continuation servers in other languages help to make those languages more prominent.
Learn
-
Better, Faster, Lighter Java, by Bruce A. Tate and Justin Gehtland (O'Reilly, 2004), provides a good overview of lightweight development.
-
Programming Ruby, by Dave Thomas (Pragmatic Bookshelf, 2004), is the best Ruby book out there.
-
Continuations Made Simple and Illustrated (comp.lang.python newsgroup) is a good resource for information about continuations in the Python programming language.
-
Read Martin Fowler's excellent article about Closure (martinfowler.com, September 2004).
-
Seaside is an amazing framework based on the Smalltalk language and continuations.
-
Visit the developerWorks Open source zone for extensive how-to information, tools, and project updates to help you develop with open source technologies and use them with IBM's products.
Get products and technologies
-
Check out the Java frameworks Apache Cocoon 2, RIFE, and Spring Web flow.
-
Innovate your next open source development project with IBM trial software, available for download or on DVD.
Discuss
-
Get involved in the developerWorks community by participating in developerWorks blogs.

Bruce Tate is a father, mountain biker, and kayaker in Austin, Texas. He's the author of three best-selling Java books, including the Jolt winner Better, Faster, Lighter Java. He recently released Spring: A Developer's Notebook. He spent 13 years at IBM and is now the founder of the J2Life, LLC, consultancy, where he specializes in lightweight development strategies and architectures based on Java technology and Ruby.





