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