Remove Devtools remote debugging support

Closes gh-9489
This commit is contained in:
Stephane Nicoll
2017-06-15 17:59:32 +02:00
parent bc4603925e
commit 8cae031441
16 changed files with 11 additions and 512 deletions

View File

@@ -38,10 +38,9 @@ import org.springframework.core.io.ClassPathResource;
/**
* Application that can be used to establish a link to remotely running Spring Boot code.
* Allows remote debugging and remote updates (if enabled). This class should be launched
* from within your IDE and should have the same classpath configuration as the locally
* developed application. The remote URL of the application should be provided as a
* non-option argument.
* Allows remote updates (if enabled). This class should be launched from within your IDE
* and should have the same classpath configuration as the locally developed application.
* The remote URL of the application should be provided as a non-option argument.
*
* @author Phillip Webb
* @since 1.3.0

View File

@@ -24,7 +24,6 @@ import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
@@ -45,10 +44,6 @@ import org.springframework.boot.devtools.restart.server.DefaultSourceFolderUrlFi
import org.springframework.boot.devtools.restart.server.HttpRestartServer;
import org.springframework.boot.devtools.restart.server.HttpRestartServerHandler;
import org.springframework.boot.devtools.restart.server.SourceFolderUrlFilter;
import org.springframework.boot.devtools.tunnel.server.HttpTunnelServer;
import org.springframework.boot.devtools.tunnel.server.HttpTunnelServerHandler;
import org.springframework.boot.devtools.tunnel.server.RemoteDebugPortProvider;
import org.springframework.boot.devtools.tunnel.server.SocketTargetServerConnection;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.Order;
@@ -148,39 +143,6 @@ public class RemoteDevToolsAutoConfiguration {
}
/**
* Configuration for remote debug HTTP tunneling.
*/
@ConditionalOnProperty(prefix = "spring.devtools.remote.debug", name = "enabled", matchIfMissing = true)
static class RemoteDebugTunnelConfiguration {
@Autowired
private DevToolsProperties properties;
@Autowired
private ServerProperties serverProperties;
@Bean
@ConditionalOnMissingBean(name = "remoteDebugHandlerMapper")
public UrlHandlerMapper remoteDebugHandlerMapper(
@Qualifier("remoteDebugHttpTunnelServer") HttpTunnelServer server) {
String url = (this.serverProperties.getServlet().getContextPath() == null ? ""
: this.serverProperties.getServlet().getContextPath())
+ this.properties.getRemote().getContextPath() + "/debug";
logger.warn("Listening for remote debug traffic on " + url);
Handler handler = new HttpTunnelServerHandler(server);
return new UrlHandlerMapper(url, handler);
}
@Bean
@ConditionalOnMissingBean(name = "remoteDebugHttpTunnelServer")
public HttpTunnelServer remoteDebugHttpTunnelServer() {
return new HttpTunnelServer(
new SocketTargetServerConnection(new RemoteDebugPortProvider()));
}
}
@Configuration
@ConditionalOnClass(WebSecurityConfigurerAdapter.class)
@ConditionalOnBean(ObjectPostProcessor.class)

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2015 the original author or authors.
* Copyright 2012-2017 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.
@@ -48,8 +48,6 @@ public class RemoteDevToolsProperties {
private Restart restart = new Restart();
private Debug debug = new Debug();
private Proxy proxy = new Proxy();
public String getContextPath() {
@@ -80,10 +78,6 @@ public class RemoteDevToolsProperties {
return this.restart;
}
public Debug getDebug() {
return this.debug;
}
public Proxy getProxy() {
return this.proxy;
}
@@ -105,38 +99,6 @@ public class RemoteDevToolsProperties {
}
public static class Debug {
public static final Integer DEFAULT_LOCAL_PORT = 8000;
/**
* Enable remote debug support.
*/
private boolean enabled = true;
/**
* Local remote debug server port.
*/
private int localPort = DEFAULT_LOCAL_PORT;
public boolean isEnabled() {
return this.enabled;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public int getLocalPort() {
return this.localPort;
}
public void setLocalPort(int localPort) {
this.localPort = localPort;
}
}
public static class Proxy {
/**

View File

@@ -1,60 +0,0 @@
/*
* Copyright 2012-2017 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.remote.client;
import javax.net.ServerSocketFactory;
import org.springframework.boot.autoconfigure.condition.ConditionMessage;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
import org.springframework.boot.devtools.autoconfigure.RemoteDevToolsProperties;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
/**
* Condition used to check that the actual local port is available.
*
* @author Phillip Webb
* @author Madhura Bhave
*/
class LocalDebugPortAvailableCondition extends SpringBootCondition {
@Override
public ConditionOutcome getMatchOutcome(ConditionContext context,
AnnotatedTypeMetadata metadata) {
ConditionMessage.Builder message = ConditionMessage
.forCondition("Local Debug Port Condition");
Integer port = context.getEnvironment().getProperty(
"spring.devtools.remote.debug.local-port", Integer.class,
RemoteDevToolsProperties.Debug.DEFAULT_LOCAL_PORT);
if (isPortAvailable(port)) {
return ConditionOutcome.match(message.foundExactly("local debug port"));
}
return ConditionOutcome.noMatch(message.didNotFind("local debug port").atAll());
}
private boolean isPortAvailable(int port) {
try {
ServerSocketFactory.getDefault().createServerSocket(port).close();
return true;
}
catch (Exception ex) {
return false;
}
}
}

View File

@@ -1,46 +0,0 @@
/*
* Copyright 2012-2015 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.remote.client;
import java.nio.channels.SocketChannel;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.devtools.tunnel.client.TunnelClientListener;
/**
* {@link TunnelClientListener} to log open/close events.
*
* @author Phillip Webb
*/
class LoggingTunnelClientListener implements TunnelClientListener {
private static final Log logger = LogFactory
.getLog(LoggingTunnelClientListener.class);
@Override
public void onOpen(SocketChannel socket) {
logger.info("Remote debug connection opened");
}
@Override
public void onClose(SocketChannel socket) {
logger.info("Remote debug connection closed");
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2016 the original author or authors.
* Copyright 2012-2017 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.
@@ -25,14 +25,12 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.annotation.PostConstruct;
import javax.servlet.Filter;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
@@ -52,11 +50,7 @@ import org.springframework.boot.devtools.livereload.LiveReloadServer;
import org.springframework.boot.devtools.restart.DefaultRestartInitializer;
import org.springframework.boot.devtools.restart.RestartScope;
import org.springframework.boot.devtools.restart.Restarter;
import org.springframework.boot.devtools.tunnel.client.HttpTunnelConnection;
import org.springframework.boot.devtools.tunnel.client.TunnelClient;
import org.springframework.boot.devtools.tunnel.client.TunnelConnection;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.EventListener;
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
@@ -120,9 +114,8 @@ public class RemoteClientConfiguration {
@PostConstruct
private void logWarnings() {
RemoteDevToolsProperties remoteProperties = this.properties.getRemote();
if (!remoteProperties.getDebug().isEnabled()
&& !remoteProperties.getRestart().isEnabled()) {
logger.warn("Remote restart and debug are both disabled.");
if (!remoteProperties.getRestart().isEnabled()) {
logger.warn("Remote restart is disabled.");
}
if (!this.remoteUrl.startsWith("https://")) {
logger.warn("The connection to " + this.remoteUrl
@@ -239,32 +232,4 @@ public class RemoteClientConfiguration {
}
/**
* Client configuration for remote debug HTTP tunneling.
*/
@ConditionalOnProperty(prefix = "spring.devtools.remote.debug", name = "enabled", matchIfMissing = true)
@ConditionalOnClass(Filter.class)
@Conditional(LocalDebugPortAvailableCondition.class)
static class RemoteDebugTunnelClientConfiguration {
@Autowired
private DevToolsProperties properties;
@Value("${remoteUrl}")
private String remoteUrl;
@Bean
public TunnelClient remoteDebugTunnelClient(
ClientHttpRequestFactory requestFactory) {
RemoteDevToolsProperties remoteProperties = this.properties.getRemote();
String url = this.remoteUrl + remoteProperties.getContextPath() + "/debug";
TunnelConnection connection = new HttpTunnelConnection(url, requestFactory);
int localPort = remoteProperties.getDebug().getLocalPort();
TunnelClient client = new TunnelClient(localPort, connection);
client.addListener(new LoggingTunnelClientListener());
return client;
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2012-2016 the original author or authors.
* Copyright 2012-2017 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.
@@ -196,12 +196,6 @@ 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

@@ -158,9 +158,6 @@ public class HttpTunnelServer {
catch (ConnectException ex) {
httpConnection.respond(HttpStatus.GONE);
}
catch (RemoteDebugNotRunningException ex) {
httpConnection.respond(HttpStatus.SERVICE_UNAVAILABLE);
}
}
/**

View File

@@ -1,26 +0,0 @@
/*
* 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,65 +0,0 @@
/*
* 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;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.boot.lang.UsesUnsafeJava;
/**
* {@link PortProvider} that provides the port being used by the Java remote debugging.
*
* @author Phillip Webb
* @author Andy Wilkinson
*/
public class RemoteDebugPortProvider implements PortProvider {
private static final String JDWP_ADDRESS_PROPERTY = "sun.jdwp.listenerAddress";
private static final Log logger = LogFactory.getLog(RemoteDebugPortProvider.class);
@Override
public int getPort() {
if (!isRemoteDebugRunning()) {
throw new RemoteDebugNotRunningException();
}
return getRemoteDebugPort();
}
public static boolean isRemoteDebugRunning() {
return getRemoteDebugPort() != -1;
}
@UsesUnsafeJava
@SuppressWarnings("restriction")
private static int getRemoteDebugPort() {
String property = sun.misc.VMSupport.getAgentProperties()
.getProperty(JDWP_ADDRESS_PROPERTY);
try {
if (property != null && property.contains(":")) {
return Integer.valueOf(property.split(":")[1]);
}
}
catch (Exception ex) {
logger.trace(
"Unable to get JDWP port from property value '" + property + "'");
}
return -1;
}
}

View File

@@ -31,8 +31,6 @@ import org.springframework.boot.devtools.restart.MockRestarter;
import org.springframework.boot.devtools.restart.server.HttpRestartServer;
import org.springframework.boot.devtools.restart.server.SourceFolderUrlFilter;
import org.springframework.boot.devtools.tunnel.server.HttpTunnelServer;
import org.springframework.boot.devtools.tunnel.server.RemoteDebugPortProvider;
import org.springframework.boot.devtools.tunnel.server.SocketTargetServerConnection;
import org.springframework.boot.devtools.tunnel.server.TargetServerConnection;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.context.annotation.Bean;
@@ -154,46 +152,6 @@ public class RemoteDevToolsAutoConfigurationTests {
this.context.getBean("remoteRestartHandlerMapper");
}
@Test
public void invokeTunnelWithDefaultSetup() throws Exception {
loadContext("spring.devtools.remote.secret:supersecret");
DispatcherFilter filter = this.context.getBean(DispatcherFilter.class);
this.request.setRequestURI(DEFAULT_CONTEXT_PATH + "/debug");
this.request.addHeader(DEFAULT_SECRET_HEADER_NAME, "supersecret");
filter.doFilter(this.request, this.response, this.chain);
assertTunnelInvoked(true);
}
@Test
public void invokeTunnelWithCustomServerContextPath() throws Exception {
loadContext("spring.devtools.remote.secret:supersecret",
"server.servlet.context-path:/test");
DispatcherFilter filter = this.context.getBean(DispatcherFilter.class);
this.request.setRequestURI("/test" + DEFAULT_CONTEXT_PATH + "/debug");
this.request.addHeader(DEFAULT_SECRET_HEADER_NAME, "supersecret");
filter.doFilter(this.request, this.response, this.chain);
assertTunnelInvoked(true);
}
@Test
public void invokeTunnelWithCustomHeaderName() throws Exception {
loadContext("spring.devtools.remote.secret:supersecret",
"spring.devtools.remote.secretHeaderName:customheader");
DispatcherFilter filter = this.context.getBean(DispatcherFilter.class);
this.request.setRequestURI(DEFAULT_CONTEXT_PATH + "/debug");
this.request.addHeader("customheader", "supersecret");
filter.doFilter(this.request, this.response, this.chain);
assertTunnelInvoked(true);
}
@Test
public void disableRemoteDebug() throws Exception {
loadContext("spring.devtools.remote.secret:supersecret",
"spring.devtools.remote.debug.enabled:false");
this.thrown.expect(NoSuchBeanDefinitionException.class);
this.context.getBean("remoteDebugHandlerMapper");
}
@Test
public void devToolsHealthReturns200() throws Exception {
loadContext("spring.devtools.remote.secret:supersecret");
@@ -239,12 +197,6 @@ public class RemoteDevToolsAutoConfigurationTests {
@Import(RemoteDevToolsAutoConfiguration.class)
static class Config {
@Bean
public HttpTunnelServer remoteDebugHttpTunnelServer() {
return new MockHttpTunnelServer(
new SocketTargetServerConnection(new RemoteDebugPortProvider()));
}
@Bean
public HttpRestartServer remoteRestartHttpRestartServer() {
SourceFolderUrlFilter sourceFolderUrlFilter = mock(

View File

@@ -1,72 +0,0 @@
/*
* Copyright 2012-2017 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.remote.client;
import java.net.ServerSocket;
import javax.net.ServerSocketFactory;
import org.junit.Test;
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.mock.env.MockEnvironment;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
/**
* Tests for {@link LocalDebugPortAvailableCondition}.
*
* @author Phillip Webb
*/
public class LocalDebugPortAvailableConditionTests {
private LocalDebugPortAvailableCondition condition = new LocalDebugPortAvailableCondition();
@Test
public void portAvailable() throws Exception {
ConditionOutcome outcome = getOutcome(0);
assertThat(outcome.isMatch()).isTrue();
assertThat(outcome.getMessage())
.isEqualTo("Local Debug Port Condition found local debug port");
}
@Test
public void portInUse() throws Exception {
ServerSocket serverSocket = ServerSocketFactory.getDefault()
.createServerSocket(0);
ConditionOutcome outcome = getOutcome(serverSocket.getLocalPort());
serverSocket.close();
assertThat(outcome.isMatch()).isFalse();
assertThat(outcome.getMessage())
.isEqualTo("Local Debug Port Condition did not find local debug port");
}
private ConditionOutcome getOutcome(int port) {
MockEnvironment environment = new MockEnvironment();
TestPropertyValues.of("spring.devtools.remote.debug.local-port:" + port)
.applyTo(environment);
ConditionContext context = mock(ConditionContext.class);
given(context.getEnvironment()).willReturn(environment);
ConditionOutcome outcome = this.condition.getMatchOutcome(context, null);
return outcome;
}
}

View File

@@ -38,7 +38,6 @@ import org.springframework.boot.devtools.remote.server.Dispatcher;
import org.springframework.boot.devtools.remote.server.DispatcherFilter;
import org.springframework.boot.devtools.restart.MockRestarter;
import org.springframework.boot.devtools.restart.RestartScopeInitializer;
import org.springframework.boot.devtools.tunnel.client.TunnelClient;
import org.springframework.boot.test.rule.OutputCapture;
import org.springframework.boot.test.util.TestPropertyValues;
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
@@ -86,11 +85,10 @@ public class RemoteClientConfigurationTests {
}
@Test
public void warnIfDebugAndRestartDisabled() throws Exception {
configure("spring.devtools.remote.debug.enabled:false",
"spring.devtools.remote.restart.enabled:false");
public void warnIfRestartDisabled() throws Exception {
configure("spring.devtools.remote.restart.enabled:false");
assertThat(this.output.toString())
.contains("Remote restart and debug are both disabled");
.contains("Remote restart is disabled");
}
@Test
@@ -140,13 +138,6 @@ public class RemoteClientConfigurationTests {
this.context.getBean(ClassPathFileSystemWatcher.class);
}
@Test
public void remoteDebugDisabled() throws Exception {
configure("spring.devtools.remote.debug.enabled:false");
this.thrown.expect(NoSuchBeanDefinitionException.class);
this.context.getBean(TunnelClient.class);
}
private void configure(String... pairs) {
configure("http://localhost", true, pairs);
}

View File

@@ -146,15 +146,6 @@ 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());