Skip to content

Commit 93f7e2b

Browse files
committed
Limit when PortInUseException is thrown
Refactor `PortInUseException` logic to a single place and refine when the exception is thrown. Prior to this commit, we assumed that a `BindException` was only thrown when the port was in use. In fact, it's possible that the exception could be thrown because the requested address "could not be assigned". We now only throw a `PortInUserException` if the `BindException` message includes the phrase "in use". Fixes gh-21101
1 parent 9bb53a4 commit 93f7e2b

File tree

7 files changed

+85
-78
lines changed

7 files changed

+85
-78
lines changed

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyWebServer.java

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
package org.springframework.boot.web.embedded.jetty;
1818

1919
import java.io.IOException;
20-
import java.net.BindException;
2120
import java.util.Arrays;
2221
import java.util.List;
2322
import java.util.Objects;
@@ -147,8 +146,9 @@ public void start() throws WebServerException {
147146
connector.start();
148147
}
149148
catch (IOException ex) {
150-
if (connector instanceof NetworkConnector && findBindException(ex) != null) {
151-
throw new PortInUseException(((NetworkConnector) connector).getPort(), ex);
149+
if (connector instanceof NetworkConnector) {
150+
PortInUseException.throwIfPortBindingException(ex,
151+
() -> ((NetworkConnector) connector).getPort());
152152
}
153153
throw ex;
154154
}
@@ -168,16 +168,6 @@ public void start() throws WebServerException {
168168
}
169169
}
170170

