diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyWebServer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyWebServer.java index 54c483a552..f972d63b4b 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyWebServer.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/jetty/JettyWebServer.java @@ -17,7 +17,6 @@ package org.springframework.boot.web.embedded.jetty; import java.io.IOException; -import java.net.BindException; import java.time.Duration; import java.util.Arrays; import java.util.List; @@ -174,8 +173,9 @@ public class JettyWebServer implements WebServer { connector.start(); } catch (IOException ex) { - if (connector instanceof NetworkConnector && findBindException(ex) != null) { - throw new PortInUseException(((NetworkConnector) connector).getPort(), ex); + if (connector instanceof NetworkConnector) { + PortInUseException.throwIfPortBindingException(ex, + () -> ((NetworkConnector) connector).getPort()); } throw ex; } @@ -195,16 +195,6 @@ public class JettyWebServer implements WebServer { } } - private BindException findBindException(Throwable ex) { - if (ex == null) { - return null; - } - if (ex instanceof BindException) { - return (BindException) ex; - } - return findBindException(ex.getCause()); - } - private String getActualPortsDescription() { StringBuilder ports = new StringBuilder(); for (Connector connector : this.server.getConnectors()) { diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/netty/NettyWebServer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/netty/NettyWebServer.java index 3747a29c4a..dfd140fbf9 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/netty/NettyWebServer.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/netty/NettyWebServer.java @@ -116,10 +116,11 @@ public class NettyWebServer implements WebServer { this.disposableServer = startHttpServer(); } catch (Exception ex) { - ChannelBindException bindException = findBindException(ex); - if (bindException != null && !isPermissionDenied(bindException.getCause())) { - throw new PortInUseException(bindException.localPort(), ex); - } + PortInUseException.ifCausedBy(ex, ChannelBindException.class, (bindException) -> { + if (!isPermissionDenied(bindException.getCause())) { + throw new PortInUseException(bindException.localPort(), ex); + } + }); throw new WebServerException("Unable to start Netty", ex); } logger.info("Netty started on port(s): " + getPort()); @@ -168,17 +169,6 @@ public class NettyWebServer implements WebServer { routes.route(ALWAYS, this.handler); } - private ChannelBindException findBindException(Exception ex) { - Throwable candidate = ex; - while (candidate != null) { - if (candidate instanceof ChannelBindException) { - return (ChannelBindException) candidate; - } - candidate = candidate.getCause(); - } - return null; - } - private void startDaemonAwaitThread(DisposableServer disposableServer) { Thread awaitThread = new Thread("server") { diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatWebServer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatWebServer.java index 8a10e28a6f..314fb7302e 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatWebServer.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/tomcat/TomcatWebServer.java @@ -16,7 +16,6 @@ package org.springframework.boot.web.embedded.tomcat; -import java.net.BindException; import java.time.Duration; import java.util.Arrays; import java.util.HashMap; @@ -226,9 +225,7 @@ public class TomcatWebServer implements WebServer { throw ex; } catch (Exception ex) { - if (findBindException(ex) != null) { - throw new PortInUseException(this.tomcat.getConnector().getPort()); - } + PortInUseException.throwIfPortBindingException(ex, () -> this.tomcat.getConnector().getPort()); throw new WebServerException("Unable to start embedded Tomcat server", ex); } finally { @@ -251,16 +248,6 @@ public class TomcatWebServer implements WebServer { } } - private BindException findBindException(Throwable ex) { - if (ex == null) { - return null; - } - if (ex instanceof BindException) { - return (BindException) ex; - } - return findBindException(ex.getCause()); - } - private void stopSilently() { try { stopTomcat(); diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/undertow/UndertowServletWebServer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/undertow/UndertowServletWebServer.java index 15531df16b..04d8cd2abb 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/undertow/UndertowServletWebServer.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/undertow/UndertowServletWebServer.java @@ -17,7 +17,6 @@ package org.springframework.boot.web.embedded.undertow; import java.lang.reflect.Field; -import java.net.BindException; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.time.Duration; @@ -173,14 +172,13 @@ public class UndertowServletWebServer implements WebServer { } catch (Exception ex) { try { - if (findBindException(ex) != null) { + PortInUseException.ifPortBindingException(ex, (bindException) -> { List failedPorts = getConfiguredPorts(); - List actualPorts = getActualPorts(); - failedPorts.removeAll(actualPorts); + failedPorts.removeAll(getActualPorts()); if (failedPorts.size() == 1) { - throw new PortInUseException(failedPorts.iterator().next().getNumber(), ex); + throw new PortInUseException(failedPorts.get(0).getNumber()); } - } + }); throw new WebServerException("Unable to start embedded Undertow", ex); } finally { @@ -207,17 +205,6 @@ public class UndertowServletWebServer implements WebServer { } } - private BindException findBindException(Exception ex) { - Throwable candidate = ex; - while (candidate != null) { - if (candidate instanceof BindException) { - return (BindException) candidate; - } - candidate = candidate.getCause(); - } - return null; - } - private Undertow createUndertowServer() throws ServletException { HttpHandler httpHandler = this.manager.start(); httpHandler = getContextHandler(httpHandler); diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/undertow/UndertowWebServer.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/undertow/UndertowWebServer.java index bb96db0df1..7b07da0247 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/undertow/UndertowWebServer.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/embedded/undertow/UndertowWebServer.java @@ -18,7 +18,6 @@ package org.springframework.boot.web.embedded.undertow; import java.io.Closeable; import java.lang.reflect.Field; -import java.net.BindException; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.util.ArrayList; @@ -121,14 +120,13 @@ public class UndertowWebServer implements WebServer { } catch (Exception ex) { try { - if (findBindException(ex) != null) { - List failedPorts = getConfiguredPorts(); - List actualPorts = getActualPorts(); - failedPorts.removeAll(actualPorts); + PortInUseException.ifPortBindingException(ex, (bindException) -> { + List failedPorts = getConfiguredPorts(); + failedPorts.removeAll(getActualPorts()); if (failedPorts.size() == 1) { - throw new PortInUseException(failedPorts.iterator().next().getNumber(), ex); + throw new PortInUseException(failedPorts.get(0).getNumber()); } - } + }); throw new WebServerException("Unable to start embedded Undertow", ex); } finally { @@ -150,17 +148,6 @@ public class UndertowWebServer implements WebServer { } } - private BindException findBindException(Exception ex) { - Throwable candidate = ex; - while (candidate != null) { - if (candidate instanceof BindException) { - return (BindException) candidate; - } - candidate = candidate.getCause(); - } - return null; - } - private String getPortsDescription() { List ports = getActualPorts(); if (!ports.isEmpty()) { diff --git a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/server/PortInUseException.java b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/server/PortInUseException.java index edfb21c2a5..360c28ce71 100644 --- a/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/server/PortInUseException.java +++ b/spring-boot-project/spring-boot/src/main/java/org/springframework/boot/web/server/PortInUseException.java @@ -16,11 +16,16 @@ package org.springframework.boot.web.server; +import java.net.BindException; +import java.util.function.Consumer; +import java.util.function.IntSupplier; + /** * A {@code PortInUseException} is thrown when a web server fails to start due to a port * already being in use. * * @author Andy Wilkinson + * @author Phillip Webb * @since 2.0.0 */ public class PortInUseException extends WebServerException { @@ -53,4 +58,53 @@ public class PortInUseException extends WebServerException { return this.port; } + /** + * Throw a {@link PortInUseException} if the given exception was caused by a "port in + * use" {@link BindException}. + * @param ex the source exception + * @param port a suppler used to provide the port + * @since 2.2.7 + */ + public static void throwIfPortBindingException(Exception ex, IntSupplier port) { + ifPortBindingException(ex, (bindException) -> { + throw new PortInUseException(port.getAsInt(), ex); + }); + } + + /** + * Perform an action if the given exception was caused by a "port in use" + * {@link BindException}. + * @param ex the source exception + * @param action the action to perform + * @since 2.2.7 + */ + public static void ifPortBindingException(Exception ex, Consumer action) { + ifCausedBy(ex, BindException.class, (bindException) -> { + // bind exception can be also thrown because an address can't be assigned + if (bindException.getMessage().toLowerCase().contains("in use")) { + action.accept(bindException); + } + }); + } + + /** + * Perform an action if the given exception was caused by a specific exception type. + * @param the cause exception type + * @param ex the source exception + * @param causedBy the required cause type + * @param action the action to perform + * @since 2.2.7 + */ + @SuppressWarnings("unchecked") + public static void ifCausedBy(Exception ex, Class causedBy, Consumer action) { + Throwable candidate = ex; + while (candidate != null) { + if (causedBy.isInstance(candidate)) { + action.accept((E) candidate); + return; + } + candidate = candidate.getCause(); + } + } + } diff --git a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/servlet/server/AbstractServletWebServerFactoryTests.java b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/servlet/server/AbstractServletWebServerFactoryTests.java index e67f16dd88..8f63c4a2c0 100644 --- a/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/servlet/server/AbstractServletWebServerFactoryTests.java +++ b/spring-boot-project/spring-boot/src/test/java/org/springframework/boot/web/servlet/server/AbstractServletWebServerFactoryTests.java @@ -22,6 +22,7 @@ import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; import java.io.PrintWriter; +import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.MalformedURLException; import java.net.ServerSocket; @@ -112,6 +113,7 @@ import org.springframework.boot.testsupport.web.servlet.ExampleServlet; import org.springframework.boot.web.server.Compression; import org.springframework.boot.web.server.ErrorPage; import org.springframework.boot.web.server.MimeMappings; +import org.springframework.boot.web.server.PortInUseException; import org.springframework.boot.web.server.Shutdown; import org.springframework.boot.web.server.Ssl; import org.springframework.boot.web.server.Ssl.ClientAuth; @@ -919,6 +921,16 @@ public abstract class AbstractServletWebServerFactoryTests { }); } + @Test + void malformedAddress() throws Exception { + AbstractServletWebServerFactory factory = getFactory(); + factory.setAddress(InetAddress.getByName("123456")); + assertThatExceptionOfType(RuntimeException.class).isThrownBy(() -> { + this.webServer = factory.getWebServer(); + this.webServer.start(); + }).isNotInstanceOf(PortInUseException.class); + } + @Test void localeCharsetMappingsAreConfigured() { AbstractServletWebServerFactory factory = getFactory();