Skip to content

Commit 564207b

Browse files
committed
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
1 parent 1641bb2 commit 564207b

File tree

8 files changed

+127
-7
lines changed

8 files changed

+127
-7
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/*
2+
* Copyright 2012-2016 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.context.embedded.tomcat;
18+
19+
import org.apache.catalina.connector.Connector;
20+
21+
import org.springframework.boot.context.embedded.EmbeddedServletContainerException;
22+
23+
/**
24+
* A {@code ConnectorStartFailedException} is thrown when a Tomcat {@link Connector} fails
25+
* to start, for example due to a port clash or incorrect SSL configuration.
26+
*
27+
* @author Andy Wilkinson
28+
* @since 1.4.1
29+
*/
30+
public class ConnectorStartFailedException extends EmbeddedServletContainerException {
31+
32+
private final int port;
33+
34+
/**
35+
* Creates a new {@code ConnectorStartFailedException} for a connector that's
36+
* configured to listen on the given {@code port}.
37+
*
38+
* @param port the port
39+
*/
40+
public ConnectorStartFailedException(int port) {
41+
super("Connector configured to listen on port " + port + " failed to start",
42+
null);
43+
this.port = port;
44+
}
45+
46+
public int getPort() {
47+
return this.port;
48+
}
49+
50+
}

spring-boot/src/main/java/org/springframework/boot/context/embedded/tomcat/TomcatEmbeddedServletContainer.java

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,6 @@
3636

3737
import org.springframework.boot.context.embedded.EmbeddedServletContainer;
3838
import org.springframework.boot.context.embedded.EmbeddedServletContainerException;
39-
import org.springframework.boot.context.embedded.PortInUseException;
4039
import org.springframework.util.Assert;
4140

4241
/**
@@ -185,7 +184,7 @@ public void start() throws EmbeddedServletContainerException {
185184
TomcatEmbeddedServletContainer.logger
186185
.info("Tomcat started on port(s): " + getPortsDescription(true));
187186
}
188-
catch (PortInUseException ex) {
187+
catch (ConnectorStartFailedException ex) {
189188
stopSilently();
190189
throw ex;
191190
}
@@ -203,7 +202,7 @@ public void start() throws EmbeddedServletContainerException {
203202
private void checkThatConnectorsHaveStarted() {
204203
for (Connector connector : this.tomcat.getService().findConnectors()) {
205204
if (LifecycleState.FAILED.equals(connector.getState())) {
206-
throw new PortInUseException(connector.getPort());
205+
throw new ConnectorStartFailedException(connector.getPort());
207206
}
208207
}
209208
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright 2012-2016 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.boot.diagnostics.analyzer;
18+
19+
import org.springframework.boot.context.embedded.tomcat.ConnectorStartFailedException;
20+
import org.springframework.boot.diagnostics.AbstractFailureAnalyzer;
21+
import org.springframework.boot.diagnostics.FailureAnalysis;
22+
23+
/**
24+
* An {@link AbstractFailureAnalyzer} for {@link ConnectorStartFailedException}.
25+
*
26+
* @author Andy Wilkinson
27+
*/
28+
class ConnectorStartFailureAnalyzer
29+
extends AbstractFailureAnalyzer<ConnectorStartFailedException> {
30+
31+
@Override
32+
protected FailureAnalysis analyze(Throwable rootFailure,
33+
ConnectorStartFailedException cause) {
34+
return new FailureAnalysis(
35+
"The Tomcat connector configured to listen on port " + cause.getPort()
36+
+ " failed to start. The port may already be in use or the"
37+
+ " connector may be misconfigured.",
38+
"Verify the connector's configuration, identify and stop any process "
39+
+ "that's listening on port " + cause.getPort()
40+
+ ", or configure this application to listen on another port.",
41+
cause);
42+
}
43+
44+
}

spring-boot/src/main/resources/META-INF/spring.factories

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@ org.springframework.boot.diagnostics.FailureAnalyzer=\
3636
org.springframework.boot.diagnostics.analyzer.BeanCurrentlyInCreationFailureAnalyzer,\
3737
org.springframework.boot.diagnostics.analyzer.BeanNotOfRequiredTypeFailureAnalyzer,\
3838
org.springframework.boot.diagnostics.analyzer.BindFailureAnalyzer,\
39+
org.springframework.boot.diagnostics.analyzer.ConnectorStartFailureAnalyzer,\
3940
org.springframework.boot.diagnostics.analyzer.NoUniqueBeanDefinitionFailureAnalyzer,\
4041
org.springframework.boot.diagnostics.analyzer.PortInUseFailureAnalyzer,\
4142
org.springframework.boot.diagnostics.analyzer.ValidationExceptionFailureAnalyzer

