From e927f52f2674fe0dd85ad2a051b1bb9cacaaf975 Mon Sep 17 00:00:00 2001 From: Andy Wilkinson Date: Wed, 4 Mar 2015 17:20:06 +0000 Subject: [PATCH] =?UTF-8?q?Don=E2=80=99t=20use=20findAvailableTcpPort=20to?= =?UTF-8?q?=20allocate=20Undertow=20a=20random=20port?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Using findAvailableTcpPort is prone to failure when another process starts using the available port before Undertow starts. This commit changes UndertowEmbeddedServletContainerFactory to pass the value of zero down into Undertow where it will ultimately be passed to a ServerSocket which will then use the underlying OS’s support for binding to an available port. Undertow doesn’t provide an API for getting the port(s) on which it’s listening. Previously, reflection was being used to access the listener configuration but, when a random port is used, this configuration would return zero rather than the actual port. The reflective logic has been updated to look at Undertow’s channels from which the underlying ServerSocket can be accessed and the local port retrieved Closes gh-2584 --- .../UndertowEmbeddedServletContainer.java | 99 ++++++++++++++----- ...dertowEmbeddedServletContainerFactory.java | 4 - 2 files changed, 77 insertions(+), 26 deletions(-) diff --git a/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainer.java b/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainer.java index 12856b2990..e0d341c71c 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainer.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainer.java @@ -23,6 +23,8 @@ import io.undertow.server.HttpHandler; import io.undertow.servlet.api.DeploymentManager; import java.lang.reflect.Field; +import java.net.ServerSocket; +import java.util.ArrayList; import java.util.List; import javax.servlet.ServletException; @@ -42,7 +44,7 @@ import org.springframework.util.StringUtils; * @author Ivan Sopov * @author Andy Wilkinson * @since 1.2.0 - * @see UndertowEmbeddedServletContainer + * @see UndertowEmbeddedServletContainerFactory */ public class UndertowEmbeddedServletContainer implements EmbeddedServletContainer { @@ -55,8 +57,6 @@ public class UndertowEmbeddedServletContainer implements EmbeddedServletContaine private final String contextPath; - private final int port; - private final boolean autoStart; private Undertow undertow; @@ -68,7 +68,6 @@ public class UndertowEmbeddedServletContainer implements EmbeddedServletContaine this.builder = builder; this.manager = manager; this.contextPath = contextPath; - this.port = port; this.autoStart = autoStart; } @@ -105,29 +104,64 @@ public class UndertowEmbeddedServletContainer implements EmbeddedServletContaine return Handlers.path().addPrefixPath(this.contextPath, servletHandler); } - @SuppressWarnings("rawtypes") private String getPortsDescription() { + List ports = getPorts(); + if (!ports.isEmpty()) { + return StringUtils.collectionToDelimitedString(ports, " "); + } + return "unknown"; + } + + @SuppressWarnings("rawtypes") + private List getPorts() { + List ports = new ArrayList(); try { - // Use reflection if possible to get the actual listener configuration - Field listenersField = ReflectionUtils.findField(Undertow.class, "listeners"); - listenersField.setAccessible(true); - List listeners = (List) listenersField.get(this.undertow); - StringBuilder ports = new StringBuilder(); - for (Object listener : listeners) { - Field portField = ReflectionUtils.findField(listener.getClass(), "port"); - portField.setAccessible(true); - Field typeField = ReflectionUtils.findField(listener.getClass(), "type"); - typeField.setAccessible(true); - ports.append(ports.length() == 0 ? "" : ", "); - ports.append(portField.get(listener) + " (" - + typeField.get(listener).toString().toLowerCase() + ")"); + // Use reflection if possible to get the underlying XNIO channels + if (!this.autoStart) { + ports.add(new Port(-1, "unknown")); + } + else { + Field channelsField = ReflectionUtils.findField(Undertow.class, + "channels"); + ReflectionUtils.makeAccessible(channelsField); + List channels = (List) ReflectionUtils.getField(channelsField, + this.undertow); + for (Object channel : channels) { + Port port = getPortFromChannel(channel); + if (port != null) { + ports.add(port); + } + } } - return ports.toString(); } catch (Exception ex) { + // Continue } - // We at least know our port - return String.valueOf(this.port); + return ports; + } + + private Port getPortFromChannel(Object channel) { + Object tcpServer; + String protocol; + Field sslContext = ReflectionUtils.findField(channel.getClass(), "sslContext"); + if (sslContext != null) { + Field tcpServerField = ReflectionUtils.findField(channel.getClass(), + "tcpServer"); + ReflectionUtils.makeAccessible(tcpServerField); + tcpServer = ReflectionUtils.getField(tcpServerField, channel); + protocol = "https"; + } + else { + tcpServer = channel; + protocol = "http"; + } + Field socketField = ReflectionUtils.findField(tcpServer.getClass(), "socket"); + if (socketField != null) { + ReflectionUtils.makeAccessible(socketField); + return new Port(((ServerSocket) ReflectionUtils.getField(socketField, + tcpServer)).getLocalPort(), protocol); + } + return null; } @Override @@ -140,7 +174,28 @@ public class UndertowEmbeddedServletContainer implements EmbeddedServletContaine @Override public int getPort() { - return this.port; + List ports = getPorts(); + if (ports.isEmpty()) { + return 0; + } + return ports.get(0).portNumber; + } + + private static class Port { + + private final int portNumber; + + private final String protocol; + + private Port(int portNumber, String protocol) { + this.portNumber = portNumber; + this.protocol = protocol; + } + + @Override + public String toString() { + return this.portNumber + " (" + this.protocol + ")"; + } } } diff --git a/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainerFactory.java b/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainerFactory.java index 20e1cc36ef..099ba33238 100644 --- a/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainerFactory.java +++ b/spring-boot/src/main/java/org/springframework/boot/context/embedded/undertow/UndertowEmbeddedServletContainerFactory.java @@ -69,7 +69,6 @@ import org.springframework.context.ResourceLoaderAware; import org.springframework.core.io.ResourceLoader; import org.springframework.util.Assert; import org.springframework.util.ResourceUtils; -import org.springframework.util.SocketUtils; import org.xnio.Options; import org.xnio.SslClientAuthMode; @@ -203,9 +202,6 @@ public class UndertowEmbeddedServletContainerFactory extends ServletContextInitializer... initializers) { DeploymentManager manager = createDeploymentManager(initializers); int port = getPort(); - if (port == 0) { - port = SocketUtils.findAvailableTcpPort(40000); - } Builder builder = createBuilder(port); return new UndertowEmbeddedServletContainer(builder, manager, getContextPath(), port, port >= 0);