org.springframework.test.util.TestSocketUtils Maven / Gradle / Ivy
Show all versions of spring-test Show documentation
/*
* Copyright 2002-2023 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.test.util;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.util.Random;
import javax.net.ServerSocketFactory;
import org.springframework.util.Assert;
/**
* Simple utility for finding available TCP ports on {@code localhost} for use in
* integration testing scenarios.
*
* This is a limited form of {@code org.springframework.util.SocketUtils}, which
* has been deprecated since Spring Framework 5.3.16 and removed in Spring
* Framework 6.0.
*
*
{@code TestSocketUtils} can be used in integration tests which start an
* external server on an available random port. However, these utilities make no
* guarantee about the subsequent availability of a given port and are therefore
* unreliable. Instead of using {@code TestSocketUtils} to find an available local
* port for a server, it is recommended that you rely on a server's ability to
* start on a random ephemeral port that it selects or is assigned by the
* operating system. To interact with that server, you should query the server
* for the port it is currently using.
*
* @author Sam Brannen
* @author Ben Hale
* @author Arjen Poutsma
* @author Gunnar Hillert
* @author Gary Russell
* @author Chris Bono
* @since 5.3.24
*/
public class TestSocketUtils {
/**
* The minimum value for port ranges used when finding an available TCP port.
*/
static final int PORT_RANGE_MIN = 1024;
/**
* The maximum value for port ranges used when finding an available TCP port.
*/
static final int PORT_RANGE_MAX = 65535;
private static final int PORT_RANGE_PLUS_ONE = PORT_RANGE_MAX - PORT_RANGE_MIN + 1;
private static final int MAX_ATTEMPTS = 1_000;
private static final Random random = new Random(System.nanoTime());
private static final TestSocketUtils INSTANCE = new TestSocketUtils();
/**
* Although {@code TestSocketUtils} consists solely of static utility methods,
* this constructor is intentionally {@code public}.
*
Rationale
* Static methods from this class may be invoked from within XML
* configuration files using the Spring Expression Language (SpEL) and the
* following syntax.
*
* <bean id="myBean" ... p:port="#{T(org.springframework.test.util.TestSocketUtils).findAvailableTcpPort()}" />
*
* If this constructor were {@code private}, you would be required to supply
* the fully qualified class name to SpEL's {@code T()} function for each usage.
* Thus, the fact that this constructor is {@code public} allows you to reduce
* boilerplate configuration with SpEL as can be seen in the following example.
*
* <bean id="socketUtils" class="org.springframework.test.util.TestSocketUtils" />
* <bean id="myBean" ... p:port="#{socketUtils.findAvailableTcpPort()}" />
*
*/
public TestSocketUtils() {
}
/**
* Find an available TCP port randomly selected from the range [1024, 65535].
* @return an available TCP port number
* @throws IllegalStateException if no available port could be found
*/
public static int findAvailableTcpPort() {
return INSTANCE.findAvailableTcpPortInternal();
}
/**
* Internal implementation of {@link #findAvailableTcpPort()}.
* Package-private solely for testing purposes.
*/
int findAvailableTcpPortInternal() {
int candidatePort;
int searchCounter = 0;
do {
Assert.state(++searchCounter <= MAX_ATTEMPTS, () -> String.format(
"Could not find an available TCP port in the range [%d, %d] after %d attempts",
PORT_RANGE_MIN, PORT_RANGE_MAX, MAX_ATTEMPTS));
candidatePort = PORT_RANGE_MIN + random.nextInt(PORT_RANGE_PLUS_ONE);
}
while (!isPortAvailable(candidatePort));
return candidatePort;
}
/**
* Determine if the specified TCP port is currently available on {@code localhost}.
*
Package-private solely for testing purposes.
*/
boolean isPortAvailable(int port) {
try {
ServerSocket serverSocket = ServerSocketFactory.getDefault()
.createServerSocket(port, 1, InetAddress.getByName("localhost"));
serverSocket.close();
return true;
}
catch (Exception ex) {
return false;
}
}
}