171-
private BindException findBindException(Throwable ex) {
172-
if (ex == null) {
173-
return null;
174-
}
175-
if (ex instanceof BindException) {
176-
return (BindException) ex;
177-
}
178-
return findBindException(ex.getCause());
179-
}
180-
181171
private String getActualPortsDescription() {
182172
StringBuilder ports = new StringBuilder();
183173
for (Connector connector : this.server.getConnectors()) {

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/netty/NettyWebServer.java

Lines changed: 5 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -86,10 +86,11 @@ public void start() throws WebServerException {
8686
this.disposableServer = startHttpServer();
8787
}
8888
catch (Exception ex) {
89-
ChannelBindException bindException = findBindException(ex);
90-
if (bindException != null && !isPermissionDenied(bindException.getCause())) {
91-
throw new PortInUseException(bindException.localPort(), ex);
92-
}
89+
PortInUseException.ifCausedBy(ex, ChannelBindException.class, (bindException) -> {
90+
if (!isPermissionDenied(bindException.getCause())) {
91+
throw new PortInUseException(bindException.localPort(), ex);
92+
}
93+
});
9394
throw new WebServerException("Unable to start Netty", ex);
9495
}
9596
logger.info("Netty started on port(s): " + getPort());
@@ -129,17 +130,6 @@ private void applyRouteProviders(HttpServerRoutes routes) {
129130
routes.route(ALWAYS, this.handlerAdapter);
130131
}
131132

132-
private ChannelBindException findBindException(Exception ex) {
133-
Throwable candidate = ex;
134-
while (candidate != null) {
135-
if (candidate instanceof ChannelBindException) {
136-
return (ChannelBindException) candidate;
137-
}
138-
candidate = candidate.getCause();
139-
}
140-
return null;
141-
}
142-
143133
private void startDaemonAwaitThread(DisposableServer disposableServer) {
144134
Thread awaitThread = new Thread("server") {
145135

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatWebServer.java

Lines changed: 2 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2012-2019 the original author or authors.
2+
* Copyright 2012-2020 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,7 +16,6 @@
1616

1717
package org.springframework.boot.web.embedded.tomcat;
1818

19-
import java.net.BindException;
2019
import java.util.Arrays;
2120
import java.util.HashMap;
2221
import java.util.Map;
@@ -209,9 +208,7 @@ public void start() throws WebServerException {
209208
throw ex;
210209
}
211210
catch (Exception ex) {
212-
if (findBindException(ex) != null) {
213-
throw new PortInUseException(this.tomcat.getConnector().getPort());
214-
}
211+
PortInUseException.throwIfPortBindingException(ex, () -> this.tomcat.getConnector().getPort());
215212
throw new WebServerException("Unable to start embedded Tomcat server", ex);
216213
}
217214
finally {
@@ -234,16 +231,6 @@ private void checkConnectorHasStarted(Connector connector) {
234231
}
235232
}
236233

237-
private BindException findBindException(Throwable ex) {
238-
if (ex == null) {
239-
return null;
240-
}
241-
if (ex instanceof BindException) {
242-
return (BindException) ex;
243-
}
244-
return findBindException(ex.getCause());
245-
}
246-
247234
private void stopSilently() {
248235
try {
249236
stopTomcat();

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/undertow/UndertowServletWebServer.java

Lines changed: 4 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717
package org.springframework.boot.web.embedded.undertow;
1818

1919
import java.lang.reflect.Field;
20-
import java.net.BindException;
2120
import java.net.InetSocketAddress;
2221
import java.net.SocketAddress;
2322
import java.util.ArrayList;
@@ -146,14 +145,13 @@ public void start() throws WebServerException {
146145
}
147146
catch (Exception ex) {
148147
try {
149-
if (findBindException(ex) != null) {
148+
PortInUseException.ifPortBindingException(ex, (bindException) -> {
150149
List<Port> failedPorts = getConfiguredPorts();
151-
List<Port> actualPorts = getActualPorts();
152-
failedPorts.removeAll(actualPorts);
150+
failedPorts.removeAll(getActualPorts());
153151
if (failedPorts.size() == 1) {
154-
throw new PortInUseException(failedPorts.iterator().next().getNumber(), ex);
152+
throw new PortInUseException(failedPorts.get(0).getNumber());
155153
}
156-
}
154+
});
157155
throw new WebServerException("Unable to start embedded Undertow", ex);
158156
}
159157
finally {
@@ -180,17 +178,6 @@ private void stopSilently() {
180178
}
181179
}
182180

183-
private BindException findBindException(Exception ex) {
184-
Throwable candidate = ex;
185-
while (candidate != null) {
186-
if (candidate instanceof BindException) {
187-
return (BindException) candidate;
188-
}
189-
candidate = candidate.getCause();
190-
}
191-
return null;
192-
}
193-
194181
private Undertow createUndertowServer() throws ServletException {
195182
HttpHandler httpHandler = this.manager.start();
196183
httpHandler = getContextHandler(httpHandler);

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/undertow/UndertowWebServer.java

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818

1919
import java.io.Closeable;
2020
import java.lang.reflect.Field;
21-
import java.net.BindException;
2221
import java.net.InetSocketAddress;
2322
import java.net.SocketAddress;
2423
import java.util.ArrayList;
@@ -104,14 +103,13 @@ public void start() throws WebServerException {
104103
}
105104
catch (Exception ex) {
106105
try {
107-
if (findBindException(ex) != null) {
108-
List<UndertowWebServer.Port> failedPorts = getConfiguredPorts();
109-
List<UndertowWebServer.Port> actualPorts = getActualPorts();
110-
failedPorts.removeAll(actualPorts);
106+
PortInUseException.ifPortBindingException(ex, (bindException) -> {
107+
List<Port> failedPorts = getConfiguredPorts();
108+
failedPorts.removeAll(getActualPorts());
111109
if (failedPorts.size() == 1) {
112-
throw new PortInUseException(failedPorts.iterator().next().getNumber(), ex);
110+
throw new PortInUseException(failedPorts.get(0).getNumber());
113111
}
114-
}
112+
});
115113
throw new WebServerException("Unable to start embedded Undertow", ex);
116114
}
117115
finally {
@@ -133,17 +131,6 @@ private void stopSilently() {
133131
}
134132
}
135133

136-
private BindException findBindException(Exception ex) {
137-
Throwable candidate = ex;
138-
while (candidate != null) {
139-
if (candidate instanceof BindException) {
140-
return (BindException) candidate;
141-
}
142-
candidate = candidate.getCause();
143-
}
144-
return null;
145-
}
146-
147134
private String getPortsDescription() {
148135
List<UndertowWebServer.Port> ports = getActualPorts();
149136
if (!ports.isEmpty()) {

spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/server/PortInUseException.java

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,16 @@
1616

1717
package org.springframework.boot.web.server;
1818

19+
import java.net.BindException;
20+
import java.util.function.Consumer;
21+
import java.util.function.IntSupplier;
22+
1923
/**
2024
* A {@code PortInUseException} is thrown when a web server fails to start due to a port
2125
* already being in use.
2226
*
2327
* @author Andy Wilkinson
28+
* @author Phillip Webb
2429
* @since 2.0.0
2530
*/
2631
public class PortInUseException extends WebServerException {
@@ -53,4 +58,53 @@ public int getPort() {
5358
return this.port;
5459
}
5560

61+
/**
62+
* Throw a {@link PortInUseException} if the given exception was caused by a "port in
63+
* use" {@link BindException}.
64+
* @param ex the source exception
65+
* @param port a suppler used to provide the port
66+
* @since 2.2.7
67+
*/
68+
public static void throwIfPortBindingException(Exception ex, IntSupplier port) {
69+
ifPortBindingException(ex, (bindException) -> {
70+
throw new PortInUseException(port.getAsInt(), ex);
71+
});
72+
}
73+
74+
/**
75+
* Perform an action if the given exception was caused by a "port in use"
76+
* {@link BindException}.
77+
* @param ex the source exception
78+
* @param action the action to perform
79+
* @since 2.2.7
80+
*/
81+
public static void ifPortBindingException(Exception ex, Consumer<BindException> action) {
82+
ifCausedBy(ex, BindException.class, (bindException) -> {
83+
// bind exception can be also thrown because an address can't be assigned
84+
if (bindException.getMessage().toLowerCase().contains("in use")) {
85+
action.accept(bindException);
86+
}
87+
});
88+
}
89+
90+
/**
91+
* Perform an action if the given exception was caused by a specific exception type.
92+
* @param <E> the cause exception type
93+
* @param ex the source exception
94+
* @param causedBy the required cause type
95+
* @param action the action to perform
96+
* @since 2.2.7
97+
*/
98+
@SuppressWarnings("unchecked")
99+
public static <E extends Exception> void ifCausedBy(Exception ex, Class<E> causedBy, Consumer<E> action) {
100+
Throwable candidate = ex;
101+
while (candidate != null) {
102+
if (causedBy.isInstance(candidate)) {
103+
action.accept((E) candidate);
104+
return;
105+
}
106+
candidate = candidate.getCause();
107+
}
108+
}
109+
56110
}

spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/servlet/server/AbstractServletWebServerFactoryTests.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
import java.io.IOException;
2323
import java.io.InputStream;
2424
import java.io.PrintWriter;
25+
import java.net.InetAddress;
2526
import java.net.InetSocketAddress;
2627
import java.net.MalformedURLException;
2728
import java.net.ServerSocket;
@@ -96,6 +97,7 @@
9697
import org.springframework.boot.web.server.Compression;
9798
import org.springframework.boot.web.server.ErrorPage;
9899
import org.springframework.boot.web.server.MimeMappings;
100+
import org.springframework.boot.web.server.PortInUseException;
99101
import org.springframework.boot.web.server.Ssl;
100102
import org.springframework.boot.web.server.Ssl.ClientAuth;
101103
import org.springframework.boot.web.server.SslStoreProvider;
@@ -873,6 +875,16 @@ void portClashOfSecondaryConnectorResultsInPortInUseException() throws Exception
873875
});
874876
}
875877

878+
@Test
879+
void malformedAddress() throws Exception {
880+
AbstractServletWebServerFactory factory = getFactory();
881+
factory.setAddress(InetAddress.getByName("123456"));
882+
assertThatExceptionOfType(RuntimeException.class).isThrownBy(() -> {
883+
this.webServer = factory.getWebServer();
884+
this.webServer.start();
885+
}).isNotInstanceOf(PortInUseException.class);
886+
}
887+
876888
@Test
877889
void localeCharsetMappingsAreConfigured() {
878890
AbstractServletWebServerFactory factory = getFactory();

0 commit comments

Comments
 (0)