Commit 564207b0 authored by Andy Wilkinson's avatar Andy Wilkinson

Add Tomcat-specific failure analysis for connector start failures

Previously, when a Tomcat connector failed to start it was assumed that
the failure was due to the port being in use and a PortInUseException
was thrown. Unfortunately, this assumption doesn’t always hold true.
For example, a Tomcat connector will also fail to start when its using
SSL and the key store password is wrong. This could lead to incorrect
guidance from the PortInUseFailureAnalyzer indicating that a port clash
had occurred when, in fact, it was the SSL configuration that needed to
be corrected.

Unfortunately, Tomcat only tells us that the connector failed to start.
It doesn’t provide access to the exception that would allow us to
determine why it failed to start. This commit updates the embedded
Tomcat container to throw a ConnectorStartFailedException in the event
of a connector failing to start. A new failure analyser,
ConnectorStartFailureAnalyzer, has been introduced to analyse the new
exception and offer some more general guidance.

Closes gh-6896
parent 1641bb21
/*
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.context.embedded.tomcat;
import org.apache.catalina.connector.Connector;
import org.springframework.boot.context.embedded.EmbeddedServletContainerException;
/**
* A {@code ConnectorStartFailedException} is thrown when a Tomcat {@link Connector} fails
* to start, for example due to a port clash or incorrect SSL configuration.
*
* @author Andy Wilkinson
* @since 1.4.1
*/
public class ConnectorStartFailedException extends EmbeddedServletContainerException {
private final int port;
/**
* Creates a new {@code ConnectorStartFailedException} for a connector that's
* configured to listen on the given {@code port}.
*
* @param port the port
*/
public ConnectorStartFailedException(int port) {
super("Connector configured to listen on port " + port + " failed to start",
null);
this.port = port;
}
public int getPort() {
return this.port;
}
}
...@@ -36,7 +36,6 @@ import org.apache.naming.ContextBindings; ...@@ -36,7 +36,6 @@ import org.apache.naming.ContextBindings;
import org.springframework.boot.context.embedded.EmbeddedServletContainer; import org.springframework.boot.context.embedded.EmbeddedServletContainer;
import org.springframework.boot.context.embedded.EmbeddedServletContainerException; import org.springframework.boot.context.embedded.EmbeddedServletContainerException;
import org.springframework.boot.context.embedded.PortInUseException;
import org.springframework.util.Assert; import org.springframework.util.Assert;
/** /**
...@@ -185,7 +184,7 @@ public class TomcatEmbeddedServletContainer implements EmbeddedServletContainer ...@@ -185,7 +184,7 @@ public class TomcatEmbeddedServletContainer implements EmbeddedServletContainer
TomcatEmbeddedServletContainer.logger TomcatEmbeddedServletContainer.logger
.info("Tomcat started on port(s): " + getPortsDescription(true)); .info("Tomcat started on port(s): " + getPortsDescription(true));
} }
catch (PortInUseException ex) { catch (ConnectorStartFailedException ex) {
stopSilently(); stopSilently();
throw ex; throw ex;
} }
...@@ -203,7 +202,7 @@ public class TomcatEmbeddedServletContainer implements EmbeddedServletContainer ...@@ -203,7 +202,7 @@ public class TomcatEmbeddedServletContainer implements EmbeddedServletContainer
private void checkThatConnectorsHaveStarted() { private void checkThatConnectorsHaveStarted() {
for (Connector connector : this.tomcat.getService().findConnectors()) { for (Connector connector : this.tomcat.getService().findConnectors()) {
if (LifecycleState.FAILED.equals(connector.getState())) { if (LifecycleState.FAILED.equals(connector.getState())) {
throw new PortInUseException(connector.getPort()); throw new ConnectorStartFailedException(connector.getPort());
} }
} }
} }
......
/*
* Copyright 2012-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.boot.diagnostics.analyzer;
import org.springframework.boot.context.embedded.tomcat.ConnectorStartFailedException;
import org.springframework.boot.diagnostics.AbstractFailureAnalyzer;
import org.springframework.boot.diagnostics.FailureAnalysis;
/**
* An {@link AbstractFailureAnalyzer} for {@link ConnectorStartFailedException}.
*
* @author Andy Wilkinson
*/
class ConnectorStartFailureAnalyzer
extends AbstractFailureAnalyzer<ConnectorStartFailedException> {
@Override
protected FailureAnalysis analyze(Throwable rootFailure,
ConnectorStartFailedException cause) {
return new FailureAnalysis(
"The Tomcat connector configured to listen on port " + cause.getPort()
+ " failed to start. The port may already be in use or the"
+ " connector may be misconfigured.",
"Verify the connector's configuration, identify and stop any process "
+ "that's listening on port " + cause.getPort()
+ ", or configure this application to listen on another port.",
cause);
}
}
...@@ -36,6 +36,7 @@ org.springframework.boot.diagnostics.FailureAnalyzer=\ ...@@ -36,6 +36,7 @@ org.springframework.boot.diagnostics.FailureAnalyzer=\
org.springframework.boot.diagnostics.analyzer.BeanCurrentlyInCreationFailureAnalyzer,\ org.springframework.boot.diagnostics.analyzer.BeanCurrentlyInCreationFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BeanNotOfRequiredTypeFailureAnalyzer,\ org.springframework.boot.diagnostics.analyzer.BeanNotOfRequiredTypeFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.BindFailureAnalyzer,\ org.springframework.boot.diagnostics.analyzer.BindFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.ConnectorStartFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.NoUniqueBeanDefinitionFailureAnalyzer,\ org.springframework.boot.diagnostics.analyzer.NoUniqueBeanDefinitionFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.PortInUseFailureAnalyzer,\ org.springframework.boot.diagnostics.analyzer.PortInUseFailureAnalyzer,\
org.springframework.boot.diagnostics.analyzer.ValidationExceptionFailureAnalyzer org.springframework.boot.diagnostics.analyzer.ValidationExceptionFailureAnalyzer
......
...@@ -840,8 +840,8 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests { ...@@ -840,8 +840,8 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests {
AbstractEmbeddedServletContainerFactoryTests.this.container.start(); AbstractEmbeddedServletContainerFactoryTests.this.container.start();
fail(); fail();
} }
catch (PortInUseException ex) { catch (RuntimeException ex) {
assertThat(ex.getPort()).isEqualTo(port); handleExceptionCausedByBlockedPort(ex, port);
} }
} }
...@@ -864,8 +864,8 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests { ...@@ -864,8 +864,8 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests {
AbstractEmbeddedServletContainerFactoryTests.this.container.start(); AbstractEmbeddedServletContainerFactoryTests.this.container.start();
fail(); fail();
} }
catch (PortInUseException ex) { catch (RuntimeException ex) {
assertThat(ex.getPort()).isEqualTo(port); handleExceptionCausedByBlockedPort(ex, port);
} }
} }
...@@ -886,6 +886,9 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests { ...@@ -886,6 +886,9 @@ public abstract class AbstractEmbeddedServletContainerFactoryTests {
protected abstract void addConnector(int port, protected abstract void addConnector(int port,
AbstractEmbeddedServletContainerFactory factory); AbstractEmbeddedServletContainerFactory factory);
protected abstract void handleExceptionCausedByBlockedPort(RuntimeException ex,
int blockedPort);
private boolean doTestCompression(int contentSize, String[] mimeTypes, private boolean doTestCompression(int contentSize, String[] mimeTypes,
String[] excludedUserAgents) throws Exception { String[] excludedUserAgents) throws Exception {
String testContent = setUpFactoryForCompression(contentSize, mimeTypes, String testContent = setUpFactoryForCompression(contentSize, mimeTypes,
......
...@@ -45,6 +45,7 @@ import org.mockito.InOrder; ...@@ -45,6 +45,7 @@ import org.mockito.InOrder;
import org.springframework.boot.context.embedded.AbstractEmbeddedServletContainerFactory; import org.springframework.boot.context.embedded.AbstractEmbeddedServletContainerFactory;
import org.springframework.boot.context.embedded.AbstractEmbeddedServletContainerFactoryTests; import org.springframework.boot.context.embedded.AbstractEmbeddedServletContainerFactoryTests;
import org.springframework.boot.context.embedded.Compression; import org.springframework.boot.context.embedded.Compression;
import org.springframework.boot.context.embedded.PortInUseException;
import org.springframework.boot.context.embedded.Ssl; import org.springframework.boot.context.embedded.Ssl;
import org.springframework.boot.web.servlet.ServletRegistrationBean; import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.http.HttpHeaders; import org.springframework.http.HttpHeaders;
...@@ -336,4 +337,11 @@ public class JettyEmbeddedServletContainerFactoryTests ...@@ -336,4 +337,11 @@ public class JettyEmbeddedServletContainerFactoryTests
return (charsetName != null) ? Charset.forName(charsetName) : null; return (charsetName != null) ? Charset.forName(charsetName) : null;
} }
@Override
protected void handleExceptionCausedByBlockedPort(RuntimeException ex,
int blockedPort) {
assertThat(ex).isInstanceOf(PortInUseException.class);
assertThat(((PortInUseException) ex).getPort()).isEqualTo(blockedPort);
}
} }
...@@ -498,4 +498,11 @@ public class TomcatEmbeddedServletContainerFactoryTests ...@@ -498,4 +498,11 @@ public class TomcatEmbeddedServletContainerFactoryTests
return ((TomcatEmbeddedServletContainer) this.container).getTomcat(); return ((TomcatEmbeddedServletContainer) this.container).getTomcat();
} }
@Override
protected void handleExceptionCausedByBlockedPort(RuntimeException ex,
int blockedPort) {
assertThat(ex).isInstanceOf(ConnectorStartFailedException.class);
assertThat(((ConnectorStartFailedException) ex).getPort()).isEqualTo(blockedPort);
}
} }
...@@ -41,6 +41,7 @@ import org.springframework.boot.context.embedded.AbstractEmbeddedServletContaine ...@@ -41,6 +41,7 @@ import org.springframework.boot.context.embedded.AbstractEmbeddedServletContaine
import org.springframework.boot.context.embedded.AbstractEmbeddedServletContainerFactoryTests; import org.springframework.boot.context.embedded.AbstractEmbeddedServletContainerFactoryTests;
import org.springframework.boot.context.embedded.ExampleServlet; import org.springframework.boot.context.embedded.ExampleServlet;
import org.springframework.boot.context.embedded.MimeMappings.Mapping; import org.springframework.boot.context.embedded.MimeMappings.Mapping;
import org.springframework.boot.context.embedded.PortInUseException;
import org.springframework.boot.web.servlet.ErrorPage; import org.springframework.boot.web.servlet.ErrorPage;
import org.springframework.boot.web.servlet.ServletRegistrationBean; import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.http.HttpStatus; import org.springframework.http.HttpStatus;
...@@ -291,4 +292,11 @@ public class UndertowEmbeddedServletContainerFactoryTests ...@@ -291,4 +292,11 @@ public class UndertowEmbeddedServletContainerFactoryTests
return (charsetName != null) ? Charset.forName(charsetName) : null; return (charsetName != null) ? Charset.forName(charsetName) : null;
} }
@Override
protected void handleExceptionCausedByBlockedPort(RuntimeException ex,
int blockedPort) {
assertThat(ex).isInstanceOf(PortInUseException.class);
assertThat(((PortInUseException) ex).getPort()).isEqualTo(blockedPort);
}
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment