Add remote debug tunnel auto-configuration
Provide auto-configuration for remote debugging over a HTTP tunnel. The RemoteClientConfiguration provides a server that the local IDE can connect to. When a client connects the remote connection is established using the HTTP tunnel. See gh-3087
This commit is contained in:
@@ -23,6 +23,7 @@ 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.Qualifier;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
@@ -39,6 +40,10 @@ import org.springframework.boot.developertools.restart.server.DefaultSourceFolde
|
||||
import org.springframework.boot.developertools.restart.server.HttpRestartServer;
|
||||
import org.springframework.boot.developertools.restart.server.HttpRestartServerHandler;
|
||||
import org.springframework.boot.developertools.restart.server.SourceFolderUrlFilter;
|
||||
import org.springframework.boot.developertools.tunnel.server.HttpTunnelServer;
|
||||
import org.springframework.boot.developertools.tunnel.server.HttpTunnelServerHandler;
|
||||
import org.springframework.boot.developertools.tunnel.server.RemoteDebugPortProvider;
|
||||
import org.springframework.boot.developertools.tunnel.server.SocketTargetServerConnection;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.server.ServerHttpRequest;
|
||||
@@ -109,4 +114,32 @@ public class RemoteDeveloperToolsAutoConfiguration {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Configuration for remote debug HTTP tunneling.
|
||||
*/
|
||||
@ConditionalOnProperty(prefix = "spring.developertools.remote.debug", name = "enabled", matchIfMissing = true)
|
||||
static class RemoteDebugTunnelConfiguration {
|
||||
|
||||
@Autowired
|
||||
private DeveloperToolsProperties properties;
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean(name = "remoteDebugHanderMapper")
|
||||
public UrlHandlerMapper remoteDebugHanderMapper(
|
||||
@Qualifier("remoteDebugHttpTunnelServer") HttpTunnelServer server) {
|
||||
String url = 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()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -35,6 +35,8 @@ public class RemoteDeveloperToolsProperties {
|
||||
|
||||
private Restart restart = new Restart();
|
||||
|
||||
private Debug debug = new Debug();
|
||||
|
||||
public String getContextPath() {
|
||||
return this.contextPath;
|
||||
}
|
||||
@@ -47,6 +49,10 @@ public class RemoteDeveloperToolsProperties {
|
||||
return this.restart;
|
||||
}
|
||||
|
||||
public Debug getDebug() {
|
||||
return this.debug;
|
||||
}
|
||||
|
||||
public static class Restart {
|
||||
|
||||
/**
|
||||
@@ -64,4 +70,36 @@ public class RemoteDeveloperToolsProperties {
|
||||
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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.developertools.remote.client;
|
||||
|
||||
import javax.net.ServerSocketFactory;
|
||||
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
|
||||
import org.springframework.boot.autoconfigure.condition.SpringBootCondition;
|
||||
import org.springframework.boot.bind.RelaxedPropertyResolver;
|
||||
import org.springframework.boot.developertools.autoconfigure.RemoteDeveloperToolsProperties;
|
||||
import org.springframework.context.annotation.ConditionContext;
|
||||
import org.springframework.core.type.AnnotatedTypeMetadata;
|
||||
|
||||
/**
|
||||
* Condition used to check that the actual local port is available.
|
||||
*/
|
||||
class LocalDebugPortAvailableCondition extends SpringBootCondition {
|
||||
|
||||
@Override
|
||||
public ConditionOutcome getMatchOutcome(ConditionContext context,
|
||||
AnnotatedTypeMetadata metadata) {
|
||||
RelaxedPropertyResolver resolver = new RelaxedPropertyResolver(
|
||||
context.getEnvironment(), "spring.developertools.remote.debug.");
|
||||
Integer port = resolver.getProperty("local-port", Integer.class);
|
||||
if (port == null) {
|
||||
port = RemoteDeveloperToolsProperties.Debug.DEFAULT_LOCAL_PORT;
|
||||
}
|
||||
if (isPortAvailable(port)) {
|
||||
return ConditionOutcome.match("Local debug port availble");
|
||||
}
|
||||
return ConditionOutcome.noMatch("Local debug port unavailble");
|
||||
}
|
||||
|
||||
private boolean isPortAvailable(int port) {
|
||||
try {
|
||||
ServerSocketFactory.getDefault().createServerSocket(port).close();
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* 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.developertools.remote.client;
|
||||
|
||||
import java.nio.channels.SocketChannel;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.boot.developertools.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");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -21,11 +21,13 @@ 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;
|
||||
@@ -40,7 +42,11 @@ import org.springframework.boot.developertools.livereload.LiveReloadServer;
|
||||
import org.springframework.boot.developertools.restart.DefaultRestartInitializer;
|
||||
import org.springframework.boot.developertools.restart.RestartScope;
|
||||
import org.springframework.boot.developertools.restart.Restarter;
|
||||
import org.springframework.boot.developertools.tunnel.client.HttpTunnelConnection;
|
||||
import org.springframework.boot.developertools.tunnel.client.TunnelClient;
|
||||
import org.springframework.boot.developertools.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;
|
||||
@@ -79,8 +85,9 @@ public class RemoteClientConfiguration {
|
||||
@PostConstruct
|
||||
private void logWarnings() {
|
||||
RemoteDeveloperToolsProperties remoteProperties = this.properties.getRemote();
|
||||
if (!remoteProperties.getRestart().isEnabled()) {
|
||||
logger.warn("Remote restart is not enabled.");
|
||||
if (!remoteProperties.getDebug().isEnabled()
|
||||
&& !remoteProperties.getRestart().isEnabled()) {
|
||||
logger.warn("Remote restart and debug are both disabled.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,4 +175,32 @@ public class RemoteClientConfiguration {
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Client configuration for remote debug HTTP tunneling.
|
||||
*/
|
||||
@ConditionalOnProperty(prefix = "spring.developertools.remote.debug", name = "enabled", matchIfMissing = true)
|
||||
@ConditionalOnClass(Filter.class)
|
||||
@Conditional(LocalDebugPortAvailableCondition.class)
|
||||
static class RemoteDebugTunnelClientConfiguration {
|
||||
|
||||
@Autowired
|
||||
private DeveloperToolsProperties properties;
|
||||
|
||||
@Value("${remoteUrl}")
|
||||
private String remoteUrl;
|
||||
|
||||
@Bean
|
||||
public TunnelClient remoteDebugTunnelClient(
|
||||
ClientHttpRequestFactory requestFactory) {
|
||||
RemoteDeveloperToolsProperties 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user