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}