I had a need to allow a web call to stop and restart an embedded Jetty instance.
So basically, I need to have something like this:
curl -v http://localhost:9103/some/path # Calls a handler and does something RESTful curl -v http://localhost:9103/stop # Stops the Jetty instance curl -v http://localhost:9103/restart # Restarts the Jetty instance
First up create a main class:
package com.company; import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.NCSARequestLog; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.ContextHandlerCollection; import org.eclipse.jetty.server.handler.HandlerCollection; import org.eclipse.jetty.server.handler.RequestLogHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class EmbeddedJetty { private static Logger log = LoggerFactory.getLogger(EmbeddedJetty.class); public static void main(String[] args) throws Exception { while (true) { // Realistically all parameters should be read from some external file... log.info("Starting Jetty on port 9103"); Server server = new Server(9103); HandlerCollection handlers = new HandlerCollection(); ContextHandlerCollection contexts = new ContextHandlerCollection(); // I added this to show how to add access logs to an embedded server. RequestLogHandler requestLogHandler = new RequestLogHandler(); NCSARequestLog requestLog = new NCSARequestLog("/tmp/jetty-yyyy_mm_dd.request.log"); requestLog.setAppend(true); requestLog.setExtended(true); requestLog.setLogTimeZone("UTC"); requestLogHandler.setRequestLog(requestLog); // We want the server to gracefully allow current requests to stop server.setGracefulShutdown(1000); server.setStopAtShutdown(true); // Now add the handlers YourHandler yourHandler = new YourHandler(server); handlers.setHandlers(new Handler[]{contexts, yourHandler, requestLogHandler}); server.setHandler(handlers); // Start the server server.start(); server.join(); // It's stopped. log.info("Jetty stopped"); if (!yourHandler.restartPlease) { break; } log.warn("Restarting Jetty"); } } }
Now the handler:
package com.company; import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import javax.servlet.ServletException; import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import org.eclipse.jetty.server.Request; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.AbstractHandler; import org.slf4j.Logger; import org.slf4j.LoggerFactory; public class YourHandler extends AbstractHandler { private static Logger log = LoggerFactory.getLogger(YourHandler.class); private Server server = null; public Boolean restartPlease = false; public YourHandler(Server server) { this.server = server; } private boolean stopServer(HttpServletResponse response) throws IOException { log.warn("Stopping Jetty"); response.setStatus(202); response.setContentType("text/plain"); ServletOutputStream os = response.getOutputStream(); os.println("Shutting down."); os.close(); response.flushBuffer(); try { // Stop the server. new Thread() { @Override public void run() { try { log.info("Shutting down Jetty..."); server.stop(); log.info("Jetty has stopped."); } catch (Exception ex) { log.error("Error when stopping Jetty: " + ex.getMessage(), ex); } } }.start(); } catch (Exception ex) { log.error("Unable to stop Jetty: " + ex); return false; } return true; } @Override public void handle(String string, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { String pathInfo = request.getPathInfo(); // THIS SHOULD OBVIOUSLY BE SECURED!!! if ("/stop".equals(pathInfo)) { stopServer(response); return; } if ("/restart".equals(pathInfo)) { restartPlease = true; stopServer(response); return; } // Go off and do the rest of your RESTful calls... // And close off how you please response.sendRedirect("http://nowhere.com"); } }
And you can now run your main class from your IDE of choice.
Just for reference I added a log4j.properties:
# Site log4j.logger.com.company=DEBUG, SITE_CONSOLE log4j.additivity.com.company=false # Set root logger level log4j.rootLogger=DEBUG, CONSOLE log4j.logger.org.eclipse=INFO, CONSOLE log4j.additivity.org.eclipse=false # --------------------------------------------------------------------------------- # Appenders - Notice I have added a discriminator to the conversion pattern # --------------------------------------------------------------------------------- # For console logging - not in production obviously as you'd use a rolling file appender log4j.appender.SITE_CONSOLE=org.apache.log4j.ConsoleAppender log4j.appender.SITE_CONSOLE.layout=org.apache.log4j.EnhancedPatternLayout log4j.appender.SITE_CONSOLE.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS}{UTC} SITE %-5p [%t] %c{1.} %x - %m%n # The baseline CONSOLE logger log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender log4j.appender.CONSOLE.layout=org.apache.log4j.EnhancedPatternLayout log4j.appender.CONSOLE.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss,SSS}{UTC} JETTY %-5p [%t] %c{1.} %x - %m%n
I created a plain Java SE app and added a 'lib' folder with these jars in it:
jetty-continuation-8.0.4.v20111024.jar jetty-http-8.0.4.v20111024.jar jetty-io-8.0.4.v20111024.jar jetty-server-8.0.4.v20111024.jar jetty-servlet-8.0.4.v20111024.jar jetty-servlets-8.0.4.v20111024.jar jetty-util-8.0.4.v20111024.jar jetty-webapp-8.0.4.v20111024.jar log4j-1.2.16.jar servlet-api-3.0.jar slf4j-api-1.6.2.jar slf4j-log4j12-1.6.2.jar
From the Jetty8 release.
I then ensured that the MANIFEST included these jars.
Just for reference the META-INF/MANIFEST.MF looks like this:
cat META-INF/MANIFEST.MF Manifest-Version: 1.0 Ant-Version: Apache Ant 1.8.2 Created-By: 1.6.0_29-b11-402-10M3527 (Apple Inc.) Class-Path: lib/jetty-all-8.0.4.v20111024-javadoc.jar lib/jetty-contin uation-8.0.4.v20111024.jar lib/jetty-http-8.0.4.v20111024.jar lib/jet ty-io-8.0.4.v20111024.jar lib/jetty-server-8.0.4.v20111024.jar lib/je tty-servlet-8.0.4.v20111024.jar lib/jetty-servlets-8.0.4.v20111024.ja r lib/jetty-util-8.0.4.v20111024.jar lib/jetty-webapp-8.0.4.v20111024 .jar lib/log4j-1.2.16.jar lib/servlet-api-3.0.jar lib/slf4j-api-1.6.2 .jar lib/slf4j-log4j12-1.6.2.jar X-COMMENT: Main-Class will be added automatically by build Main-Class: com.company.EmbeddedJetty
I know I included the javadoc un-intentionally...
This scheme requires a lib folder alongside the jar with those files in it.
(Need to figure out how to bundle the Jetty8 jars so that the app is self-contained...)
So the jar can be started like this:
java -jar /some/folder/EmbeddedJetty/dist/EmbeddedJetty.jar
K
Thanks for the code snippets - very useful!
ReplyDelete>>Need to figure out how to bundle the Jetty8 jars so that the app is self-contained
I guess the tips from http://eclipsesource.com/blogs/2009/10/02/executable-wars-with-jetty/ will help you out with the last part of making the app self-contained.