Friday, September 11th 2009

Faster development with eclipse and embedded jetty

These days I am working on some web applications, which I build with good old ant. Build time is less that 10 seconds which is pretty good. Yet even this is too slow when developing, if I had to wait 10 seconds (or 5 minutes if I were using maven) every time I made a change before I could test it, I would go nuts.

Instead I would like to make changes in eclipse and be able to instantly test the results in my browser, before my mind wanders off and I start playing tetris or barrage. To achieve this I am using an embedded jetty server.

public class EclipseMain {
    public static void main(String[] args) throws Exception {
        Server server = new Server();

        Context context = new Context(server, "/", Context.SESSIONS);

        // This allows me to have cookies that work over subdomains
        HashMap map = new HashMap();
        map.put("org.mortbay.jetty.servlet.SessionDomain", ".localhost.localhost");
        context.setInitParams(map);

        // I am using both http and https in my app and want to test both
        Connector connector = new SelectChannelConnector();
        connector.setPort(8080);

        SslSocketConnector sslConnector = new SslSocketConnector();
        sslConnector.setPort(8443);
        sslConnector.setKeystore("/path/to/my/keystore");
        sslConnector.setPassword("wouldntyouliketoknow");
        sslConnector.setKeyPassword("wouldntyouliketoknow");
        sslConnector.setTruststore("/path/to/my/keystore");
        sslConnector.setTrustPassword("wouldntyouliketoknow");  
        server.setConnectors(new Connector[] {connector, sslConnector});

        // enable persistent sessions, so we don't need to relogin after restart
        enablePersistentSessions(context, "/home/manuel/.jetty_test");

        // Using my own jurlmap library here
        context.addFilter(new FilterHolder(new GoblinDispatchFilter()), "/*", 
                          org.mortbay.jetty.Handler.REQUEST 
                          | org.mortbay.jetty.Handler.FORWARD);

        // Static files such as images, css
        context.setResourceBase("web"); 
        // Serve static files
        context.addServlet(DefaultServlet.class, "/"); 

        // Server is ready to start
        server.start();
    }

    public static void enablePersistentSessions(Context context, String folder) {
        File file = new File(folder);

        if (!file.exists()) {
            file.mkdirs();
        } else if (file.isFile()) {
            throw new RuntimeException("Session location not a folder: " + folder);
        }

        final HashSessionManager manager = new HashSessionManager();
        manager.setStoreDirectory(file);
        context.getSessionHandler().setSessionManager(manager);

        Runtime.getRuntime().addShutdownHook(new Thread() {
            public void run() {
                try {
                    manager.saveSessions();
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        });
    }
}

If you are not using JSP then you just need to add these three libraries from the jetty distribution to your project classpath:

jetty-6.1.14.jar
jetty-util-6.1.14.jar
servlet-api-2.5-6.1.14.jar

With this setup it takes about two seconds between making changes and being able to test the results. Just edit your code and run EclipseMain as a normal application (in eclipse Run As Java Application). I have configured persistent sessions which means I don't even have to log back in to my app after every server restart, I just hit refresh and I am right back where I left.