spring-boot/src/test/java/org/springframework/boot/context/embedded/AbstractEmbeddedServletContainerFactoryTests.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -840,8 +840,8 @@ public void run(int port) {
840840
AbstractEmbeddedServletContainerFactoryTests.this.container.start();
841841
fail();
842842
}
843-
catch (PortInUseException ex) {
844-
assertThat(ex.getPort()).isEqualTo(port);
843+
catch (RuntimeException ex) {
844+
handleExceptionCausedByBlockedPort(ex, port);
845845
}
846846
}
847847

@@ -864,8 +864,8 @@ public void run(int port) {
864864
AbstractEmbeddedServletContainerFactoryTests.this.container.start();
865865
fail();
866866
}
867-
catch (PortInUseException ex) {
868-
assertThat(ex.getPort()).isEqualTo(port);
867+
catch (RuntimeException ex) {
868+
handleExceptionCausedByBlockedPort(ex, port);
869869
}
870870
}
871871

@@ -886,6 +886,9 @@ public void localeCharsetMappingsAreConfigured() throws Exception {
886886
protected abstract void addConnector(int port,
887887
AbstractEmbeddedServletContainerFactory factory);
888888

889+
protected abstract void handleExceptionCausedByBlockedPort(RuntimeException ex,
890+
int blockedPort);
891+
889892
private boolean doTestCompression(int contentSize, String[] mimeTypes,
890893
String[] excludedUserAgents) throws Exception {
891894
String testContent = setUpFactoryForCompression(contentSize, mimeTypes,

spring-boot/src/test/java/org/springframework/boot/context/embedded/jetty/JettyEmbeddedServletContainerFactoryTests.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
import org.springframework.boot.context.embedded.AbstractEmbeddedServletContainerFactory;
4646
import org.springframework.boot.context.embedded.AbstractEmbeddedServletContainerFactoryTests;
4747
import org.springframework.boot.context.embedded.Compression;
48+
import org.springframework.boot.context.embedded.PortInUseException;
4849
import org.springframework.boot.context.embedded.Ssl;
4950
import org.springframework.boot.web.servlet.ServletRegistrationBean;
5051
import org.springframework.http.HttpHeaders;
@@ -336,4 +337,11 @@ protected Charset getCharset(Locale locale) {
336337
return (charsetName != null) ? Charset.forName(charsetName) : null;
337338
}
338339

340+
@Override
341+
protected void handleExceptionCausedByBlockedPort(RuntimeException ex,
342+
int blockedPort) {
343+
assertThat(ex).isInstanceOf(PortInUseException.class);
344+
assertThat(((PortInUseException) ex).getPort()).isEqualTo(blockedPort);
345+
}
346+
339347
}

spring-boot/src/test/java/org/springframework/boot/context/embedded/tomcat/TomcatEmbeddedServletContainerFactoryTests.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,4 +498,11 @@ private Tomcat getTomcat(TomcatEmbeddedServletContainerFactory factory) {
498498
return ((TomcatEmbeddedServletContainer) this.container).getTomcat();
499499
}
500500

501+
@Override
502+
protected void handleExceptionCausedByBlockedPort(RuntimeException ex,
503+
int blockedPort) {
504+
assertThat(ex).isInstanceOf(ConnectorStartFailedException.class);
505+
assertThat(((ConnectorStartFailedException) ex).getPort()).isEqualTo(blockedPort);
506+
}
507+
501508
}

spring-boot/src/test/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainerFactoryTests.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@
4141
import org.springframework.boot.context.embedded.AbstractEmbeddedServletContainerFactoryTests;
4242
import org.springframework.boot.context.embedded.ExampleServlet;
4343
import org.springframework.boot.context.embedded.MimeMappings.Mapping;
44+
import org.springframework.boot.context.embedded.PortInUseException;
4445
import org.springframework.boot.web.servlet.ErrorPage;
4546
import org.springframework.boot.web.servlet.ServletRegistrationBean;
4647
import org.springframework.http.HttpStatus;
@@ -291,4 +292,11 @@ protected Charset getCharset(Locale locale) {
291292
return (charsetName != null) ? Charset.forName(charsetName) : null;
292293
}
293294

295+
@Override
296+
protected void handleExceptionCausedByBlockedPort(RuntimeException ex,
297+
int blockedPort) {
298+
assertThat(ex).isInstanceOf(PortInUseException.class);
299+
assertThat(((PortInUseException) ex).getPort()).isEqualTo(blockedPort);
300+
}
301+
294302
}

0 commit comments

Comments
 (0)