From 37845a1a4fcedd6f583d0de74d3dfbc629f6b0f2 Mon Sep 17 00:00:00 2001 From: Thomas Becker Date: Fri, 30 Sep 2011 11:54:35 +0200 Subject: [PATCH] 357687: Add ShutdownHandler + UnitTest --- jetty-server/pom.xml | 6 + .../jetty/server/handler/ShutdownHandler.java | 125 ++++++++++++++++++ .../server/handler/ShutdownHandlerTest.java | 67 ++++++++++ pom.xml | 5 + 4 files changed, 203 insertions(+) create mode 100644 jetty-server/src/main/java/org/eclipse/jetty/server/handler/ShutdownHandler.java create mode 100644 jetty-server/src/test/java/org/eclipse/jetty/server/handler/ShutdownHandlerTest.java diff --git a/jetty-server/pom.xml b/jetty-server/pom.xml index 13f653d8f21..edd48f040e1 100644 --- a/jetty-server/pom.xml +++ b/jetty-server/pom.xml @@ -107,5 +107,11 @@ ${project.version} true + + org.mockito + mockito-core + 1.8.5 + test + diff --git a/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ShutdownHandler.java b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ShutdownHandler.java new file mode 100644 index 00000000000..a0edc4041c3 --- /dev/null +++ b/jetty-server/src/main/java/org/eclipse/jetty/server/handler/ShutdownHandler.java @@ -0,0 +1,125 @@ +// ======================================================================== +// Copyright (c) 2009-2009 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.handler; + +import java.io.IOException; + +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.server.Request; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.Logger; + +/* ------------------------------------------------------------ */ +/** + * A handler that shuts the server down on a valid request. Used to do "soft" restarts from Java. This handler is a contribution from Johannes Brodwall: + * https://bugs.eclipse.org/bugs/show_bug.cgi?id=357687 + * + * Usage: + * + *
+ * Server server = new Server(8080);
+ * HandlerList handlers = new HandlerList();
+ * handlers.setHandlers(new Handler[]
+ * { someOtherHandler, new ShutdownHandler(server,"secret password") });
+ * server.setHandler(handlers);
+ * server.start();
+ * 
+ */ +public class ShutdownHandler extends AbstractHandler +{ + private static final Logger LOG = Log.getLogger(ShutdownHandler.class); + + private final String shutdownToken; + + private final Server jettyServer; + + private boolean exitJvm = false; + + /** + * Creates a listener that lets the server be shut down remotely (but only from localhost). + * + * @param server + * the Jetty instance that should be shut down + * @param shutdownToken + * a secret password to avoid unauthorized shutdown attempts + */ + public ShutdownHandler(Server server, String shutdownToken) + { + this.jettyServer = server; + this.shutdownToken = shutdownToken; + } + + public void handle(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException + { + if (!target.equals("/shutdown")) + { + return; + } + + if (!request.getMethod().equals("POST")) + { + response.sendError(HttpServletResponse.SC_BAD_REQUEST); + return; + } + if (!hasCorrectSecurityToken(request)) + { + LOG.warn("Unauthorized shutdown attempt from " + getRemoteAddr(request)); + response.sendError(HttpServletResponse.SC_UNAUTHORIZED); + return; + } + if (!requestFromLocalhost(request)) + { + LOG.warn("Unauthorized shutdown attempt from " + getRemoteAddr(request)); + response.sendError(HttpServletResponse.SC_UNAUTHORIZED); + return; + } + + LOG.info("Shutting down by request from " + getRemoteAddr(request)); + try + { + shutdownServer(); + } + catch (Exception e) + { + throw new RuntimeException("Shutting down server",e); + } + } + + private boolean requestFromLocalhost(HttpServletRequest request) + { + return "127.0.0.1".equals(getRemoteAddr(request)); + } + + protected String getRemoteAddr(HttpServletRequest request) + { + return request.getRemoteAddr(); + } + + private boolean hasCorrectSecurityToken(HttpServletRequest request) + { + return shutdownToken.equals(request.getParameter("token")); + } + + void shutdownServer() throws Exception + { + jettyServer.stop(); + if (exitJvm) + System.exit(0); + } + +} diff --git a/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ShutdownHandlerTest.java b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ShutdownHandlerTest.java new file mode 100644 index 00000000000..0f274eca92e --- /dev/null +++ b/jetty-server/src/test/java/org/eclipse/jetty/server/handler/ShutdownHandlerTest.java @@ -0,0 +1,67 @@ +package org.eclipse.jetty.server.handler; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.when; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.eclipse.jetty.server.Server; +import org.junit.Before; +import org.junit.Test; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +public class ShutdownHandlerTest +{ + @Mock private HttpServletRequest request; + @Mock private HttpServletResponse response; + + private Server server = new Server(0); + private String shutdownToken = "asdlnsldgnklns"; + + // class under test + private ShutdownHandler shutdownHandler; + + @Before + public void startServer() throws Exception + { + MockitoAnnotations.initMocks(this); + server.start(); + shutdownHandler = new ShutdownHandler(server,shutdownToken); + } + + @Test + public void shutdownServerWithCorrectTokenAndIPTest() throws Exception + { + setDefaultExpectations(); + shutdownHandler.handle("/shutdown",null,request,response); + assertEquals("Server should be stopped","STOPPED",server.getState()); + } + + @Test + public void wrongTokenTest() throws Exception + { + setDefaultExpectations(); + when(request.getParameter("token")).thenReturn("anothertoken"); + shutdownHandler.handle("/shutdown",null,request,response); + assertEquals("Server should be running","STARTED",server.getState()); + } + + @Test + public void shutdownRequestNotFromLocalhostTest() throws Exception + { + setDefaultExpectations(); + when(request.getRemoteAddr()).thenReturn("192.168.3.3"); + shutdownHandler.handle("/shutdown",null,request,response); + assertEquals("Server should be running","STARTED",server.getState()); + } + + private void setDefaultExpectations() + { + when(request.getMethod()).thenReturn("POST"); + when(request.getParameter("token")).thenReturn(shutdownToken); + when(request.getRemoteAddr()).thenReturn("127.0.0.1"); + } + +} diff --git a/pom.xml b/pom.xml index 92439b310e3..e37c2c4229b 100644 --- a/pom.xml +++ b/pom.xml @@ -407,6 +407,11 @@ junit ${junit-version} + + org.mockito + mockito-core + 1.8.5 +