Wicket in Action

Git Wicket running on Heroku

20 September 2011, by dashorst

At JavaZone 2011 I met with James Ward, technology evangelist for Heroku and had a brief discussion on what it would take to run Wicket applications on Heroku. We figured that it wouldn't be too hard to get a Wicket application running on a single instance (a dyno in Heroku lingo).

So I tasked myself yesterday evening to get a Wicket quickstart running on Heroku. Basically the recipe is as follows:
- generate a quickstart
- modify the pom such that the packaging goes from war to jar
- modify the pom to depend on jetty at compile time, and on the servlet api on compile time (not provided)
- modify the pom to generate a shell script to start the application (using appassembler-maven-plugin)
- add a Procfile to the root of your project
- add the following Start class to the src/main/java path

import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.webapp.WebAppContext;

public class Start {
    public static void main(String[] args) throws Exception {
        String webPort = System.getenv("PORT");
        if (webPort == null || webPort.isEmpty()) {
            webPort = "8080";
        }
        String webappDirLocation = "src/main/webapp/";

        Server server = new Server(Integer.valueOf(webPort));
        WebAppContext root = new WebAppContext();
        root.setContextPath("/");
        root.setDescriptor(webappDirLocation + "/WEB-INF/web.xml");
        root.setResourceBase(webappDirLocation);
        root.setParentLoaderPriority(true);

        server.setHandler(root);
        server.start();
        server.join();
    }
}

For some reason the Start class provided by the quickstart archetype doesn't work on Heroku, but the above class is simple enough. It should be included in the src/main/java folder (and default package) because with Heroku, you bring your own server (in our case the Jetty server). The code will pick up the provided socket port to connect to (Heroku supplies external settings using environment variables) and start up the Jetty server.

The Procfile is just a descriptor for Heroku to identify the start up script.

web:   sh target/bin/webapp

When you have everything up and running locally:

$ mvn install
[INFO] Scanning for projects...
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building Heroku Wicket Quick Start 1.0-SNAPSHOT
[INFO] ------------------------------------------------------------------------
[INFO] 
...

$ export REPO=~/.m2/repository
$ sh target/bin/webapp
INFO  - log                        - jetty-7.3.0.v20110203
...
INFO  - WebApplication             - [wicket.heroku-wicket] Started Wicket version 1.5.0 in DEVELOPMENT mode
********************************************************************
*** WARNING: Wicket is running in DEVELOPMENT mode.              ***
***                               ^^^^^^^^^^^                    ***
*** Do NOT deploy to your live server(s) without changing this.  ***
*** See Application#getConfigurationType() for more information. ***
********************************************************************
INFO  - log                        - Started SelectChannelConnector@0.0.0.0:8080

When you see this (don't forget to add the REPO environment variable to your shell and point it to your maven repository), you can push your code to Heroku using

heroku create -s cedar
git push heroku master

Getting the Wicket examples running on Heroku was easy now that the recipe is clear. It just works™.

You can see the code modifications I made on my github repositories for both the quickstart and the wicket-examples.

You can see the Heroku QuickStart in action, as well as the Heroku Wicket Examples both running on a single dyno.

Things worthy of note

Not everything is rosy in the garden of Heroku and Wicket.

Server side state

Wicket uses the HTTP session to communicate the last accessed page to a cluster. As HTTP session clustering is not supported on Heroku (unless you persist the session in a memcache or redis instance), you will loose running sessions when Heroku reassigns your dyno. Of course you can persist your HTTP session in a memcache or redis database.

Since Wicket stores the component tree in the filesystem (by default, you can store it in a memcache or other cloud storage) and Heroku has a read-only filesystem the page store can be evaporated at any time. This will affect the back button support when Heroku has reclaimed your specific dyno instance.

Update: As James Ward points out in the comments,

the file system is writeable but ephemeral – so it can go away at any time</p>

.

Concurrency support

Heroku doesn't support sticky sessions, and as such concurrent access to the component tree cannot be prevented by synchronizing on the page. I think we might be able to come up with something that will work in such an environment in the future as we migrate towards more granular locks (Wicket 1.4 would lock on a page map, 1.5 on a page instance). So running a Wicket application on multiple dynos is not supported (yet).

One advantage of Heroku is that one dyno can only process one request at a time, so synchronization on the component tree in a single JVM is no longer necessary.

UPDATE: After some discussion with the Heroku guys, it appears I based this assumption on old information on the Heroku website. The new Java supported platform called cedar stack does support multiple requests coming in at the same time. The running Wicket Heroku example applications are deployed to the herokuapp.com platform, and make use of the cedar stack.

Conclusion

It is possible to run Wicket applications on a Heroku instance which is a great testament of Heroku's engineering team. I found it easier to get a Wicket application up and running on Heroku than on Google App Engine. There are several loose ends, and we might not be able to fix those given Wicket's architecture. That said, I find Heroku's architecture quite compelling and am anxious to get Wicket fully supported on this platform.

-->