Protect against available port actually being unavailable
Closes gh-19355
This commit is contained in:
		
							parent
							
								
									f75c73eb7a
								
							
						
					
					
						commit
						3cadde0900
					
				| 
						 | 
					@ -288,7 +288,7 @@ public class TomcatServletWebServerFactoryTests extends AbstractServletWebServer
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Test
 | 
						@Test
 | 
				
			||||||
	public void primaryConnectorPortClashThrowsWebServerException() throws IOException {
 | 
						public void primaryConnectorPortClashThrowsWebServerException() throws Exception {
 | 
				
			||||||
		doWithBlockedPort((port) -> {
 | 
							doWithBlockedPort((port) -> {
 | 
				
			||||||
			TomcatServletWebServerFactory factory = getFactory();
 | 
								TomcatServletWebServerFactory factory = getFactory();
 | 
				
			||||||
			factory.setPort(port);
 | 
								factory.setPort(port);
 | 
				
			||||||
| 
						 | 
					@ -300,7 +300,7 @@ public class TomcatServletWebServerFactoryTests extends AbstractServletWebServer
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Test
 | 
						@Test
 | 
				
			||||||
	public void startupFailureDoesNotResultInUnstoppedThreadsBeingReported() throws IOException {
 | 
						public void startupFailureDoesNotResultInUnstoppedThreadsBeingReported() throws Exception {
 | 
				
			||||||
		super.portClashOfPrimaryConnectorResultsInPortInUseException();
 | 
							super.portClashOfPrimaryConnectorResultsInPortInUseException();
 | 
				
			||||||
		String string = this.outputCapture.toString();
 | 
							String string = this.outputCapture.toString();
 | 
				
			||||||
		assertThat(string).doesNotContain("appears to have started a thread named [main]");
 | 
							assertThat(string).doesNotContain("appears to have started a thread named [main]");
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -23,6 +23,7 @@ import java.nio.charset.StandardCharsets;
 | 
				
			||||||
import java.security.KeyStore;
 | 
					import java.security.KeyStore;
 | 
				
			||||||
import java.time.Duration;
 | 
					import java.time.Duration;
 | 
				
			||||||
import java.util.Arrays;
 | 
					import java.util.Arrays;
 | 
				
			||||||
 | 
					import java.util.concurrent.Callable;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import javax.net.ssl.KeyManagerFactory;
 | 
					import javax.net.ssl.KeyManagerFactory;
 | 
				
			||||||
import javax.net.ssl.SSLException;
 | 
					import javax.net.ssl.SSLException;
 | 
				
			||||||
| 
						 | 
					@ -92,12 +93,15 @@ public abstract class AbstractReactiveWebServerFactoryTests {
 | 
				
			||||||
	protected abstract AbstractReactiveWebServerFactory getFactory();
 | 
						protected abstract AbstractReactiveWebServerFactory getFactory();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Test
 | 
						@Test
 | 
				
			||||||
	public void specificPort() {
 | 
						public void specificPort() throws Exception {
 | 
				
			||||||
		AbstractReactiveWebServerFactory factory = getFactory();
 | 
							AbstractReactiveWebServerFactory factory = getFactory();
 | 
				
			||||||
		int specificPort = SocketUtils.findAvailableTcpPort(41000);
 | 
							int specificPort = doWithRetry(() -> {
 | 
				
			||||||
		factory.setPort(specificPort);
 | 
								int port = SocketUtils.findAvailableTcpPort(41000);
 | 
				
			||||||
		this.webServer = factory.getWebServer(new EchoHandler());
 | 
								factory.setPort(port);
 | 
				
			||||||
		this.webServer.start();
 | 
								this.webServer = factory.getWebServer(new EchoHandler());
 | 
				
			||||||
 | 
								this.webServer.start();
 | 
				
			||||||
 | 
								return port;
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
		Mono<String> result = getWebClient().build().post().uri("/test").contentType(MediaType.TEXT_PLAIN)
 | 
							Mono<String> result = getWebClient().build().post().uri("/test").contentType(MediaType.TEXT_PLAIN)
 | 
				
			||||||
				.body(BodyInserters.fromObject("Hello World")).exchange()
 | 
									.body(BodyInserters.fromObject("Hello World")).exchange()
 | 
				
			||||||
				.flatMap((response) -> response.bodyToMono(String.class));
 | 
									.flatMap((response) -> response.bodyToMono(String.class));
 | 
				
			||||||
| 
						 | 
					@ -113,6 +117,7 @@ public abstract class AbstractReactiveWebServerFactoryTests {
 | 
				
			||||||
	@Test
 | 
						@Test
 | 
				
			||||||
	public void basicSslFromFileSystem() {
 | 
						public void basicSslFromFileSystem() {
 | 
				
			||||||
		testBasicSslWithKeyStore("src/test/resources/test.jks", "password");
 | 
							testBasicSslWithKeyStore("src/test/resources/test.jks", "password");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	protected final void testBasicSslWithKeyStore(String keyStore, String keyPassword) {
 | 
						protected final void testBasicSslWithKeyStore(String keyStore, String keyPassword) {
 | 
				
			||||||
| 
						 | 
					@ -335,6 +340,19 @@ public abstract class AbstractReactiveWebServerFactoryTests {
 | 
				
			||||||
		assertThat(body).isEqualTo("https");
 | 
							assertThat(body).isEqualTo("https");
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						private <T> T doWithRetry(Callable<T> action) throws Exception {
 | 
				
			||||||
 | 
							Exception lastFailure = null;
 | 
				
			||||||
 | 
							for (int i = 0; i < 10; i++) {
 | 
				
			||||||
 | 
								try {
 | 
				
			||||||
 | 
									return action.call();
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
								catch (Exception ex) {
 | 
				
			||||||
 | 
									lastFailure = ex;
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							throw new IllegalStateException("Action was not successful in 10 attempts", lastFailure);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	protected static class EchoHandler implements HttpHandler {
 | 
						protected static class EchoHandler implements HttpHandler {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		public EchoHandler() {
 | 
							public EchoHandler() {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -44,6 +44,7 @@ import java.util.EnumSet;
 | 
				
			||||||
import java.util.HashMap;
 | 
					import java.util.HashMap;
 | 
				
			||||||
import java.util.Locale;
 | 
					import java.util.Locale;
 | 
				
			||||||
import java.util.Map;
 | 
					import java.util.Map;
 | 
				
			||||||
 | 
					import java.util.concurrent.Callable;
 | 
				
			||||||
import java.util.concurrent.atomic.AtomicBoolean;
 | 
					import java.util.concurrent.atomic.AtomicBoolean;
 | 
				
			||||||
import java.util.concurrent.atomic.AtomicReference;
 | 
					import java.util.concurrent.atomic.AtomicReference;
 | 
				
			||||||
import java.util.zip.GZIPInputStream;
 | 
					import java.util.zip.GZIPInputStream;
 | 
				
			||||||
| 
						 | 
					@ -248,10 +249,13 @@ public abstract class AbstractServletWebServerFactoryTests {
 | 
				
			||||||
	@Test
 | 
						@Test
 | 
				
			||||||
	public void specificPort() throws Exception {
 | 
						public void specificPort() throws Exception {
 | 
				
			||||||
		AbstractServletWebServerFactory factory = getFactory();
 | 
							AbstractServletWebServerFactory factory = getFactory();
 | 
				
			||||||
		int specificPort = SocketUtils.findAvailableTcpPort(41000);
 | 
							int specificPort = doWithRetry(() -> {
 | 
				
			||||||
		factory.setPort(specificPort);
 | 
								int port = SocketUtils.findAvailableTcpPort(41000);
 | 
				
			||||||
		this.webServer = factory.getWebServer(exampleServletRegistration());
 | 
								factory.setPort(port);
 | 
				
			||||||
		this.webServer.start();
 | 
								this.webServer = factory.getWebServer(exampleServletRegistration());
 | 
				
			||||||
 | 
								this.webServer.start();
 | 
				
			||||||
 | 
								return port;
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
		assertThat(getResponse("http://localhost:" + specificPort + "/hello")).isEqualTo("Hello World");
 | 
							assertThat(getResponse("http://localhost:" + specificPort + "/hello")).isEqualTo("Hello World");
 | 
				
			||||||
		assertThat(this.webServer.getPort()).isEqualTo(specificPort);
 | 
							assertThat(this.webServer.getPort()).isEqualTo(specificPort);
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
| 
						 | 
					@ -822,7 +826,7 @@ public abstract class AbstractServletWebServerFactoryTests {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Test
 | 
						@Test
 | 
				
			||||||
	public void portClashOfPrimaryConnectorResultsInPortInUseException() throws IOException {
 | 
						public void portClashOfPrimaryConnectorResultsInPortInUseException() throws Exception {
 | 
				
			||||||
		doWithBlockedPort((port) -> {
 | 
							doWithBlockedPort((port) -> {
 | 
				
			||||||
			assertThatExceptionOfType(RuntimeException.class).isThrownBy(() -> {
 | 
								assertThatExceptionOfType(RuntimeException.class).isThrownBy(() -> {
 | 
				
			||||||
				AbstractServletWebServerFactory factory = getFactory();
 | 
									AbstractServletWebServerFactory factory = getFactory();
 | 
				
			||||||
| 
						 | 
					@ -834,7 +838,7 @@ public abstract class AbstractServletWebServerFactoryTests {
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	@Test
 | 
						@Test
 | 
				
			||||||
	public void portClashOfSecondaryConnectorResultsInPortInUseException() throws IOException {
 | 
						public void portClashOfSecondaryConnectorResultsInPortInUseException() throws Exception {
 | 
				
			||||||
		doWithBlockedPort((port) -> {
 | 
							doWithBlockedPort((port) -> {
 | 
				
			||||||
			assertThatExceptionOfType(RuntimeException.class).isThrownBy(() -> {
 | 
								assertThatExceptionOfType(RuntimeException.class).isThrownBy(() -> {
 | 
				
			||||||
				AbstractServletWebServerFactory factory = getFactory();
 | 
									AbstractServletWebServerFactory factory = getFactory();
 | 
				
			||||||
| 
						 | 
					@ -1128,19 +1132,28 @@ public abstract class AbstractServletWebServerFactoryTests {
 | 
				
			||||||
		return bean;
 | 
							return bean;
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	protected final void doWithBlockedPort(BlockedPortAction action) throws IOException {
 | 
						private <T> T doWithRetry(Callable<T> action) throws Exception {
 | 
				
			||||||
		int port = SocketUtils.findAvailableTcpPort(40000);
 | 
							Exception lastFailure = null;
 | 
				
			||||||
		ServerSocket serverSocket = new ServerSocket();
 | 
					 | 
				
			||||||
		for (int i = 0; i < 10; i++) {
 | 
							for (int i = 0; i < 10; i++) {
 | 
				
			||||||
			try {
 | 
								try {
 | 
				
			||||||
				serverSocket.bind(new InetSocketAddress(port));
 | 
									return action.call();
 | 
				
			||||||
				break;
 | 
					 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
			catch (Exception ex) {
 | 
								catch (Exception ex) {
 | 
				
			||||||
 | 
									lastFailure = ex;
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
							throw new IllegalStateException("Action was not successful in 10 attempts", lastFailure);
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						protected final void doWithBlockedPort(BlockedPortAction action) throws Exception {
 | 
				
			||||||
 | 
							ServerSocket serverSocket = new ServerSocket();
 | 
				
			||||||
 | 
							int blockedPort = doWithRetry(() -> {
 | 
				
			||||||
 | 
								int port = SocketUtils.findAvailableTcpPort(40000);
 | 
				
			||||||
 | 
								serverSocket.bind(new InetSocketAddress(port));
 | 
				
			||||||
 | 
								return port;
 | 
				
			||||||
 | 
							});
 | 
				
			||||||
		try {
 | 
							try {
 | 
				
			||||||
			action.run(port);
 | 
								action.run(blockedPort);
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		finally {
 | 
							finally {
 | 
				
			||||||
			serverSocket.close();
 | 
								serverSocket.close();
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue