Improve handling of connection failures in remote debug tunnel
Previously, if an application had been started without remote debugging enabled, an attempt to connect to it via RemoteSpringApplication and the HTTP tunnel would result in the application being hammered by connection attempts for 30 seconds. This commit updates the tunnel server to respond with Service Unavailable (503) when a connection attempt is made and the JVM does not have remote debugging enabled. When the client receives a 503 response, it now logs a warning message describing the possible problem before closing the connection. The client has also been updated to provide improved diagnostics when a connection to the tunnel server cannot be established, for example because the remote URL is incorrect, or the remote application isn't running. Lastly, the client has been updated so that it continues to accept connections when a connection to the server is closed. This allows the user to correct a problem with the remote application, such as restarting it with remote debugging enabled, without having to also restart the process that's running RemoteSpringApplication. Closes gh-5021
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
* Copyright 2012-2016 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.
|
||||
@@ -37,12 +37,13 @@ import org.springframework.mock.http.client.MockClientHttpResponse;
|
||||
* Mock {@link ClientHttpRequestFactory}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class MockClientHttpRequestFactory implements ClientHttpRequestFactory {
|
||||
|
||||
private AtomicLong seq = new AtomicLong();
|
||||
|
||||
private Deque<Response> responses = new ArrayDeque<Response>();
|
||||
private Deque<Object> responses = new ArrayDeque<Object>();
|
||||
|
||||
private List<MockClientHttpRequest> executedRequests = new ArrayList<MockClientHttpRequest>();
|
||||
|
||||
@@ -58,6 +59,12 @@ public class MockClientHttpRequestFactory implements ClientHttpRequestFactory {
|
||||
}
|
||||
}
|
||||
|
||||
public void willRespond(IOException... response) {
|
||||
for (IOException exception : response) {
|
||||
this.responses.addLast(exception);
|
||||
}
|
||||
}
|
||||
|
||||
public void willRespond(String... response) {
|
||||
for (String payload : response) {
|
||||
this.responses.add(new Response(0, payload.getBytes(), HttpStatus.OK));
|
||||
@@ -81,11 +88,15 @@ public class MockClientHttpRequestFactory implements ClientHttpRequestFactory {
|
||||
@Override
|
||||
protected ClientHttpResponse executeInternal() throws IOException {
|
||||
MockClientHttpRequestFactory.this.executedRequests.add(this);
|
||||
Response response = MockClientHttpRequestFactory.this.responses.pollFirst();
|
||||
Object response = MockClientHttpRequestFactory.this.responses.pollFirst();
|
||||
if (response instanceof IOException) {
|
||||
throw (IOException) response;
|
||||
}
|
||||
if (response == null) {
|
||||
response = new Response(0, null, HttpStatus.GONE);
|
||||
}
|
||||
return response.asHttpResponse(MockClientHttpRequestFactory.this.seq);
|
||||
return ((Response) response)
|
||||
.asHttpResponse(MockClientHttpRequestFactory.this.seq);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
* Copyright 2012-2016 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.
|
||||
@@ -19,6 +19,7 @@ package org.springframework.boot.devtools.tunnel.client;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.Closeable;
|
||||
import java.io.IOException;
|
||||
import java.net.ConnectException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.channels.Channels;
|
||||
import java.nio.channels.WritableByteChannel;
|
||||
@@ -33,11 +34,14 @@ import org.mockito.MockitoAnnotations;
|
||||
|
||||
import org.springframework.boot.devtools.test.MockClientHttpRequestFactory;
|
||||
import org.springframework.boot.devtools.tunnel.client.HttpTunnelConnection.TunnelChannel;
|
||||
import org.springframework.boot.test.OutputCapture;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.util.SocketUtils;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.hamcrest.Matchers.greaterThan;
|
||||
import static org.hamcrest.Matchers.is;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
@@ -48,12 +52,16 @@ import static org.mockito.Mockito.verify;
|
||||
*
|
||||
* @author Phillip Webb
|
||||
* @author Rob Winch
|
||||
* @author Andy Wilkinson
|
||||
*/
|
||||
public class HttpTunnelConnectionTests {
|
||||
|
||||
@Rule
|
||||
public ExpectedException thrown = ExpectedException.none();
|
||||
|
||||
@Rule
|
||||
public OutputCapture outputCapture = new OutputCapture();
|
||||
|
||||
private int port = SocketUtils.findAvailableTcpPort();
|
||||
|
||||
private String url;
|
||||
@@ -144,6 +152,25 @@ public class HttpTunnelConnectionTests {
|
||||
assertThat(this.requestFactory.getExecutedRequests().size(), greaterThan(10));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void serviceUnavailableResponseLogsWarningAndClosesTunnel() throws Exception {
|
||||
this.requestFactory.willRespond(HttpStatus.SERVICE_UNAVAILABLE);
|
||||
TunnelChannel tunnel = openTunnel(true);
|
||||
assertThat(tunnel.isOpen(), is(false));
|
||||
this.outputCapture.expect(containsString(
|
||||
"Did you forget to start it with remote debugging enabled?"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void connectFailureLogsWarning() throws Exception {
|
||||
this.requestFactory.willRespond(new ConnectException());
|
||||
TunnelChannel tunnel = openTunnel(true);
|
||||
assertThat(tunnel.isOpen(), is(false));
|
||||
this.outputCapture.expect(containsString(
|
||||
"Failed to connect to remote application at http://localhost:"
|
||||
+ this.port));
|
||||
}
|
||||
|
||||
private void write(TunnelChannel channel, String string) throws IOException {
|
||||
channel.write(ByteBuffer.wrap(string.getBytes()));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user