Shutting down Jetty programmatically safely

When Googling the web for a flexible and secure way to shutdown an embedded Jetty server programmatically i found some examples. These examples were nor safe because the server could be shutdown by guessing the correct uri initiating the shutdown, or did require extra interfaces (sockets) listening on the loopback interface to initiate the shutdown. When writing my own embedded Jetty concept i had 4 requirements. First, it should be practically impossible to stop the Jetty server from outside the application (unless you have a special key). Secondly, it must be possible to stop the Jetty server from any servlet. No extra implementations of socket listeners. Last, when initiating a stop of Jetty anywhere in the code, is should be able to set the exit or return code to the underlying operating system.

Here in a nutshell how i realized all these requirements. First in the main class, the same class wherein the Jetty server will be started, a random shared secret key is generated. I used a randomly generated BigInteger of 130 bits, secure enough for me.
// Generate random shared secret key
secretKey = new BigInteger(130, new SecureRandom()).toString(32);
This shared secret key is passed to each servlet using an initialization parameter. The same secret key and an the instance of server are passed to an instance of AbstractHandler which is also added to the server context. The shared secret key is now known by the servlets and AbstractHandler, responsible for stopping the Jetty server.
        // Add handler on path /stopjettydemo/stopjetty
        ContextHandler handlerContext = new ContextHandler();
        handlerContext.setContextPath("/stopjettydemo/stopjetty");
        StopJettyHandler contextStopJettyHandler = new StopJettyHandler(secretKey,server) ;
        handlerContext.setHandler(contextStopJettyHandler);
When called, the handler method within the AbstractHandler retrieves the value of the url parameters, secretKey and exitCode. When the value of the secretKey in the url parameter matches the value of the shared secret key passed to the constructor the server stop is initiated. The value of the url parameter exitCode is assigned to an protected attribute exitCode which can be referenced from the main class starting the Jetty server.
When the AbstractHandler is called using no secretKey url parameter or a non matching value is passed, the stop of the Jetty server is denied.
        // get url parameters
        String sharedSecretKey = rqst.getParameter("secretKey");

        try {
            exitCode = Integer.valueOf(rqst.getParameter("ex"));
            jlogger.info("Set system exit code in StopJettyHandler to " + exitCode, this) ;
        } catch (NumberFormatException nfe) {
            exitCode = 0;
        }

        // Basis http response
        hsr1.setContentType("text/html");
        hsr1.setStatus(HttpServletResponse.SC_OK); //200
        rqst.setHandled(true);

        if (sharedSecretKey != null) {
            if (sharedSecretKey.equals(this.secretKey)) {
                // Stop the server in separate thread.
                new Thread() {
                    @Override
                    public void run() {
                        try {
                            server.stop();
                        } catch (Exception ex) {
                            jlogger.info("Failed to stop Jetty", ex);
                        }
                    }
                }.start();
            } else {
                jlogger.info("Tried to stop Jetty using wrong key value", this);
                hsr1.getWriter().println("Sorry you guessed the wrong key<br/>");
            }
        } else {
            jlogger.info("Tried to stop Jetty without a key value", this);
            hsr1.getWriter().println("You have to supply a proper key<br/>");
        }
When a server stop is initiated from the AbstractHandler the main class continues after the server.join() and returns the value of the attribute exitCode of the instance of AbstractHandler.
try {
	jlogger.info("Starting Jetty server", null) ;
	server.start();
	jlogger.info("Joining Jetty server", null) ;
	server.join();
} catch (Exception ex) {
	jlogger.info("Failed to start the Jetty server", ex);
	try {
		server.stop();
	} catch (Exception ex1) {
		jlogger.info("Failed to stop the Jetty server", ex1);
	}
	server.destroy();
}
        
