1 // 2 // ======================================================================== 3 // Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd. 4 // ------------------------------------------------------------------------ 5 // All rights reserved. This program and the accompanying materials 6 // are made available under the terms of the Eclipse Public License v1.0 7 // and Apache License v2.0 which accompanies this distribution. 8 // 9 // The Eclipse Public License is available at 10 // http://www.eclipse.org/legal/epl-v10.html 11 // 12 // The Apache License v2.0 is available at 13 // http://www.opensource.org/licenses/apache2.0.php 14 // 15 // You may elect to redistribute this code under either of these licenses. 16 // ======================================================================== 17 // 18 19 package org.eclipse.jetty.server; 20 21 import java.io.IOException; 22 import java.io.InputStreamReader; 23 import java.io.LineNumberReader; 24 import java.io.OutputStream; 25 import java.net.InetAddress; 26 import java.net.ServerSocket; 27 import java.net.Socket; 28 import java.util.Properties; 29 30 import org.eclipse.jetty.util.StringUtil; 31 import org.eclipse.jetty.util.thread.ShutdownThread; 32 33 /** 34 * Shutdown/Stop Monitor thread. 35 * <p> 36 * 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 37 * by the STOP.KEY system parameter (defaults to "eclipse") for admin requests. 38 * <p> 39 * If the stop port is set to zero, then a random port is assigned and the port number is printed to stdout. 40 * <p> 41 * Commands "stop" and "status" are currently supported. 42 */ 43 public class ShutdownMonitor 44 { 45 // Implementation of safe lazy init, using Initialization on Demand Holder technique. 46 static class Holder 47 { 48 static ShutdownMonitor instance = new ShutdownMonitor(); 49 } 50 getInstance()51 public static ShutdownMonitor getInstance() 52 { 53 return Holder.instance; 54 } 55 56 /** 57 * ShutdownMonitorThread 58 * 59 * Thread for listening to STOP.PORT for command to stop Jetty. 60 * If ShowndownMonitor.exitVm is true, then Sytem.exit will also be 61 * called after the stop. 62 * 63 */ 64 public class ShutdownMonitorThread extends Thread 65 { 66 ShutdownMonitorThread()67 public ShutdownMonitorThread () 68 { 69 setDaemon(true); 70 setName("ShutdownMonitor"); 71 } 72 73 @Override run()74 public void run() 75 { 76 if (serverSocket == null) 77 { 78 return; 79 } 80 81 while (serverSocket != null) 82 { 83 Socket socket = null; 84 try 85 { 86 socket = serverSocket.accept(); 87 88 LineNumberReader lin = new LineNumberReader(new InputStreamReader(socket.getInputStream())); 89 String receivedKey = lin.readLine(); 90 if (!key.equals(receivedKey)) 91 { 92 System.err.println("Ignoring command with incorrect key"); 93 continue; 94 } 95 96 OutputStream out = socket.getOutputStream(); 97 98 String cmd = lin.readLine(); 99 debug("command=%s",cmd); 100 if ("stop".equals(cmd)) 101 { 102 // Graceful Shutdown 103 debug("Issuing graceful shutdown.."); 104 ShutdownThread.getInstance().run(); 105 106 // Reply to client 107 debug("Informing client that we are stopped."); 108 out.write("Stopped\r\n".getBytes(StringUtil.__UTF8)); 109 out.flush(); 110 111 // Shutdown Monitor 112 debug("Shutting down monitor"); 113 close(socket); 114 socket = null; 115 close(serverSocket); 116 serverSocket = null; 117 118 if (exitVm) 119 { 120 // Kill JVM 121 debug("Killing JVM"); 122 System.exit(0); 123 } 124 } 125 else if ("status".equals(cmd)) 126 { 127 // Reply to client 128 out.write("OK\r\n".getBytes(StringUtil.__UTF8)); 129 out.flush(); 130 } 131 } 132 catch (Exception e) 133 { 134 debug(e); 135 System.err.println(e.toString()); 136 } 137 finally 138 { 139 close(socket); 140 socket = null; 141 } 142 } 143 } 144 start()145 public void start() 146 { 147 if (isAlive()) 148 { 149 System.err.printf("ShutdownMonitorThread already started"); 150 return; // cannot start it again 151 } 152 153 startListenSocket(); 154 155 if (serverSocket == null) 156 { 157 return; 158 } 159 if (DEBUG) 160 System.err.println("Starting ShutdownMonitorThread"); 161 super.start(); 162 } 163 startListenSocket()164 private void startListenSocket() 165 { 166 if (port < 0) 167 { 168 if (DEBUG) 169 System.err.println("ShutdownMonitor not in use (port < 0): " + port); 170 return; 171 } 172 173 try 174 { 175 serverSocket = new ServerSocket(port,1,InetAddress.getByName("127.0.0.1")); 176 if (port == 0) 177 { 178 // server assigned port in use 179 port = serverSocket.getLocalPort(); 180 System.out.printf("STOP.PORT=%d%n",port); 181 } 182 183 if (key == null) 184 { 185 // create random key 186 key = Long.toString((long)(Long.MAX_VALUE * Math.random() + this.hashCode() + System.currentTimeMillis()),36); 187 System.out.printf("STOP.KEY=%s%n",key); 188 } 189 } 190 catch (Exception e) 191 { 192 debug(e); 193 System.err.println("Error binding monitor port " + port + ": " + e.toString()); 194 serverSocket = null; 195 } 196 finally 197 { 198 // establish the port and key that are in use 199 debug("STOP.PORT=%d",port); 200 debug("STOP.KEY=%s",key); 201 debug("%s",serverSocket); 202 } 203 } 204 205 } 206 207 private boolean DEBUG; 208 private int port; 209 private String key; 210 private boolean exitVm; 211 private ServerSocket serverSocket; 212 private ShutdownMonitorThread thread; 213 214 215 216 /** 217 * Create a ShutdownMonitor using configuration from the System properties. 218 * <p> 219 * <code>STOP.PORT</code> = the port to listen on (empty, null, or values less than 0 disable the stop ability)<br> 220 * <code>STOP.KEY</code> = the magic key/passphrase to allow the stop (defaults to "eclipse")<br> 221 * <p> 222 * Note: server socket will only listen on localhost, and a successful stop will issue a System.exit() call. 223 */ ShutdownMonitor()224 private ShutdownMonitor() 225 { 226 Properties props = System.getProperties(); 227 228 this.DEBUG = props.containsKey("DEBUG"); 229 230 // Use values passed thru via /jetty-start/ 231 this.port = Integer.parseInt(props.getProperty("STOP.PORT","-1")); 232 this.key = props.getProperty("STOP.KEY",null); 233 this.exitVm = true; 234 } 235 close(ServerSocket server)236 private void close(ServerSocket server) 237 { 238 if (server == null) 239 { 240 return; 241 } 242 243 try 244 { 245 server.close(); 246 } 247 catch (IOException ignore) 248 { 249 /* ignore */ 250 } 251 } 252 close(Socket socket)253 private void close(Socket socket) 254 { 255 if (socket == null) 256 { 257 return; 258 } 259 260 try 261 { 262 socket.close(); 263 } 264 catch (IOException ignore) 265 { 266 /* ignore */ 267 } 268 } 269 debug(String format, Object... args)270 private void debug(String format, Object... args) 271 { 272 if (DEBUG) 273 { 274 System.err.printf("[ShutdownMonitor] " + format + "%n",args); 275 } 276 } 277 debug(Throwable t)278 private void debug(Throwable t) 279 { 280 if (DEBUG) 281 { 282 t.printStackTrace(System.err); 283 } 284 } 285 getKey()286 public String getKey() 287 { 288 return key; 289 } 290 getPort()291 public int getPort() 292 { 293 return port; 294 } 295 getServerSocket()296 public ServerSocket getServerSocket() 297 { 298 return serverSocket; 299 } 300 isExitVm()301 public boolean isExitVm() 302 { 303 return exitVm; 304 } 305 306 setDebug(boolean flag)307 public void setDebug(boolean flag) 308 { 309 this.DEBUG = flag; 310 } 311 setExitVm(boolean exitVm)312 public void setExitVm(boolean exitVm) 313 { 314 synchronized (this) 315 { 316 if (thread != null && thread.isAlive()) 317 { 318 throw new IllegalStateException("ShutdownMonitorThread already started"); 319 } 320 this.exitVm = exitVm; 321 } 322 } 323 setKey(String key)324 public void setKey(String key) 325 { 326 synchronized (this) 327 { 328 if (thread != null && thread.isAlive()) 329 { 330 throw new IllegalStateException("ShutdownMonitorThread already started"); 331 } 332 this.key = key; 333 } 334 } 335 setPort(int port)336 public void setPort(int port) 337 { 338 synchronized (this) 339 { 340 if (thread != null && thread.isAlive()) 341 { 342 throw new IllegalStateException("ShutdownMonitorThread already started"); 343 } 344 this.port = port; 345 } 346 } 347 start()348 protected void start() throws Exception 349 { 350 ShutdownMonitorThread t = null; 351 synchronized (this) 352 { 353 if (thread != null && thread.isAlive()) 354 { 355 System.err.printf("ShutdownMonitorThread already started"); 356 return; // cannot start it again 357 } 358 359 thread = new ShutdownMonitorThread(); 360 t = thread; 361 } 362 363 if (t != null) 364 t.start(); 365 } 366 367 isAlive()368 protected boolean isAlive () 369 { 370 boolean result = false; 371 synchronized (this) 372 { 373 result = (thread != null && thread.isAlive()); 374 } 375 return result; 376 } 377 378 379 @Override toString()380 public String toString() 381 { 382 return String.format("%s[port=%d]",this.getClass().getName(),port); 383 } 384 } 385