001/* 002 * Copyright 2012-2016 the original author or authors. 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016 017package org.springframework.boot.devtools.autoconfigure; 018 019import java.util.Collection; 020 021import javax.servlet.Filter; 022 023import org.apache.commons.logging.Log; 024import org.apache.commons.logging.LogFactory; 025 026import org.springframework.beans.factory.annotation.Autowired; 027import org.springframework.beans.factory.annotation.Qualifier; 028import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 029import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; 030import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; 031import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; 032import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 033import org.springframework.boot.autoconfigure.security.SecurityProperties; 034import org.springframework.boot.autoconfigure.web.ServerProperties; 035import org.springframework.boot.context.properties.EnableConfigurationProperties; 036import org.springframework.boot.devtools.remote.server.AccessManager; 037import org.springframework.boot.devtools.remote.server.Dispatcher; 038import org.springframework.boot.devtools.remote.server.DispatcherFilter; 039import org.springframework.boot.devtools.remote.server.Handler; 040import org.springframework.boot.devtools.remote.server.HandlerMapper; 041import org.springframework.boot.devtools.remote.server.HttpHeaderAccessManager; 042import org.springframework.boot.devtools.remote.server.HttpStatusHandler; 043import org.springframework.boot.devtools.remote.server.UrlHandlerMapper; 044import org.springframework.boot.devtools.restart.server.DefaultSourceFolderUrlFilter; 045import org.springframework.boot.devtools.restart.server.HttpRestartServer; 046import org.springframework.boot.devtools.restart.server.HttpRestartServerHandler; 047import org.springframework.boot.devtools.restart.server.SourceFolderUrlFilter; 048import org.springframework.boot.devtools.tunnel.server.HttpTunnelServer; 049import org.springframework.boot.devtools.tunnel.server.HttpTunnelServerHandler; 050import org.springframework.boot.devtools.tunnel.server.RemoteDebugPortProvider; 051import org.springframework.boot.devtools.tunnel.server.SocketTargetServerConnection; 052import org.springframework.context.annotation.Bean; 053import org.springframework.context.annotation.Configuration; 054import org.springframework.core.annotation.Order; 055import org.springframework.http.server.ServerHttpRequest; 056import org.springframework.security.config.annotation.ObjectPostProcessor; 057import org.springframework.security.config.annotation.web.builders.HttpSecurity; 058import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter; 059 060/** 061 * {@link EnableAutoConfiguration Auto-configuration} for remote development support. 062 * 063 * @author Phillip Webb 064 * @author Rob Winch 065 * @author Andy Wilkinson 066 * @since 1.3.0 067 */ 068@Configuration 069@ConditionalOnProperty(prefix = "spring.devtools.remote", name = "secret") 070@ConditionalOnClass({ Filter.class, ServerHttpRequest.class }) 071@EnableConfigurationProperties(DevToolsProperties.class) 072public class RemoteDevToolsAutoConfiguration { 073 074 private static final Log logger = LogFactory 075 .getLog(RemoteDevToolsAutoConfiguration.class); 076 077 private final DevToolsProperties properties; 078 079 private final ServerProperties serverProperties; 080 081 public RemoteDevToolsAutoConfiguration(DevToolsProperties properties, 082 ServerProperties serverProperties) { 083 this.properties = properties; 084 this.serverProperties = serverProperties; 085 } 086 087 @Bean 088 @ConditionalOnMissingBean 089 public AccessManager remoteDevToolsAccessManager() { 090 RemoteDevToolsProperties remoteProperties = this.properties.getRemote(); 091 return new HttpHeaderAccessManager(remoteProperties.getSecretHeaderName(), 092 remoteProperties.getSecret()); 093 } 094 095 @Bean 096 public HandlerMapper remoteDevToolsHealthCheckHandlerMapper() { 097 Handler handler = new HttpStatusHandler(); 098 return new UrlHandlerMapper((this.serverProperties.getContextPath() == null ? "" 099 : this.serverProperties.getContextPath()) 100 + this.properties.getRemote().getContextPath(), handler); 101 } 102 103 @Bean 104 @ConditionalOnMissingBean 105 public DispatcherFilter remoteDevToolsDispatcherFilter(AccessManager accessManager, 106 Collection<HandlerMapper> mappers) { 107 Dispatcher dispatcher = new Dispatcher(accessManager, mappers); 108 return new DispatcherFilter(dispatcher); 109 } 110 111 /** 112 * Configuration for remote update and restarts. 113 */ 114 @ConditionalOnProperty(prefix = "spring.devtools.remote.restart", name = "enabled", matchIfMissing = true) 115 static class RemoteRestartConfiguration { 116 117 @Autowired 118 private DevToolsProperties properties; 119 120 @Autowired 121 private ServerProperties serverProperties; 122 123 @Bean 124 @ConditionalOnMissingBean 125 public SourceFolderUrlFilter remoteRestartSourceFolderUrlFilter() { 126 return new DefaultSourceFolderUrlFilter(); 127 } 128 129 @Bean 130 @ConditionalOnMissingBean 131 public HttpRestartServer remoteRestartHttpRestartServer( 132 SourceFolderUrlFilter sourceFolderUrlFilter) { 133 return new HttpRestartServer(sourceFolderUrlFilter); 134 } 135 136 @Bean 137 @ConditionalOnMissingBean(name = "remoteRestartHandlerMapper") 138 public UrlHandlerMapper remoteRestartHandlerMapper(HttpRestartServer server) { 139 String url = (this.serverProperties.getContextPath() == null ? "" 140 : this.serverProperties.getContextPath()) 141 + this.properties.getRemote().getContextPath() + "/restart"; 142 logger.warn("Listening for remote restart updates on " + url); 143 Handler handler = new HttpRestartServerHandler(server); 144 return new UrlHandlerMapper(url, handler); 145 } 146 147 } 148 149 /** 150 * Configuration for remote debug HTTP tunneling. 151 */ 152 @ConditionalOnProperty(prefix = "spring.devtools.remote.debug", name = "enabled", matchIfMissing = true) 153 static class RemoteDebugTunnelConfiguration { 154 155 @Autowired 156 private DevToolsProperties properties; 157 158 @Autowired 159 private ServerProperties serverProperties; 160 161 @Bean 162 @ConditionalOnMissingBean(name = "remoteDebugHandlerMapper") 163 public UrlHandlerMapper remoteDebugHandlerMapper( 164 @Qualifier("remoteDebugHttpTunnelServer") HttpTunnelServer server) { 165 String url = (this.serverProperties.getContextPath() == null ? "" 166 : this.serverProperties.getContextPath()) 167 + this.properties.getRemote().getContextPath() + "/debug"; 168 logger.warn("Listening for remote debug traffic on " + url); 169 Handler handler = new HttpTunnelServerHandler(server); 170 return new UrlHandlerMapper(url, handler); 171 } 172 173 @Bean 174 @ConditionalOnMissingBean(name = "remoteDebugHttpTunnelServer") 175 public HttpTunnelServer remoteDebugHttpTunnelServer() { 176 return new HttpTunnelServer( 177 new SocketTargetServerConnection(new RemoteDebugPortProvider())); 178 } 179 180 } 181 182 @Configuration 183 @ConditionalOnClass(WebSecurityConfigurerAdapter.class) 184 @ConditionalOnBean(ObjectPostProcessor.class) 185 static class RemoteDevToolsSecurityConfiguration { 186 187 @Bean 188 public RemoteRestartWebSecurityConfigurer remoteRestartWebSecurityConfigurer() { 189 return new RemoteRestartWebSecurityConfigurer(); 190 } 191 192 @Order(SecurityProperties.IGNORED_ORDER + 2) 193 static class RemoteRestartWebSecurityConfigurer 194 extends WebSecurityConfigurerAdapter { 195 196 @Autowired 197 private DevToolsProperties properties; 198 199 @Override 200 public void configure(HttpSecurity http) throws Exception { 201 http.antMatcher(this.properties.getRemote().getContextPath() + "/**"); 202 http.csrf().disable(); 203 } 204 205 } 206 207 } 208 209}