View Javadoc

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