Commit 93f7e2b6 authored by Phillip Webb's avatar Phillip Webb

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
parent 9bb53a4c
...@@ -17,7 +17,6 @@ ...@@ -17,7 +17,6 @@
package org.springframework.boot.web.embedded.jetty; package org.springframework.boot.web.embedded.jetty;
import java.io.IOException; import java.io.IOException;
import java.net.BindException;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.Objects; import java.util.Objects;
...@@ -147,8 +146,9 @@ public class JettyWebServer implements WebServer { ...@@ -147,8 +146,9 @@ public class JettyWebServer implements WebServer {
connector.start(); connector.start();
} }
catch (IOException ex) { catch (IOException ex) {
if (connector instanceof NetworkConnector && findBindException(ex) != null) { if (connector instanceof NetworkConnector) {
throw new PortInUseException(((NetworkConnector) connector).getPort(), ex); PortInUseException.throwIfPortBindingException(ex,
() -> ((NetworkConnector) connector).getPort());
} }
throw ex; throw ex;
} }
...@@ -168,16 +168,6 @@ public class JettyWebServer implements WebServer { ...@@ -168,16 +168,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() { private String getActualPortsDescription() {
StringBuilder ports = new StringBuilder(); StringBuilder ports = new StringBuilder();
for (Connector connector : this.server.getConnectors()) { for (Connector connector : this.server.getConnectors()) {
......
...@@ -86,10 +86,11 @@ public class NettyWebServer implements WebServer { ...@@ -86,10 +86,11 @@ public class NettyWebServer implements WebServer {
this.disposableServer = startHttpServer(); this.disposableServer = startHttpServer();
} }
catch (Exception ex) { catch (Exception ex) {
ChannelBindException bindException = findBindException(ex); PortInUseException.ifCausedBy(ex, ChannelBindException.class, (bindException) -> {
if (bindException != null && !isPermissionDenied(bindException.getCause())) { if (!isPermissionDenied(bindException.getCause())) {
throw new PortInUseException(bindException.localPort(), ex); throw new PortInUseException(bindException.localPort(), ex);
} }
});
throw new WebServerException("Unable to start Netty", ex); throw new WebServerException("Unable to start Netty", ex);
} }
logger.info("Netty started on port(s): " + getPort()); logger.info("Netty started on port(s): " + getPort());
...@@ -129,17 +130,6 @@ public class NettyWebServer implements WebServer { ...@@ -129,17 +130,6 @@ public class NettyWebServer implements WebServer {
routes.route(ALWAYS, this.handlerAdapter); routes.route(ALWAYS, this.handlerAdapter);
} }
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) { private void startDaemonAwaitThread(DisposableServer disposableServer) {
Thread awaitThread = new Thread("server") { Thread awaitThread = new Thread("server") {
......
/* /*
* Copyright 2012-2019 the original author or authors. * Copyright 2012-2020 the original author or authors.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
...@@ -16,7 +16,6 @@ ...@@ -16,7 +16,6 @@
package org.springframework.boot.web.embedded.tomcat; package org.springframework.boot.web.embedded.tomcat;
import java.net.BindException;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
...@@ -209,9 +208,7 @@ public class TomcatWebServer implements WebServer { ...@@ -209,9 +208,7 @@ public class TomcatWebServer implements WebServer {
throw ex; throw ex;
} }
catch (Exception ex) { catch (Exception ex) {
if (findBindException(ex) != null) { PortInUseException.throwIfPortBindingException(ex, () -> this.tomcat.getConnector().getPort());
throw new PortInUseException(this.tomcat.getConnector().getPort());
}
throw new WebServerException("Unable to start embedded Tomcat server", ex); throw new WebServerException("Unable to start embedded Tomcat server", ex);
} }
finally { finally {
...@@ -234,16 +231,6 @@ public class TomcatWebServer implements WebServer { ...@@ -234,16 +231,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() { private void stopSilently() {
try { try {
stopTomcat(); stopTomcat();
......
...@@ -17,7 +17,6 @@ ...@@ -17,7 +17,6 @@
package org.springframework.boot.web.embedded.undertow; package org.springframework.boot.web.embedded.undertow;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.net.BindException;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.SocketAddress; import java.net.SocketAddress;
import java.util.ArrayList; import java.util.ArrayList;
...@@ -146,14 +145,13 @@ public class UndertowServletWebServer implements WebServer { ...@@ -146,14 +145,13 @@ public class UndertowServletWebServer implements WebServer {
} }
catch (Exception ex) { catch (Exception ex) {
try { try {
if (findBindException(ex) != null) { PortInUseException.ifPortBindingException(ex, (bindException) -> {
List<Port> failedPorts = getConfiguredPorts(); List<Port> failedPorts = getConfiguredPorts();
List<Port> actualPorts = getActualPorts(); failedPorts.removeAll(getActualPorts());
failedPorts.removeAll(actualPorts);
if (failedPorts.size() == 1) { 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); throw new WebServerException("Unable to start embedded Undertow", ex);
} }
finally { finally {
...@@ -180,17 +178,6 @@ public class UndertowServletWebServer implements WebServer { ...@@ -180,17 +178,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 { private Undertow createUndertowServer() throws ServletException {
HttpHandler httpHandler = this.manager.start(); HttpHandler httpHandler = this.manager.start();
httpHandler = getContextHandler(httpHandler); httpHandler = getContextHandler(httpHandler);
......
...@@ -18,7 +18,6 @@ package org.springframework.boot.web.embedded.undertow; ...@@ -18,7 +18,6 @@ package org.springframework.boot.web.embedded.undertow;
import java.io.Closeable; import java.io.Closeable;
import java.lang.reflect.Field; import java.lang.reflect.Field;
import java.net.BindException;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.SocketAddress; import java.net.SocketAddress;
import java.util.ArrayList; import java.util.ArrayList;
...@@ -104,14 +103,13 @@ public class UndertowWebServer implements WebServer { ...@@ -104,14 +103,13 @@ public class UndertowWebServer implements WebServer {
} }
catch (Exception ex) { catch (Exception ex) {
try { try {
if (findBindException(ex) != null) { PortInUseException.ifPortBindingException(ex, (bindException) -> {
List<UndertowWebServer.Port> failedPorts = getConfiguredPorts(); List<Port> failedPorts = getConfiguredPorts();
List<UndertowWebServer.Port> actualPorts = getActualPorts(); failedPorts.removeAll(getActualPorts());
failedPorts.removeAll(actualPorts);
if (failedPorts.size() == 1) { 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); throw new WebServerException("Unable to start embedded Undertow", ex);
} }
finally { finally {
...@@ -133,17 +131,6 @@ public class UndertowWebServer implements WebServer { ...@@ -133,17 +131,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() { private String getPortsDescription() {
List<UndertowWebServer.Port> ports = getActualPorts(); List<UndertowWebServer.Port> ports = getActualPorts();
if (!ports.isEmpty()) { if (!ports.isEmpty()) {
......
...@@ -16,11 +16,16 @@ ...@@ -16,11 +16,16 @@
package org.springframework.boot.web.server; 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 * A {@code PortInUseException} is thrown when a web server fails to start due to a port
* already being in use. * already being in use.
* *
* @author Andy Wilkinson * @author Andy Wilkinson
* @author Phillip Webb
* @since 2.0.0 * @since 2.0.0
*/ */
public class PortInUseException extends WebServerException { public class PortInUseException extends WebServerException {
...@@ -53,4 +58,53 @@ public class PortInUseException extends WebServerException { ...@@ -53,4 +58,53 @@ public class PortInUseException extends WebServerException {
return this.port; 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<BindException> 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 <E> 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 <E extends Exception> void ifCausedBy(Exception ex, Class<E> causedBy, Consumer<E> action) {
Throwable candidate = ex;
while (candidate != null) {
if (causedBy.isInstance(candidate)) {
action.accept((E) candidate);
return;
}
candidate = candidate.getCause();
}
}
} }
...@@ -22,6 +22,7 @@ import java.io.FileWriter; ...@@ -22,6 +22,7 @@ import java.io.FileWriter;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.ServerSocket; import java.net.ServerSocket;
...@@ -96,6 +97,7 @@ import org.springframework.boot.testsupport.web.servlet.ExampleServlet; ...@@ -96,6 +97,7 @@ import org.springframework.boot.testsupport.web.servlet.ExampleServlet;
import org.springframework.boot.web.server.Compression; import org.springframework.boot.web.server.Compression;
import org.springframework.boot.web.server.ErrorPage; import org.springframework.boot.web.server.ErrorPage;
import org.springframework.boot.web.server.MimeMappings; import org.springframework.boot.web.server.MimeMappings;
import org.springframework.boot.web.server.PortInUseException;
import org.springframework.boot.web.server.Ssl; import org.springframework.boot.web.server.Ssl;
import org.springframework.boot.web.server.Ssl.ClientAuth; import org.springframework.boot.web.server.Ssl.ClientAuth;
import org.springframework.boot.web.server.SslStoreProvider; import org.springframework.boot.web.server.SslStoreProvider;
...@@ -873,6 +875,16 @@ public abstract class AbstractServletWebServerFactoryTests { ...@@ -873,6 +875,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 @Test
void localeCharsetMappingsAreConfigured() { void localeCharsetMappingsAreConfigured() {
AbstractServletWebServerFactory factory = getFactory(); AbstractServletWebServerFactory factory = getFactory();
......
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