diff --git a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/DeploymentManager.java b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/DeploymentManager.java index 5160a2cd503..e15eccaf561 100644 --- a/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/DeploymentManager.java +++ b/jetty-deploy/src/main/java/org/eclipse/jetty/deploy/DeploymentManager.java @@ -40,6 +40,7 @@ import org.eclipse.jetty.deploy.graph.Path; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.handler.ContextHandlerCollection; import org.eclipse.jetty.util.AttributesMap; +import org.eclipse.jetty.util.MultiException; import org.eclipse.jetty.util.annotation.ManagedAttribute; import org.eclipse.jetty.util.annotation.ManagedObject; import org.eclipse.jetty.util.annotation.ManagedOperation; @@ -68,6 +69,7 @@ import org.eclipse.jetty.xml.XmlConfiguration; public class DeploymentManager extends ContainerLifeCycle { private static final Logger LOG = Log.getLogger(DeploymentManager.class); + private MultiException onStartupErrors; /** * Represents a single tracked app within the deployment manager. @@ -237,6 +239,12 @@ public class DeploymentManager extends ContainerLifeCycle { startAppProvider(provider); } + + if (onStartupErrors != null) + { + onStartupErrors.ifExceptionThrow(); + } + super.doStart(); } @@ -519,9 +527,23 @@ public class DeploymentManager extends ContainerLifeCycle // The runBindings failed for 'failed' node LOG.ignore(ignore); } + + if (isStarting()) + { + addOnStartupError(t); + } } } + private synchronized void addOnStartupError(Throwable cause) + { + if(onStartupErrors == null) + { + onStartupErrors = new MultiException(); + } + onStartupErrors.add(cause); + } + /** * Move an {@link App} through the {@link AppLifeCycle} to the desired {@link Node}, executing each lifecycle step * in the process to reach the desired state. diff --git a/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/BadAppDeployTest.java b/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/BadAppDeployTest.java new file mode 100644 index 00000000000..132b5cbe5f1 --- /dev/null +++ b/jetty-deploy/src/test/java/org/eclipse/jetty/deploy/BadAppDeployTest.java @@ -0,0 +1,174 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 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.deploy; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import javax.servlet.ServletException; + +import org.eclipse.jetty.deploy.providers.WebAppProvider; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.server.ServerConnector; +import org.eclipse.jetty.server.handler.ContextHandlerCollection; +import org.eclipse.jetty.server.handler.DefaultHandler; +import org.eclipse.jetty.server.handler.HandlerCollection; +import org.eclipse.jetty.toolchain.test.FS; +import org.eclipse.jetty.toolchain.test.MavenTestingUtils; +import org.eclipse.jetty.toolchain.test.jupiter.WorkDir; +import org.eclipse.jetty.toolchain.test.jupiter.WorkDirExtension; +import org.eclipse.jetty.util.log.Log; +import org.eclipse.jetty.util.log.StacklessLogging; +import org.eclipse.jetty.webapp.WebAppContext; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; + +import static java.time.Duration.ofSeconds; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@ExtendWith(WorkDirExtension.class) +public class BadAppDeployTest +{ + public WorkDir workDir; + private Server server; + + @AfterEach + public void stopServer() throws Exception + { + if (server != null) + { + server.stop(); + } + } + + @Test + public void testBadApp_ThrowOnUnavailableTrue_XmlOrder() throws Exception + { + /* Non-working Bean Order as reported in Issue #3620 + It is important that this Order be maintained for an accurate test case. + ### BEAN: QueuedThreadPool[qtp1327763628]@4f2410ac{STOPPED,8<=0<=200,i=0,r=-1,q=0}[NO_TRY] + ### BEAN: ServerConnector@16f65612{HTTP/1.1,[http/1.1]}{0.0.0.0:8080} + ### BEAN: HandlerCollection@5f150435{STOPPED} + ### BEAN: DeploymentManager@1c53fd30{STOPPED} + */ + + server = new Server(); + ServerConnector connector = new ServerConnector(server); + connector.setPort(0); + server.addConnector(connector); + + ContextHandlerCollection contexts = new ContextHandlerCollection(); + HandlerCollection handlers = new HandlerCollection(); + handlers.addHandler(contexts); + handlers.addHandler(new DefaultHandler()); + server.setHandler(handlers); // this should be done before addBean(deploymentManager) + + DeploymentManager deploymentManager = new DeploymentManager(); + deploymentManager.setContexts(contexts); + WebAppProvider webAppProvider = new WebAppProvider(); + deploymentManager.addAppProvider(webAppProvider); + + Path webappsDir = workDir.getEmptyPathDir().resolve("webapps").toAbsolutePath(); + + FS.ensureDirExists(webappsDir); + + copyTestResource("webapps/badapp/badapp.war", webappsDir.resolve("badapp.war")); + copyTestResource("webapps/badapp/badapp.xml", webappsDir.resolve("badapp.xml")); + + webAppProvider.setMonitoredDirName(webappsDir.toString()); + webAppProvider.setScanInterval(1); + + server.addBean(deploymentManager); // this should be done after setHandler(handlers) + + assertTimeoutPreemptively(ofSeconds(10), () -> { + + try (StacklessLogging ignore = new StacklessLogging(Log.getLogger(WebAppContext.class), + Log.getLogger(DeploymentManager.class), + Log.getLogger("org.eclipse.jetty.server.handler.ContextHandler.badapp"))) + { + ServletException cause = assertThrows(ServletException.class, () -> server.start()); + assertThat(cause.getMessage(), containsString("intentionally")); + assertTrue(server.isFailed(), "Server should be in failed state"); + } + }); + } + + @Test + public void testBadApp_ThrowOnUnavailableTrue_EmbeddedOrder() throws Exception + { + /* Working Bean Order + ### BEAN: QueuedThreadPool[qtp1530388690]@5b37e0d2{STOPPED,8<=0<=200,i=0,r=-1,q=0}[NO_TRY] + ### BEAN: ServerConnector@5e265ba4{HTTP/1.1,[http/1.1]}{0.0.0.0:8080} + ### BEAN: DeploymentManager@3419866c{STOPPED} + ### BEAN: HandlerCollection@63e31ee{STOPPED} + */ + + server = new Server(); + ServerConnector connector = new ServerConnector(server); + connector.setPort(0); + server.addConnector(connector); + + ContextHandlerCollection contexts = new ContextHandlerCollection(); + + DeploymentManager deploymentManager = new DeploymentManager(); + deploymentManager.setContexts(contexts); + WebAppProvider webAppProvider = new WebAppProvider(); + deploymentManager.addAppProvider(webAppProvider); + + Path webappsDir = workDir.getEmptyPathDir().resolve("webapps").toAbsolutePath(); + + FS.ensureDirExists(webappsDir); + + copyTestResource("webapps/badapp/badapp.war", webappsDir.resolve("badapp.war")); + copyTestResource("webapps/badapp/badapp.xml", webappsDir.resolve("badapp.xml")); + + webAppProvider.setMonitoredDirName(webappsDir.toString()); + webAppProvider.setScanInterval(1); + + server.addBean(deploymentManager); // this should be done before setHandler(handlers) + + HandlerCollection handlers = new HandlerCollection(); + handlers.addHandler(contexts); + handlers.addHandler(new DefaultHandler()); + server.setHandler(handlers); // this should be done after addBean(deploymentManager) + + assertTimeoutPreemptively(ofSeconds(10), () -> { + + try (StacklessLogging ignore = new StacklessLogging(Log.getLogger(WebAppContext.class), + Log.getLogger(DeploymentManager.class), + Log.getLogger("org.eclipse.jetty.server.handler.ContextHandler.badapp"))) + { + ServletException cause = assertThrows(ServletException.class, () -> server.start()); + assertThat(cause.getMessage(), containsString("intentionally")); + assertTrue(server.isFailed(), "Server should be in failed state"); + } + }); + } + + private void copyTestResource(String testResourceFile, Path webappsFile) throws IOException + { + Path srcFile = MavenTestingUtils.getTestResourcePathFile(testResourceFile); + Files.copy(srcFile, webappsFile); + } +} diff --git a/jetty-deploy/src/test/resources/webapps/badapp/badapp.war b/jetty-deploy/src/test/resources/webapps/badapp/badapp.war new file mode 100644 index 00000000000..3fc1a60d5fe Binary files /dev/null and b/jetty-deploy/src/test/resources/webapps/badapp/badapp.war differ diff --git a/jetty-deploy/src/test/resources/webapps/badapp/badapp.xml b/jetty-deploy/src/test/resources/webapps/badapp/badapp.xml new file mode 100644 index 00000000000..d085d7b9cec --- /dev/null +++ b/jetty-deploy/src/test/resources/webapps/badapp/badapp.xml @@ -0,0 +1,8 @@ + + + + + /badapp + /badapp.war + true + diff --git a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AbstractProxyServlet.java b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AbstractProxyServlet.java index 115634513f2..5f2ece7cc0d 100644 --- a/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AbstractProxyServlet.java +++ b/jetty-proxy/src/main/java/org/eclipse/jetty/proxy/AbstractProxyServlet.java @@ -28,7 +28,6 @@ import java.util.Locale; import java.util.Set; import java.util.concurrent.Executor; import java.util.concurrent.TimeoutException; - import javax.servlet.AsyncContext; import javax.servlet.ServletConfig; import javax.servlet.ServletContext; @@ -563,13 +562,18 @@ public abstract class AbstractProxyServlet extends HttpServlet boolean aborted = proxyRequest.abort(failure); if (!aborted) { - int status = failure instanceof TimeoutException ? - HttpStatus.REQUEST_TIMEOUT_408 : - HttpStatus.INTERNAL_SERVER_ERROR_500; + int status = clientRequestStatus(failure); sendProxyResponseError(clientRequest, proxyResponse, status); } } + protected int clientRequestStatus(Throwable failure) + { + return failure instanceof TimeoutException ? + HttpStatus.REQUEST_TIMEOUT_408 : + HttpStatus.INTERNAL_SERVER_ERROR_500; + } + protected void onServerResponseHeaders(HttpServletRequest clientRequest, HttpServletResponse proxyResponse, Response serverResponse) { for (HttpField field : serverResponse.getHeaders()) @@ -634,9 +638,7 @@ public abstract class AbstractProxyServlet extends HttpServlet if (_log.isDebugEnabled()) _log.debug(getRequestId(clientRequest) + " proxying failed", failure); - int status = failure instanceof TimeoutException ? - HttpStatus.GATEWAY_TIMEOUT_504 : - HttpStatus.BAD_GATEWAY_502; + int status = proxyResponseStatus(failure); int serverStatus = serverResponse == null ? status : serverResponse.getStatus(); if (expects100Continue(clientRequest) && serverStatus >= HttpStatus.OK_200) status = serverStatus; @@ -644,6 +646,13 @@ public abstract class AbstractProxyServlet extends HttpServlet } + protected int proxyResponseStatus(Throwable failure) + { + return failure instanceof TimeoutException ? + HttpStatus.GATEWAY_TIMEOUT_504 : + HttpStatus.BAD_GATEWAY_502; + } + protected int getRequestId(HttpServletRequest clientRequest) { return System.identityHashCode(clientRequest); diff --git a/jetty-server/src/main/config/modules/ssl.mod b/jetty-server/src/main/config/modules/ssl.mod index 1fa918eadf5..be27d35546f 100644 --- a/jetty-server/src/main/config/modules/ssl.mod +++ b/jetty-server/src/main/config/modules/ssl.mod @@ -58,7 +58,7 @@ etc/jetty-ssl-context.xml ## The Endpoint Identification Algorithm ## Same as javax.net.ssl.SSLParameters#setEndpointIdentificationAlgorithm(String) -#jetty.sslContext.endpointIdentificationAlgorithm=HTTPS +#jetty.sslContext.endpointIdentificationAlgorithm= ## SSL JSSE Provider # jetty.sslContext.provider= diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrSessionListener.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrSessionListener.java new file mode 100644 index 00000000000..c0444899a68 --- /dev/null +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrSessionListener.java @@ -0,0 +1,26 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 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.websocket.jsr356; + +public interface JsrSessionListener +{ + void onSessionOpened(JsrSession session); + + void onSessionClosed(JsrSession session); +} diff --git a/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrSessionTracker.java b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrSessionTracker.java new file mode 100644 index 00000000000..be97c94d05b --- /dev/null +++ b/jetty-websocket/javax-websocket-client-impl/src/main/java/org/eclipse/jetty/websocket/jsr356/JsrSessionTracker.java @@ -0,0 +1,58 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 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.websocket.jsr356; + +import java.util.Collections; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; + +import org.eclipse.jetty.util.component.AbstractLifeCycle; +import org.eclipse.jetty.util.component.LifeCycle; + +public class JsrSessionTracker extends AbstractLifeCycle implements JsrSessionListener +{ + private CopyOnWriteArraySet sessions = new CopyOnWriteArraySet<>(); + + public Set getSessions() + { + return Collections.unmodifiableSet(sessions); + } + + @Override + public void onSessionOpened(JsrSession session) + { + sessions.add(session); + } + + @Override + public void onSessionClosed(JsrSession session) + { + sessions.remove(session); + } + + @Override + protected void doStop() throws Exception + { + for (JsrSession session : sessions) + { + LifeCycle.stop(session); + } + super.doStop(); + } +} \ No newline at end of file diff --git a/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/SessionTracker.java b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/SessionTracker.java new file mode 100644 index 00000000000..f8a5ae08d4a --- /dev/null +++ b/jetty-websocket/websocket-common/src/main/java/org/eclipse/jetty/websocket/common/SessionTracker.java @@ -0,0 +1,58 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 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.websocket.common; + +import java.util.Collections; +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; + +import org.eclipse.jetty.util.component.AbstractLifeCycle; +import org.eclipse.jetty.util.component.LifeCycle; + +public class SessionTracker extends AbstractLifeCycle implements WebSocketSessionListener +{ + private CopyOnWriteArraySet sessions = new CopyOnWriteArraySet<>(); + + public Set getSessions() + { + return Collections.unmodifiableSet(sessions); + } + + @Override + public void onSessionOpened(WebSocketSession session) + { + sessions.add(session); + } + + @Override + public void onSessionClosed(WebSocketSession session) + { + sessions.remove(session); + } + + @Override + protected void doStop() throws Exception + { + for (WebSocketSession session : sessions) + { + LifeCycle.stop(session); + } + super.doStop(); + } +} \ No newline at end of file diff --git a/scripts/release-jetty.sh b/scripts/release-jetty.sh index 697497991b2..eeddd4ab3dd 100755 --- a/scripts/release-jetty.sh +++ b/scripts/release-jetty.sh @@ -158,7 +158,7 @@ if proceedyn "Are you sure you want to release using above? (y/N)" n; then # This is equivalent to 'mvn release:perform' if proceedyn "Build/Deploy from tag $TAG_NAME? (Y/n)" y; then git checkout $TAG_NAME - mvn clean package source:jar javadoc:jar gpg:sign deploy \ + mvn clean package source:jar javadoc:jar gpg:sign javadoc:aggregate-jar deploy \ -Peclipse-release $DEPLOY_OPTS reportMavenTestFailures git checkout $GIT_BRANCH_ID diff --git a/tests/test-distribution/pom.xml b/tests/test-distribution/pom.xml index f6b31cb7895..a6cf1ff70dc 100644 --- a/tests/test-distribution/pom.xml +++ b/tests/test-distribution/pom.xml @@ -78,7 +78,6 @@ org.eclipse.jetty.toolchain jetty-test-helper - test org.eclipse.jetty diff --git a/tests/test-distribution/src/main/java/org/eclipse/jetty/tests/distribution/DistributionTester.java b/tests/test-distribution/src/main/java/org/eclipse/jetty/tests/distribution/DistributionTester.java index 44a2a9edda9..043f61eb6b9 100644 --- a/tests/test-distribution/src/main/java/org/eclipse/jetty/tests/distribution/DistributionTester.java +++ b/tests/test-distribution/src/main/java/org/eclipse/jetty/tests/distribution/DistributionTester.java @@ -62,6 +62,8 @@ import org.eclipse.aether.spi.connector.transport.TransporterFactory; import org.eclipse.aether.transfer.AbstractTransferListener; import org.eclipse.aether.transport.file.FileTransporterFactory; import org.eclipse.aether.transport.http.HttpTransporterFactory; +import org.eclipse.jetty.toolchain.test.FS; +import org.eclipse.jetty.toolchain.test.MavenTestingUtils; import org.eclipse.jetty.util.IO; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; @@ -140,6 +142,9 @@ public class DistributionTester commands.add("-Djava.io.tmpdir=" + workDir.toAbsolutePath().toString()); commands.add("-jar"); commands.add(config.jettyHome.toAbsolutePath() + "/start.jar"); + // we get artifacts from local repo first + args = new ArrayList<>(args); + args.add("maven.local.repo=" + System.getProperty("mavenRepoPath")); commands.addAll(args); LOGGER.info("Executing: {}", commands); @@ -166,6 +171,21 @@ public class DistributionTester } } + /** + * Installs content from {@code src/test/resources/} into {@code ${jetty.base}/} + * + * @param testResourcePath the location of the source file in {@code src/test/resources} + * @param baseResourcePath the location of the destination file in {@code ${jetty.base}} + * @throws IOException if unable to copy file + */ + public void installBaseResource(String testResourcePath, String baseResourcePath) throws IOException + { + Path srcFile = MavenTestingUtils.getTestResourcePath(testResourcePath); + Path destFile = config.jettyBase.resolve(baseResourcePath); + + Files.copy(srcFile, destFile); + } + /** * Installs in {@code ${jetty.base}/webapps} the given war file under the given context path. * @@ -176,7 +196,7 @@ public class DistributionTester public void installWarFile(File warFile, String context) throws IOException { //webapps - Path webapps = Paths.get(config.jettyBase.toString(), "webapps", context); + Path webapps = config.jettyBase.resolve("webapps").resolve(context); if (!Files.exists(webapps)) Files.createDirectories(webapps); unzip(warFile, webapps.toFile()); @@ -213,7 +233,9 @@ public class DistributionTester if (config.jettyBase == null) { - config.jettyBase = Files.createTempDirectory("jetty_base_"); + Path bases = MavenTestingUtils.getTargetTestingPath("bases"); + FS.ensureDirExists(bases); + config.jettyBase = Files.createTempDirectory(bases, "jetty_base_"); } else { @@ -225,12 +247,12 @@ public class DistributionTester private String getJavaExecutable() { String[] javaExecutables = new String[]{"java", "java.exe"}; - File javaHomeDir = new File(System.getProperty("java.home")); + Path javaBinDir = Paths.get(System.getProperty("java.home")).resolve("bin"); for (String javaExecutable : javaExecutables) { - File javaFile = new File(javaHomeDir, "bin" + File.separator + javaExecutable); - if (javaFile.exists() && javaFile.isFile()) - return javaFile.getAbsolutePath(); + Path javaFile = javaBinDir.resolve(javaExecutable); + if (Files.exists(javaFile) && Files.isRegularFile(javaFile)) + return javaFile.toAbsolutePath().toString(); } return "java"; } diff --git a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/BadAppTests.java b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/BadAppTests.java new file mode 100644 index 00000000000..b2cfa38b9e8 --- /dev/null +++ b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/BadAppTests.java @@ -0,0 +1,156 @@ +// +// ======================================================================== +// Copyright (c) 1995-2019 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.tests.distribution; + +import java.util.concurrent.TimeUnit; + +import org.eclipse.jetty.client.api.ContentResponse; +import org.eclipse.jetty.http.HttpStatus; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.not; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +/** + * Tests where the server is started with a Bad App that will fail in its init phase. + */ +public class BadAppTests extends AbstractDistributionTest +{ + /** + * Start a server where a bad webapp is being deployed. + * The badapp.war will throw a ServletException during its deploy/init. + * The badapp.xml contains a {@code true} + * + * It is expected that the server does not start and exits with an error code + */ + @Test + public void testXml_ThrowOnUnavailable_True() throws Exception + { + String jettyVersion = System.getProperty("jettyVersion"); + DistributionTester distribution = DistributionTester.Builder.newInstance() + .jettyVersion(jettyVersion) + .mavenLocalRepository(System.getProperty("mavenRepoPath")) + .build(); + + try (DistributionTester.Run run1 = distribution.start("--add-to-start=http,deploy")) + { + assertTrue(run1.awaitFor(5, TimeUnit.SECONDS)); + assertThat(run1.getExitValue(), is(0)); + + // Setup webapps directory + distribution.installBaseResource("badapp/badapp.war", + "webapps/badapp.war"); + distribution.installBaseResource("badapp/badapp_throwonunavailable_true.xml", + "webapps/badapp.xml"); + + int port = distribution.freePort(); + try (DistributionTester.Run run2 = distribution.start("jetty.http.port=" + port)) + { + assertTrue(run2.awaitFor(5, TimeUnit.SECONDS), "Should have exited"); + assertThat("Should have gotten a non-zero exit code", run2.getExitValue(), not(is(0))); + } + } + } + + /** + * Start a server where a bad webapp is being deployed. + * The badapp.war will throw a ServletException during its deploy/init. + * The badapp.xml contains a {@code false} + * + * It is expected that the server does start and attempts to access the /badapp/ report + * that it is unavailable. + */ + @Test + public void testXml_ThrowOnUnavailable_False() throws Exception + { + String jettyVersion = System.getProperty("jettyVersion"); + DistributionTester distribution = DistributionTester.Builder.newInstance() + .jettyVersion(jettyVersion) + .mavenLocalRepository(System.getProperty("mavenRepoPath")) + .build(); + + try (DistributionTester.Run run1 = distribution.start("--add-to-start=http,deploy")) + { + assertTrue(run1.awaitFor(5, TimeUnit.SECONDS)); + assertThat(run1.getExitValue(), is(0)); + + // Setup webapps directory + distribution.installBaseResource("badapp/badapp.war", + "webapps/badapp.war"); + distribution.installBaseResource("badapp/badapp_throwonunavailable_false.xml", + "webapps/badapp.xml"); + + int port = distribution.freePort(); + try (DistributionTester.Run run2 = distribution.start("jetty.http.port=" + port)) + { + assertTrue(run2.awaitConsoleLogsFor("Started @", 10, TimeUnit.SECONDS)); + + startHttpClient(); + ContentResponse response = client.GET("http://localhost:" + port + "/badapp/"); + assertEquals(HttpStatus.SERVICE_UNAVAILABLE_503, response.getStatus()); + assertThat(response.getContentAsString(), containsString("Unavailable")); + assertThat(response.getContentAsString(), containsString("Problem accessing /badapp/")); + } + } + } + + /** + * Start a server where a bad webapp is being deployed. + * The badapp.war will throw a ServletException during its deploy/init. + * No badapp.xml is used, relying on default values for {@code throwUnavailableOnStartupException} + * + * It is expected that the server does start and attempts to access the /badapp/ report + * that it is unavailable. + */ + @Test + public void testNoXml_ThrowOnUnavailable_Default() throws Exception + { + String jettyVersion = System.getProperty("jettyVersion"); + DistributionTester distribution = DistributionTester.Builder.newInstance() + .jettyVersion(jettyVersion) + .mavenLocalRepository(System.getProperty("mavenRepoPath")) + .build(); + + try (DistributionTester.Run run1 = distribution.start("--add-to-start=http,deploy")) + { + assertTrue(run1.awaitFor(5, TimeUnit.SECONDS)); + assertThat(run1.getExitValue(), is(0)); + + // Setup webapps directory + distribution.installBaseResource("badapp/badapp.war", + "webapps/badapp.war"); + + int port = distribution.freePort(); + try (DistributionTester.Run run2 = distribution.start("jetty.http.port=" + port)) + { + assertTrue(run2.awaitConsoleLogsFor("Started @", 10, TimeUnit.SECONDS)); + + startHttpClient(); + ContentResponse response = client.GET("http://localhost:" + port + "/badapp/"); + assertEquals(HttpStatus.SERVICE_UNAVAILABLE_503, response.getStatus()); + assertThat(response.getContentAsString(), containsString("Unavailable")); + assertThat(response.getContentAsString(), containsString("Problem accessing /badapp/")); + } + } + } +} diff --git a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DistributionTests.java b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DistributionTests.java index 29c2fdeb92b..cb4d9287b45 100644 --- a/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DistributionTests.java +++ b/tests/test-distribution/src/test/java/org/eclipse/jetty/tests/distribution/DistributionTests.java @@ -242,7 +242,8 @@ public class DistributionTests extends AbstractDistributionTest }; try (DistributionTester.Run run1 = distribution.start(args1)) { - assertTrue(run1.awaitFor(5, TimeUnit.SECONDS)); + // Give it time to download the dependencies + assertTrue(run1.awaitFor(30, TimeUnit.SECONDS)); assertEquals(0, run1.getExitValue()); File war = distribution.resolveArtifact("org.eclipse.jetty.tests:test-simple-webapp:war:" + jettyVersion); diff --git a/tests/test-distribution/src/test/resources/badapp/badapp.war b/tests/test-distribution/src/test/resources/badapp/badapp.war new file mode 100644 index 00000000000..3fc1a60d5fe Binary files /dev/null and b/tests/test-distribution/src/test/resources/badapp/badapp.war differ diff --git a/tests/test-distribution/src/test/resources/badapp/badapp_throwonunavailable_false.xml b/tests/test-distribution/src/test/resources/badapp/badapp_throwonunavailable_false.xml new file mode 100644 index 00000000000..54bc161f65b --- /dev/null +++ b/tests/test-distribution/src/test/resources/badapp/badapp_throwonunavailable_false.xml @@ -0,0 +1,8 @@ + + + + + /badapp + /badapp.war + false + diff --git a/tests/test-distribution/src/test/resources/badapp/badapp_throwonunavailable_true.xml b/tests/test-distribution/src/test/resources/badapp/badapp_throwonunavailable_true.xml new file mode 100644 index 00000000000..d085d7b9cec --- /dev/null +++ b/tests/test-distribution/src/test/resources/badapp/badapp_throwonunavailable_true.xml @@ -0,0 +1,8 @@ + + + + + /badapp + /badapp.war + true +