View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2015 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.handler;
20  
21  import java.io.IOException;
22  import java.net.HttpURLConnection;
23  import java.net.InetSocketAddress;
24  import java.net.SocketException;
25  import java.net.URL;
26  
27  import javax.servlet.ServletException;
28  import javax.servlet.http.HttpServletRequest;
29  import javax.servlet.http.HttpServletResponse;
30  
31  import org.eclipse.jetty.server.Connector;
32  import org.eclipse.jetty.server.NetworkConnector;
33  import org.eclipse.jetty.server.Request;
34  import org.eclipse.jetty.server.Server;
35  import org.eclipse.jetty.util.log.Log;
36  import org.eclipse.jetty.util.log.Logger;
37  
38  /* ------------------------------------------------------------ */
39  /**
40   * A handler that shuts the server down on a valid request. Used to do "soft" restarts from Java. If _exitJvm ist set to true a hard System.exit() call is being
41   * made.
42   *
43   * This handler is a contribution from Johannes Brodwall: https://bugs.eclipse.org/bugs/show_bug.cgi?id=357687
44   *
45   * Usage:
46   *
47   * <pre>
48      Server server = new Server(8080);
49      HandlerList handlers = new HandlerList();
50      handlers.setHandlers(new Handler[]
51      { someOtherHandler, new ShutdownHandler(&quot;secret password&quot;) });
52      server.setHandler(handlers);
53      server.start();
54     </pre>
55   *
56     <pre>
57     public static void attemptShutdown(int port, String shutdownCookie) {
58          try {
59              URL url = new URL("http://localhost:" + port + "/shutdown?token=" + shutdownCookie);
60              HttpURLConnection connection = (HttpURLConnection)url.openConnection();
61              connection.setRequestMethod("POST");
62              connection.getResponseCode();
63              logger.info("Shutting down " + url + ": " + connection.getResponseMessage());
64          } catch (SocketException e) {
65              logger.debug("Not running");
66              // Okay - the server is not running
67          } catch (IOException e) {
68              throw new RuntimeException(e);
69          }
70      }
71    </pre>
72   */
73  public class ShutdownHandler extends HandlerWrapper
74  {
75      private static final Logger LOG = Log.getLogger(ShutdownHandler.class);
76  
77      private final String _shutdownToken;
78      private boolean _sendShutdownAtStart;
79      private boolean _exitJvm = false;
80  
81  
82      /**
83       * Creates a listener that lets the server be shut down remotely (but only from localhost).
84       *
85       * @param server
86       *            the Jetty instance that should be shut down
87       * @param shutdownToken
88       *            a secret password to avoid unauthorized shutdown attempts
89       */
90      @Deprecated
91      public ShutdownHandler(Server server, String shutdownToken)
92      {
93          this(shutdownToken);
94      }
95  
96      public ShutdownHandler(String shutdownToken)
97      {
98          this(shutdownToken,false,false);
99      }
100     
101     /**
102      * @param shutdownToken
103      * @param sendShutdownAtStart If true, a shutdown is sent as a HTTP post
104      * during startup, which will shutdown any previously running instances of
105      * this server with an identically configured ShutdownHandler
106      */
107     public ShutdownHandler(String shutdownToken, boolean exitJVM, boolean sendShutdownAtStart)
108     {
109         this._shutdownToken = shutdownToken;
110         setExitJvm(exitJVM);
111         setSendShutdownAtStart(sendShutdownAtStart);
112     }
113     
114 
115     public void sendShutdown() throws IOException
116     {
117         URL url = new URL(getServerUrl() + "/shutdown?token=" + _shutdownToken);
118         try
119         {
120             HttpURLConnection connection = (HttpURLConnection)url.openConnection();
121             connection.setRequestMethod("POST");
122             connection.getResponseCode();
123             LOG.info("Shutting down " + url + ": " + connection.getResponseMessage());
124         }
125         catch (SocketException e)
126         {
127             LOG.debug("Not running");
128             // Okay - the server is not running
129         }
130         catch (IOException e)
131         {
132             throw new RuntimeException(e);
133         }
134     }
135 
136     @SuppressWarnings("resource")
137     private String getServerUrl()
138     {
139         NetworkConnector connector=null;
140         for (Connector c: getServer().getConnectors())
141         {
142             if (c instanceof NetworkConnector)
143             {
144                 connector=(NetworkConnector)c;
145                 break;
146             }
147         }
148 
149         if (connector==null)
150             return "http://localhost";
151 
152         return "http://localhost:" + connector.getPort();
153     }
154     
155     
156     @Override
157     protected void doStart() throws Exception
158     {
159         super.doStart();
160         if (_sendShutdownAtStart)
161             sendShutdown();
162     }
163     
164     @Override
165     public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
166     {
167         if (!target.equals("/shutdown"))
168         {
169             super.handle(target,baseRequest,request,response);
170             return;
171         }
172 
173         if (!request.getMethod().equals("POST"))
174         {
175             response.sendError(HttpServletResponse.SC_BAD_REQUEST);
176             return;
177         }
178         if (!hasCorrectSecurityToken(request))
179         {
180             LOG.warn("Unauthorized tokenless shutdown attempt from " + request.getRemoteAddr());
181             response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
182             return;
183         }
184         if (!requestFromLocalhost(baseRequest))
185         {
186             LOG.warn("Unauthorized non-loopback shutdown attempt from " + request.getRemoteAddr());
187             response.sendError(HttpServletResponse.SC_UNAUTHORIZED);
188             return;
189         }
190 
191         LOG.info("Shutting down by request from " + request.getRemoteAddr());
192 
193         final Server server=getServer();
194         new Thread()
195         {
196             @Override
197             public void run ()
198             {
199                 try
200                 {
201                     shutdownServer(server);
202                 }
203                 catch (InterruptedException e)
204                 {
205                     LOG.ignore(e);
206                 }
207                 catch (Exception e)
208                 {
209                     throw new RuntimeException("Shutting down server",e);
210                 }
211             }
212         }.start();
213     }
214 
215     private boolean requestFromLocalhost(Request request)
216     {
217         InetSocketAddress addr = request.getRemoteInetSocketAddress();
218         if (addr == null)
219         {
220             return false;
221         }
222         return addr.getAddress().isLoopbackAddress();
223     }
224 
225     private boolean hasCorrectSecurityToken(HttpServletRequest request)
226     {
227         String tok = request.getParameter("token");
228         if (LOG.isDebugEnabled())
229             LOG.debug("Token: {}", tok);
230         return _shutdownToken.equals(tok);
231     }
232 
233     private void shutdownServer(Server server) throws Exception
234     {
235         server.stop();
236 
237         if (_exitJvm)
238         {
239             System.exit(0);
240         }
241     }
242 
243     public void setExitJvm(boolean exitJvm)
244     {
245         this._exitJvm = exitJvm;
246     }
247 
248     public boolean isSendShutdownAtStart()
249     {
250         return _sendShutdownAtStart;
251     }
252 
253     public void setSendShutdownAtStart(boolean sendShutdownAtStart)
254     {
255         _sendShutdownAtStart = sendShutdownAtStart;
256     }
257 
258     public String getShutdownToken()
259     {
260         return _shutdownToken;
261     }
262 
263     public boolean isExitJvm()
264     {
265         return _exitJvm;
266     }
267 
268 }