Merge branch '1.3.x'

This commit is contained in:
Andy Wilkinson 2016-02-12 13:18:02 +00:00
commit 6e3faecce6
7 changed files with 104 additions and 12 deletions

View File

@ -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.
@ -18,6 +18,7 @@ package org.springframework.boot.devtools.tunnel.client;
import java.io.Closeable;
import java.io.IOException;
import java.net.ConnectException;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
@ -46,6 +47,7 @@ import org.springframework.util.Assert;
*
* @author Phillip Webb
* @author Rob Winch
* @author Andy Wilkinson
* @since 1.3.0
* @see TunnelClient
* @see org.springframework.boot.devtools.tunnel.server.HttpTunnelServer
@ -157,7 +159,13 @@ public class HttpTunnelConnection implements TunnelConnection {
sendAndReceive(payload);
}
catch (IOException ex) {
logger.trace("Unexpected connection error", ex);
if (ex instanceof ConnectException) {
logger.warn("Failed to connect to remote application at "
+ HttpTunnelConnection.this.uri);
}
else {
logger.trace("Unexpected connection error", ex);
}
closeQuietly();
}
}
@ -188,6 +196,12 @@ public class HttpTunnelConnection implements TunnelConnection {
close();
return;
}
if (response.getStatusCode() == HttpStatus.SERVICE_UNAVAILABLE) {
logger.warn("Remote application responded with service unavailable. Did "
+ "you forget to start it with remote debugging enabled?");
close();
return;
}
if (response.getStatusCode() == HttpStatus.OK) {
HttpTunnelPayload payload = HttpTunnelPayload.get(response);
if (payload != null) {

View File

@ -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.
@ -21,6 +21,7 @@ import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousCloseException;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.channels.WritableByteChannel;
@ -36,6 +37,7 @@ import org.springframework.util.Assert;
* specified port for local clients to connect to.
*
* @author Phillip Webb
* @author Andy Wilkinson
* @since 1.3.0
*/
public class TunnelClient implements SmartInitializingSingleton {
@ -143,6 +145,9 @@ public class TunnelClient implements SmartInitializingSingleton {
try {
handleConnection(socket);
}
catch (AsynchronousCloseException ex) {
// Connection has been closed. Keep the server running
}
finally {
socket.close();
}

View File

@ -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.
@ -88,6 +88,10 @@ import org.springframework.util.Assert;
* <td>410 (Gone)</td>
* <td>The target server has disconnected.</td>
* </tr>
* <tr>
* <td>503 (Service Unavailable)</td>
* <td>The target server is unavailable</td>
* </tr>
* </table>
* <p>
* Requests and responses that contain payloads include a {@code x-seq} header that
@ -96,6 +100,7 @@ import org.springframework.util.Assert;
* {@code 1}.
*
* @author Phillip Webb
* @author Andy Wilkinson
* @since 1.3.0
* @see org.springframework.boot.devtools.tunnel.client.HttpTunnelConnection
*/
@ -153,6 +158,9 @@ public class HttpTunnelServer {
catch (ConnectException ex) {
httpConnection.respond(HttpStatus.GONE);
}
catch (RemoteDebugNotRunningException ex) {
httpConnection.respond(HttpStatus.SERVICE_UNAVAILABLE);
}
}
/**

View File

@ -0,0 +1,26 @@
/*
* 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.
* You may obtain a copy of the License at
*
* http://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.boot.devtools.tunnel.server;
/**
* Exception thrown to indicate that remote debug is not running.
*
* @author Andy Wilkinson
*/
class RemoteDebugNotRunningException extends RuntimeException {
}

View File

@ -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.
@ -20,12 +20,12 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.lang.UsesUnsafeJava;
import org.springframework.util.Assert;
/**
* {@link PortProvider} that provides the port being used by the Java remote debugging.
*
* @author Phillip Webb
* @author Andy Wilkinson
*/
public class RemoteDebugPortProvider implements PortProvider {
@ -35,7 +35,9 @@ public class RemoteDebugPortProvider implements PortProvider {
@Override
public int getPort() {
Assert.state(isRemoteDebugRunning(), "Remote debug is not running");
if (!isRemoteDebugRunning()) {
throw new RemoteDebugNotRunningException();
}
return getRemoteDebugPort();
}

View File

@ -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);
}
}

View File

@ -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,10 +34,12 @@ 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.assertj.core.api.Assertions.assertThat;
import static org.hamcrest.Matchers.containsString;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
@ -46,12 +49,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;
@ -142,6 +149,25 @@ public class HttpTunnelConnectionTests {
assertThat(this.requestFactory.getExecutedRequests().size()).isGreaterThan(10);
}
@Test
public void serviceUnavailableResponseLogsWarningAndClosesTunnel() throws Exception {
this.requestFactory.willRespond(HttpStatus.SERVICE_UNAVAILABLE);
TunnelChannel tunnel = openTunnel(true);
assertThat(tunnel.isOpen()).isFalse();
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()).isFalse();
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()));
}