--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.server;
+
+import java.io.IOException;
+import java.io.InputStreamReader;
+import java.io.LineNumberReader;
+import java.io.OutputStream;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.nio.charset.StandardCharsets;
+import java.util.Properties;
+
+import org.eclipse.jetty.util.thread.ShutdownThread;
+
+/**
+ * Shutdown/Stop Monitor thread.
+ * <p>
+ * This thread listens on the port specified by the STOP.PORT system parameter (defaults to -1 for not listening) for request authenticated with the key given
+ * by the STOP.KEY system parameter (defaults to "eclipse") for admin requests.
+ * <p>
+ * If the stop port is set to zero, then a random port is assigned and the port number is printed to stdout.
+ * <p>
+ * Commands "stop" and "status" are currently supported.
+ */
+public class ShutdownMonitor
+{
+ // Implementation of safe lazy init, using Initialization on Demand Holder technique.
+ static class Holder
+ {
+ static ShutdownMonitor instance = new ShutdownMonitor();
+ }
+
+ public static ShutdownMonitor getInstance()
+ {
+ return Holder.instance;
+ }
+
+ /**
+ * ShutdownMonitorThread
+ *
+ * Thread for listening to STOP.PORT for command to stop Jetty.
+ * If ShowndownMonitor.exitVm is true, then Sytem.exit will also be
+ * called after the stop.
+ *
+ */
+ public class ShutdownMonitorThread extends Thread
+ {
+
+ public ShutdownMonitorThread ()
+ {
+ setDaemon(true);
+ setName("ShutdownMonitor");
+ }
+
+ @Override
+ public void run()
+ {
+ if (serverSocket == null)
+ {
+ return;
+ }
+
+ while (serverSocket != null)
+ {
+ Socket socket = null;
+ try
+ {
+ socket = serverSocket.accept();
+
+ LineNumberReader lin = new LineNumberReader(new InputStreamReader(socket.getInputStream()));
+ String receivedKey = lin.readLine();
+ if (!key.equals(receivedKey))
+ {
+ System.err.println("Ignoring command with incorrect key");
+ continue;
+ }
+
+ OutputStream out = socket.getOutputStream();
+
+ String cmd = lin.readLine();
+ debug("command=%s",cmd);
+ if ("stop".equals(cmd))
+ {
+ // Graceful Shutdown
+ debug("Issuing graceful shutdown..");
+ ShutdownThread.getInstance().run();
+
+ //Stop accepting any more
+ close(serverSocket);
+ serverSocket = null;
+
+ //Shutdown input from client
+ shutdownInput(socket);
+
+ // Reply to client
+ debug("Informing client that we are stopped.");
+ out.write("Stopped\r\n".getBytes(StandardCharsets.UTF_8));
+ out.flush();
+
+ // Shutdown Monitor
+ socket.shutdownOutput();
+ close(socket);
+ socket = null;
+ debug("Shutting down monitor");
+
+ if (exitVm)
+ {
+ // Kill JVM
+ debug("Killing JVM");
+ System.exit(0);
+ }
+ }
+ else if ("status".equals(cmd))
+ {
+ // Reply to client
+ out.write("OK\r\n".getBytes(StandardCharsets.UTF_8));
+ out.flush();
+ }
+ }
+ catch (Exception e)
+ {
+ debug(e);
+ System.err.println(e.toString());
+ }
+ finally
+ {
+ close(socket);
+ socket = null;
+ }
+ }
+ }
+
+ public void start()
+ {
+ if (isAlive())
+ {
+ // TODO why are we reentrant here?
+ if (DEBUG)
+ System.err.printf("ShutdownMonitorThread already started");
+ return; // cannot start it again
+ }
+
+ startListenSocket();
+
+ if (serverSocket == null)
+ {
+ return;
+ }
+ if (DEBUG)
+ System.err.println("Starting ShutdownMonitorThread");
+ super.start();
+ }
+
+ private void startListenSocket()
+ {
+ if (port < 0)
+ {
+ if (DEBUG)
+ System.err.println("ShutdownMonitor not in use (port < 0): " + port);
+ return;
+ }
+
+ try
+ {
+ serverSocket = new ServerSocket(port,1,InetAddress.getByName("127.0.0.1"));
+ if (port == 0)
+ {
+ // server assigned port in use
+ port = serverSocket.getLocalPort();
+ System.out.printf("STOP.PORT=%d%n",port);
+ }
+
+ if (key == null)
+ {
+ // create random key
+ key = Long.toString((long)(Long.MAX_VALUE * Math.random() + this.hashCode() + System.currentTimeMillis()),36);
+ System.out.printf("STOP.KEY=%s%n",key);
+ }
+ }
+ catch (Exception e)
+ {
+ debug(e);
+ System.err.println("Error binding monitor port " + port + ": " + e.toString());
+ serverSocket = null;
+ }
+ finally
+ {
+ // establish the port and key that are in use
+ debug("STOP.PORT=%d",port);
+ debug("STOP.KEY=%s",key);
+ debug("%s",serverSocket);
+ }
+ }
+
+ }
+
+ private boolean DEBUG;
+ private int port;
+ private String key;
+ private boolean exitVm;
+ private ServerSocket serverSocket;
+ private ShutdownMonitorThread thread;
+
+
+
+ /**
+ * Create a ShutdownMonitor using configuration from the System properties.
+ * <p>
+ * <code>STOP.PORT</code> = the port to listen on (empty, null, or values less than 0 disable the stop ability)<br>
+ * <code>STOP.KEY</code> = the magic key/passphrase to allow the stop (defaults to "eclipse")<br>
+ * <p>
+ * Note: server socket will only listen on localhost, and a successful stop will issue a System.exit() call.
+ */
+ private ShutdownMonitor()
+ {
+ Properties props = System.getProperties();
+
+ this.DEBUG = props.containsKey("DEBUG");
+
+ // Use values passed thru via /jetty-start/
+ this.port = Integer.parseInt(props.getProperty("STOP.PORT","-1"));
+ this.key = props.getProperty("STOP.KEY",null);
+ this.exitVm = true;
+ }
+
+ private void close(ServerSocket server)
+ {
+ if (server == null)
+ {
+ return;
+ }
+
+ try
+ {
+ server.close();
+ }
+ catch (IOException ignore)
+ {
+ debug(ignore);
+ }
+ }
+
+ private void close(Socket socket)
+ {
+ if (socket == null)
+ {
+ return;
+ }
+
+ try
+ {
+ socket.close();
+ }
+ catch (IOException ignore)
+ {
+ debug(ignore);
+ }
+ }
+
+
+ private void shutdownInput(Socket socket)
+ {
+ if (socket == null)
+ return;
+
+ try
+ {
+ socket.shutdownInput();
+ }
+ catch (IOException ignore)
+ {
+ debug(ignore);
+ }
+ }
+
+
+ private void debug(String format, Object... args)
+ {
+ if (DEBUG)
+ {
+ System.err.printf("[ShutdownMonitor] " + format + "%n",args);
+ }
+ }
+
+ private void debug(Throwable t)
+ {
+ if (DEBUG)
+ {
+ t.printStackTrace(System.err);
+ }
+ }
+
+ public String getKey()
+ {
+ return key;
+ }
+
+ public int getPort()
+ {
+ return port;
+ }
+
+ public ServerSocket getServerSocket()
+ {
+ return serverSocket;
+ }
+
+ public boolean isExitVm()
+ {
+ return exitVm;
+ }
+
+
+ public void setDebug(boolean flag)
+ {
+ this.DEBUG = flag;
+ }
+
+ public void setExitVm(boolean exitVm)
+ {
+ synchronized (this)
+ {
+ if (thread != null && thread.isAlive())
+ {
+ throw new IllegalStateException("ShutdownMonitorThread already started");
+ }
+ this.exitVm = exitVm;
+ }
+ }
+
+ public void setKey(String key)
+ {
+ synchronized (this)
+ {
+ if (thread != null && thread.isAlive())
+ {
+ throw new IllegalStateException("ShutdownMonitorThread already started");
+ }
+ this.key = key;
+ }
+ }
+
+ public void setPort(int port)
+ {
+ synchronized (this)
+ {
+ if (thread != null && thread.isAlive())
+ {
+ throw new IllegalStateException("ShutdownMonitorThread already started");
+ }
+ this.port = port;
+ }
+ }
+
+ protected void start() throws Exception
+ {
+ ShutdownMonitorThread t = null;
+ synchronized (this)
+ {
+ if (thread != null && thread.isAlive())
+ {
+ // TODO why are we reentrant here?
+ if (DEBUG)
+ System.err.printf("ShutdownMonitorThread already started");
+ return; // cannot start it again
+ }
+
+ thread = new ShutdownMonitorThread();
+ t = thread;
+ }
+
+ if (t != null)
+ t.start();
+ }
+
+
+ protected boolean isAlive ()
+ {
+ boolean result = false;
+ synchronized (this)
+ {
+ result = (thread != null && thread.isAlive());
+ }
+ return result;
+ }
+
+
+ @Override
+ public String toString()
+ {
+ return String.format("%s[port=%d]",this.getClass().getName(),port);
+ }
+}