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.