Commit e927f52f authored by Andy Wilkinson's avatar Andy Wilkinson

Don’t use findAvailableTcpPort to allocate Undertow a random port

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
parent 4487823f
......@@ -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<Port> ports = getPorts();
if (!ports.isEmpty()) {
return StringUtils.collectionToDelimitedString(ports, " ");
}
return "unknown";
}
@SuppressWarnings("rawtypes")
private List<Port> getPorts() {
List<Port> ports = new ArrayList<Port>();
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<Port> 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 + ")";
}
}
}
......@@ -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);
......
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