How to start and stop Jetty – revisited

I mentioned in my previous post that I would blog about some of the things I learnt while putting together the Seam / JSF versus Wicket “perfbench“.

A while back I posted about how to start and stop Jetty from Ant – useful for those using the Jetty downloaded distribution. In this post I show how to cleanly shutdown a Jetty instance started in “embedded” mode. This tip may be useful for those using Jetty in a continuous integration build – for e.g. when Selenium tests are involved.

Info on starting an embedded Jetty instance is out there but I was not able to find ways to cleanly shutdown – other than hacks like this.

Update 2009-04-07: just found a blog post by Stephen Haberman that has a detailed explanation of WAR-less Development with Jetty

Here’s the code to start Jetty. The trick is to spawn a thread with a socket listening on another port (8079 in this case) that we can connect to later:

package mypackage;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import org.mortbay.jetty.Connector;
import org.mortbay.jetty.Server;
import org.mortbay.jetty.bio.SocketConnector;
import org.mortbay.jetty.webapp.WebAppContext;

public class Start {

    private static Server server;

	public static void main(String[] args) throws Exception {
		server = new Server();
		SocketConnector connector = new SocketConnector();
		connector.setPort(8080);
		server.setConnectors(new Connector[] { connector });
		WebAppContext context = new WebAppContext();
		context.setServer(server);
		context.setContextPath("/wicket-jpa");
		context.setWar("src/main/webapp");
		server.addHandler(context);
        Thread monitor = new MonitorThread();
        monitor.start();
        server.start();
        server.join();
	}

    private static class MonitorThread extends Thread {

        private ServerSocket socket;

        public MonitorThread() {
            setDaemon(true);
            setName("StopMonitor");
            try {
                socket = new ServerSocket(8079, 1, InetAddress.getByName("127.0.0.1"));
            } catch(Exception e) {
                throw new RuntimeException(e);
            }
        }

        @Override
        public void run() {
            System.out.println("*** running jetty 'stop' thread");
            Socket accept;
            try {
                accept = socket.accept();
                BufferedReader reader = new BufferedReader(new InputStreamReader(accept.getInputStream()));
                reader.readLine();
                System.out.println("*** stopping jetty embedded server");
                server.stop();
                accept.close();
                socket.close();
            } catch(Exception e) {
                throw new RuntimeException(e);
            }
        }
    }

}

The “MonitorThread” in the inner class above stops the embedded Jetty server if a line feed is received. So the code to stop Jetty is pretty simple. Here we duly send “\r\n” to port 8079:

package mypackage;

import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;

public class Stop {

    public static void main(String[] args) throws Exception {
        Socket s = new Socket(InetAddress.getByName("127.0.0.1"), 8079);
        OutputStream out = s.getOutputStream();
        System.out.println("*** sending jetty stop request");
        out.write(("\r\n").getBytes());
        out.flush();
        s.close();
    }
    
}

So how does one use this from Ant? Easy !

<target name="jetty-cycle">               
    <parallel>
        <java classname="mypackage.Start" classpathref="test.classpath" fork="true"/>
        <sequential>
            <waitfor>
                <socket server="127.0.0.1" port="8080"/>
            </waitfor>                
            <antcall target="my-tests"/>                            
            <java classname="mypackage.Stop" classpathref="test.classpath"/>                 
        </sequential>
    </parallel>
</target> 

Full disclosure: I adapted the approach from the Jetty code you can find over here ;)