2006-10-17

Continuation based Web Server

Programming Web Applications is hard. This has to do with the stateless HTTP protocol and with the event driven nature of the human-computer interaction.

To regain control as in the old days of batch programming with straight-forward algorithms continuation based approaches seem to come to help.

For a nice introduction to the topic, please check out the following links:
  1. Inverting back the inversion of control or,
    Continuations versus page-centric programming

  2. The Influence of Browsers on Evaluators or,
    Continuations to Program Web Servers

To learn more about this topic I implemented a Continuation based Web Server in J2ME as explained below. J2ME seems to be very strange as a platform for Web Servers but I like it much for experimental work as it lets you focus on the underlying principles and not get distracted by too many options or libraries to choose from.

The idea was to reimplement the adder application mentioned in [1] in the Java (ME) context.

To decouple the control logic from the view details I added a further abstraction and programmed the logic against that abstraction:

public interface IO {
void queryValue(String msg, StringCont cont);
void inform(String msg, Runnable cont);
}

public interface StringCont {
void cont(String val);
}

public class AdderMIDlet extends MIDlet {
public void startApp() {
IO io = new HTMLIO();
startApp(io);
}

private void startApp(final IO io) {
io.queryValue("Enter first value", new StringCont() {
public void cont(String str) {
try {
double val = Double.parseDouble(str);
addValue(io, val);
}
catch (Exception ex) {
io.inform("Invalid input, try again", new Runnable() {
public void run() {
startApp(io);
}
});
}
}
});
}

private void addValue(final IO io, final double val1) {
io.queryValue("Enter value to add to " + val1, new StringCont() {
public void cont(String str) {
try {
double val2 = Double.parseDouble(str);
double res = val1 + val2;
io.inform("" + val1 + " + " + val2 + " = " + res, new Runnable() {
public void run() {
startApp(io);
}
});
}
catch (Exception ex) {
io.inform("Invalid input, try again", new Runnable() {
public void run() {
addValue(io, val1);
}
});
}
}
});
}
}

As can be seen, the control logic uses continuation passing style (CPS) to simulate continuations via closures. Closures themselves are present in Java in the form of anonymous inner classes, which do the job quite well though they are a bit verbose to write down.

The control aspects of the application are quite straight-forward with this approach and thus allow to concentrate on the logic in the normal and execptional cases.

The view (i.e. input and output) apects are hidden behind a clear interface and thus allow for separate modifications.

Now, let's shed a light on how the IO interface is implemented by the HTMLIO class. The two methods of the interface both have to do two separate things:
  1. generate a page for presentation within a browser
  2. extract input from a user and call the continuation with those arguments

The queryValue method thus looks like this:

public void queryValue(String msg, final StringCont cont) {
setHttpResponse(200);
PrintStream ps = getResponseStream();
ps.println("<html><body>");
ps.println("<form method=\"get\" action=" + genQuotedUrl(new RequestHandler() {
public void handle(Request req) {
String val = req.getValue("input");
cont.cont(val);
}
}) + ">");
ps.println("<input type=\"text\" name=\"input\">");
ps.println("<input type=\"submit\" value=\"Submit\">");
ps.println("</body></html>");
sendResponse();
}

The inform is built the same way. The important function is the genQuotedUrl method which generates an URL and associates that url with a RequestHandler object which will be called when the URL is 'clicked-on' by the user.

The inner workings of our HTTP server now is simple: It waits for incomming connections on a server-socket (say port 8080) parses the request and looks up the registered RequestHandler object matching the request URL.

The matching is done by a simple Hashtable which maps URLs to RequestHandler objects. The question remains, when handlers have to be removed from this table again which influences the behaviour of the 'back' button, of window cloning and memory consumption. A simple approach is to use a timer based approach to this kind of garbage collection problem.

I hope, this simple J2ME based overview helps to understand the concept of Continuation based Web Servers.

cheers
marc

2 comments:

Chris Double said...

Nice idea! I've always thought that a web server on the phone itself would be a good idea - allowing people to build custom apps in javascript and html to automate common tasks.

Or to access the contacts, etc from a PC over GPRS to edit them with the power of a full keyboard by accessing the phones web server - with appropriate permissions and security of course.

Anonymous said...

Wow! Continuations in Java? You should've saved yourself the trouble and used Scheme.