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:
parent
2123b267aa
commit
bdf7663a9a
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -30,6 +30,10 @@ import org.springframework.boot.developertools.remote.server.DispatcherFilter;
|
|||
import org.springframework.boot.developertools.restart.MockRestarter;
|
||||
import org.springframework.boot.developertools.restart.server.HttpRestartServer;
|
||||
import org.springframework.boot.developertools.restart.server.SourceFolderUrlFilter;
|
||||
import org.springframework.boot.developertools.tunnel.server.HttpTunnelServer;
|
||||
import org.springframework.boot.developertools.tunnel.server.RemoteDebugPortProvider;
|
||||
import org.springframework.boot.developertools.tunnel.server.SocketTargetServerConnection;
|
||||
import org.springframework.boot.developertools.tunnel.server.TargetServerConnection;
|
||||
import org.springframework.boot.test.EnvironmentTestUtils;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
|
@ -110,6 +114,23 @@ public class RemoteDeveloperToolsAutoConfigurationTests {
|
|||
this.context.getBean("remoteRestartHanderMapper");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void invokeTunnelWithDefaultSetup() throws Exception {
|
||||
loadContext("spring.developertools.remote.enabled:true");
|
||||
DispatcherFilter filter = this.context.getBean(DispatcherFilter.class);
|
||||
this.request.setRequestURI(DEFAULT_CONTEXT_PATH + "/debug");
|
||||
filter.doFilter(this.request, this.response, this.chain);
|
||||
assertTunnelInvoked(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void disableRemoteDebug() throws Exception {
|
||||
loadContext("spring.developertools.remote.enabled:true",
|
||||
"spring.developertools.remote.debug.enabled:false");
|
||||
this.thrown.expect(NoSuchBeanDefinitionException.class);
|
||||
this.context.getBean("remoteDebugHanderMapper");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void developerToolsHealthReturns200() throws Exception {
|
||||
loadContext("spring.developertools.remote.enabled:true");
|
||||
|
|
@ -120,6 +141,11 @@ public class RemoteDeveloperToolsAutoConfigurationTests {
|
|||
assertThat(this.response.getStatus(), equalTo(200));
|
||||
}
|
||||
|
||||
private void assertTunnelInvoked(boolean value) {
|
||||
assertThat(this.context.getBean(MockHttpTunnelServer.class).invoked,
|
||||
equalTo(value));
|
||||
}
|
||||
|
||||
private void assertRestartInvoked(boolean value) {
|
||||
assertThat(this.context.getBean(MockHttpRestartServer.class).invoked,
|
||||
equalTo(value));
|
||||
|
|
@ -138,6 +164,12 @@ public class RemoteDeveloperToolsAutoConfigurationTests {
|
|||
@Import(RemoteDeveloperToolsAutoConfiguration.class)
|
||||
static class Config {
|
||||
|
||||
@Bean
|
||||
public HttpTunnelServer remoteDebugHttpTunnelServer() {
|
||||
return new MockHttpTunnelServer(new SocketTargetServerConnection(
|
||||
new RemoteDebugPortProvider()));
|
||||
}
|
||||
|
||||
@Bean
|
||||
public HttpRestartServer remoteRestartHttpRestartServer() {
|
||||
SourceFolderUrlFilter sourceFolderUrlFilter = mock(SourceFolderUrlFilter.class);
|
||||
|
|
@ -146,6 +178,25 @@ public class RemoteDeveloperToolsAutoConfigurationTests {
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock {@link HttpTunnelServer} implementation.
|
||||
*/
|
||||
static class MockHttpTunnelServer extends HttpTunnelServer {
|
||||
|
||||
private boolean invoked;
|
||||
|
||||
public MockHttpTunnelServer(TargetServerConnection serverConnection) {
|
||||
super(serverConnection);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(ServerHttpRequest request, ServerHttpResponse response)
|
||||
throws IOException {
|
||||
this.invoked = true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Mock {@link HttpRestartServer} implementation.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -0,0 +1,144 @@
|
|||
/*
|
||||
* 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.integrationtest;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory;
|
||||
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
|
||||
import org.springframework.boot.developertools.remote.server.AccessManager;
|
||||
import org.springframework.boot.developertools.remote.server.Dispatcher;
|
||||
import org.springframework.boot.developertools.remote.server.DispatcherFilter;
|
||||
import org.springframework.boot.developertools.remote.server.HandlerMapper;
|
||||
import org.springframework.boot.developertools.remote.server.UrlHandlerMapper;
|
||||
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.boot.developertools.tunnel.server.HttpTunnelServer;
|
||||
import org.springframework.boot.developertools.tunnel.server.HttpTunnelServerHandler;
|
||||
import org.springframework.boot.developertools.tunnel.server.PortProvider;
|
||||
import org.springframework.boot.developertools.tunnel.server.SocketTargetServerConnection;
|
||||
import org.springframework.boot.developertools.tunnel.server.StaticPortProvider;
|
||||
import org.springframework.boot.developertools.tunnel.server.TargetServerConnection;
|
||||
import org.springframework.boot.test.SpringApplicationConfiguration;
|
||||
import org.springframework.boot.test.TestRestTemplate;
|
||||
import org.springframework.boot.test.WebIntegrationTest;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.http.client.SimpleClientHttpRequestFactory;
|
||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||
import org.springframework.util.SocketUtils;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import org.springframework.web.servlet.DispatcherServlet;
|
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* Simple integration tests for HTTP tunneling.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@SpringApplicationConfiguration(classes = HttpTunnelIntegrationTest.Config.class)
|
||||
@WebIntegrationTest
|
||||
public class HttpTunnelIntegrationTest {
|
||||
|
||||
@Autowired
|
||||
private Config config;
|
||||
|
||||
@Test
|
||||
public void httpServerDirect() throws Exception {
|
||||
String url = "http://localhost:" + this.config.httpServerPort + "/hello";
|
||||
ResponseEntity<String> entity = new TestRestTemplate().getForEntity(url,
|
||||
String.class);
|
||||
assertEquals(HttpStatus.OK, entity.getStatusCode());
|
||||
assertEquals("Hello World", entity.getBody());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void viaTunnel() throws Exception {
|
||||
String url = "http://localhost:" + this.config.clientPort + "/hello";
|
||||
ResponseEntity<String> entity = new TestRestTemplate().getForEntity(url,
|
||||
String.class);
|
||||
assertEquals(HttpStatus.OK, entity.getStatusCode());
|
||||
assertEquals("Hello World", entity.getBody());
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableWebMvc
|
||||
static class Config {
|
||||
|
||||
private int clientPort = SocketUtils.findAvailableTcpPort();
|
||||
|
||||
private int httpServerPort = SocketUtils.findAvailableTcpPort();
|
||||
|
||||
@Bean
|
||||
public EmbeddedServletContainerFactory container() {
|
||||
return new TomcatEmbeddedServletContainerFactory(this.httpServerPort);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DispatcherFilter filter() {
|
||||
PortProvider port = new StaticPortProvider(this.httpServerPort);
|
||||
TargetServerConnection connection = new SocketTargetServerConnection(port);
|
||||
HttpTunnelServer server = new HttpTunnelServer(connection);
|
||||
HandlerMapper mapper = new UrlHandlerMapper("/httptunnel",
|
||||
new HttpTunnelServerHandler(server));
|
||||
Collection<HandlerMapper> mappers = Collections.singleton(mapper);
|
||||
Dispatcher dispatcher = new Dispatcher(AccessManager.PERMIT_ALL, mappers);
|
||||
return new DispatcherFilter(dispatcher);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public TunnelClient tunnelClient() {
|
||||
String url = "http://localhost:" + this.httpServerPort + "/httptunnel";
|
||||
TunnelConnection connection = new HttpTunnelConnection(url,
|
||||
new SimpleClientHttpRequestFactory());
|
||||
return new TunnelClient(this.clientPort, connection);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DispatcherServlet dispatcherServlet() {
|
||||
return new DispatcherServlet();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public MyController myController() {
|
||||
return new MyController();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@RestController
|
||||
static class MyController {
|
||||
|
||||
@RequestMapping("/hello")
|
||||
public String hello() {
|
||||
return "Hello World";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* 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.net.ServerSocket;
|
||||
|
||||
import javax.net.ServerSocketFactory;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionOutcome;
|
||||
import org.springframework.boot.test.EnvironmentTestUtils;
|
||||
import org.springframework.context.annotation.ConditionContext;
|
||||
import org.springframework.mock.env.MockEnvironment;
|
||||
import org.springframework.util.SocketUtils;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.junit.Assert.assertThat;
|
||||
import static org.mockito.BDDMockito.given;
|
||||
import static org.mockito.Mockito.mock;
|
||||
|
||||
/**
|
||||
* Tests for {@link LocalDebugPortAvailableCondition}.
|
||||
*
|
||||
* @author Phillip Webb
|
||||
*/
|
||||
public class LocalDebugPortAvailableConditionTests {
|
||||
|
||||
private int port = SocketUtils.findAvailableTcpPort();
|
||||
|
||||
private LocalDebugPortAvailableCondition condition = new LocalDebugPortAvailableCondition();
|
||||
|
||||
@Test
|
||||
public void portAvailable() throws Exception {
|
||||
ConditionOutcome outcome = getOutcome();
|
||||
assertThat(outcome.isMatch(), equalTo(true));
|
||||
assertThat(outcome.getMessage(), equalTo("Local debug port availble"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void portInUse() throws Exception {
|
||||
final ServerSocket serverSocket = ServerSocketFactory.getDefault()
|
||||
.createServerSocket(this.port);
|
||||
ConditionOutcome outcome = getOutcome();
|
||||
serverSocket.close();
|
||||
assertThat(outcome.isMatch(), equalTo(false));
|
||||
assertThat(outcome.getMessage(), equalTo("Local debug port unavailble"));
|
||||
}
|
||||
|
||||
private ConditionOutcome getOutcome() {
|
||||
MockEnvironment environment = new MockEnvironment();
|
||||
EnvironmentTestUtils.addEnvironment(environment,
|
||||
"spring.developertools.remote.debug.local-port:" + this.port);
|
||||
ConditionContext context = mock(ConditionContext.class);
|
||||
given(context.getEnvironment()).willReturn(environment);
|
||||
ConditionOutcome outcome = this.condition.getMatchOutcome(context, null);
|
||||
return outcome;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -38,6 +38,7 @@ import org.springframework.boot.developertools.remote.server.Dispatcher;
|
|||
import org.springframework.boot.developertools.remote.server.DispatcherFilter;
|
||||
import org.springframework.boot.developertools.restart.MockRestarter;
|
||||
import org.springframework.boot.developertools.restart.RestartScopeInitializer;
|
||||
import org.springframework.boot.developertools.tunnel.client.TunnelClient;
|
||||
import org.springframework.boot.test.EnvironmentTestUtils;
|
||||
import org.springframework.boot.test.OutputCapture;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
|
|
@ -82,10 +83,11 @@ public class RemoteClientConfigurationTests {
|
|||
}
|
||||
|
||||
@Test
|
||||
public void warnIfRestartDisabled() throws Exception {
|
||||
configure("spring.developertools.remote.restart.enabled:false");
|
||||
public void warnIfDebugAndRestartDisabled() throws Exception {
|
||||
configure("spring.developertools.remote.debug.enabled:false",
|
||||
"spring.developertools.remote.restart.enabled:false");
|
||||
assertThat(this.output.toString(),
|
||||
containsString("Remote restart is not enabled"));
|
||||
containsString("Remote restart and debug are both disabled"));
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -122,6 +124,13 @@ public class RemoteClientConfigurationTests {
|
|||
this.context.getBean(ClassPathFileSystemWatcher.class);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void remoteDebugDisabled() throws Exception {
|
||||
configure("spring.developertools.remote.debug.enabled:false");
|
||||
this.thrown.expect(NoSuchBeanDefinitionException.class);
|
||||
this.context.getBean(TunnelClient.class);
|
||||
}
|
||||
|
||||
private void configure(String... pairs) {
|
||||
configureWithRemoteUrl("http://localhost", pairs);
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue