Wednesday, 11 January 2012

Stopping and/or Restarting an embedded Jetty instance via web call


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

1 comment:

  1. Thanks for the code snippets - very useful!

    >>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.

    ReplyDelete