System.exit(contextStopJettyHandler.exitCode) ;
The shutdown of the Jetty server can now be initiated by calling the uri of the AbstractHandler with the uri parameters exitCode and secretKey. The secretKey is passed to the instance of the servlet as an initialization parameter and stored as a protected attributed in the init method of the servlet. This way the value of the key is available anywhere within this java class. The method shutdownJetty is added to make the actual HTTP request which is passed to the AbstractHandler enabling to shutdown the Jetty server safely anywhere within the servlet (also the init method) by calling the method with shutdownJetty({exitCode}).
    private void shutdownJetty(String exitCode) {
        final String shutdownCode = exitCode;

        new Thread() {
            @Override
            public void run() {
                try {
                    // Take some time to initialize the servlet when called from Init method
                    sleep(500);
                    // Do a http GET to the StopJettyHandler with the supplied
                    // secretKey
                    URLConnection connection;
                    String uri = "http://localhost:" + serverPort
                            + "/stopjettydemo/stopjetty/?secretKey=" + secretKey 
                            + "&exitCode=" + shutdownCode ;
                    jlogger.info("Shutting down Jetty making HTTP request to " + uri,this);
                    connection = new URL(uri).openConnection();
                    connection.connect();
                    connection.getContent();
                } catch (Exception ex) {
                    jlogger.info("Failed to make HTTP request", ex);
                }
            }
        }.start();
    }

You can download a working Netbeans Java Application project here. When running the project a Jetty server is started, listening on localhost port 3456. You have basically four urls to explore the code with.

http://localhost:3456/stopjettydemo/stopjetty/?secretkey={somekey}&exitcode={someexitcode}
The Jetty server is actually stopped when the proper shared key {somekey} is supplied. After stopping the Jetty server the main class returns the {someexitcode} to the operating system. Try it yourself to shut it down with any guessed shared secret key. You will see that the server will respond to you with "Sorry, will not stop Jetty, you guessed the wrong key".

http://localhost:3456/stopjettydemo/demoservlet1
This URI triggers the call to the shutdownJetty method from within the DemoServlet.java (Servlet) class. I solely added this to easely trigger the invocation of the shutdownJetty method from within the init method of the servlet. In other circumstances than this demo you would have this functionality behind this URI, because this would make it immediately insecure. After requesting the URI the reponse "The Jetty server will be shutdown within a few seconds from within the init method with exitcode 255" is returned. The Netbeans output window will show a log similar to the one below.
run:
2013-01-15 09:13:17.952:INFO::Servlet at http://localhost:3456/stopjettydemo/demoservlet1/ stops Jetty from init method null
2013-01-15 09:13:17.953:INFO::Servlet at http://localhost:3456/stopjettydemo/demoservlet2/ stops Jetty on doGet null
2013-01-15 09:13:17.953:INFO::Servlet at http://localhost:3456/stopjettydemo/demoservlet3/ results in HTTP 200 response without stopping Jetty null
2013-01-15 09:13:17.955:INFO::Starting Jetty server null
2013-01-15 09:13:17.955:INFOGaspejs.Server:jetty-8.1.8.v20121106
2013-01-15 09:13:18.044:INFOGaspejs.AbstractConnector:Started SelectChannelConnector@0.0.0.0:3456
2013-01-15 09:13:18.048:INFO::Joining Jetty server null
2013-01-15 09:13:22.434:INFO::Shutting down Jetty from withing init method stopjettydemo.DemoServlet@3f78d35f
2013-01-15 09:13:22.950:INFO::Shutting down Jetty making HTTP request to http://localhost:3456/stopjettydemo/stopjetty/?secretKey=anrtcdepgdg09keh2ca5lj2kvo&exitCode=255 Thread[Thread-11,5,main]
2013-01-15 09:13:22.968:INFO::Set system exit code in StopJettyHandler to 255 stopjettydemo.StopJettyHandler@12554af0
2013-01-15 09:13:22.982:INFOGaspejsh.ContextHandler:stopped o.e.j.s.h.ContextHandler{/stopjettydemo/stopjetty,null}
2013-01-15 09:13:22.983:INFOGaspejsh.ContextHandler:stopped o.e.j.s.ServletContextHandler{/stopjettydemo,null}
2013-01-15 09:13:22.983:INFOGaspejsh.ContextHandler:stopped o.e.j.s.ServletContextHandler{/stopjettydemo,null}
2013-01-15 09:13:22.983:INFOGaspejsh.ContextHandler:stopped o.e.j.s.ServletContextHandler{/stopjettydemo,null}
Java Result: 255
BUILD SUCCESSFUL (total time: 5 seconds)
http://localhost:3456/stopjettydemo/demoservlet2
This URI initiates the same shutdownJetty from within the servlet, only now from the doGet method resulting in an exitcode 254.

http://localhost:3456/stopjettydemo/demoservlet3
This URI does nothing more than responding to you "The Jetty server is still running".




blog comments powered by Disqus