diff --git a/spring-boot-autoconfigure/pom.xml b/spring-boot-autoconfigure/pom.xml index 36e9e72a6cf..f4e3fc4631c 100644 --- a/spring-boot-autoconfigure/pom.xml +++ b/spring-boot-autoconfigure/pom.xml @@ -71,6 +71,11 @@ spring-web true + + org.springframework + spring-websocket + true + org.springframework spring-webmvc diff --git a/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/websocket/WebSocketAutoConfiguration.java b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/websocket/WebSocketAutoConfiguration.java new file mode 100644 index 00000000000..6887a8f8db6 --- /dev/null +++ b/spring-boot-autoconfigure/src/main/java/org/springframework/boot/autoconfigure/websocket/WebSocketAutoConfiguration.java @@ -0,0 +1,151 @@ +/* + * Copyright 2012-2013 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.autoconfigure.websocket; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; + +import javax.servlet.ServletContainerInitializer; + +import org.apache.catalina.Context; +import org.springframework.beans.BeanUtils; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.BeanPostProcessor; +import org.springframework.boot.autoconfigure.AutoConfigureBefore; +import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; +import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; +import org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration; +import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; +import org.springframework.util.ClassUtils; +import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping; +import org.springframework.web.socket.WebSocketHandler; +import org.springframework.web.socket.sockjs.SockJsHttpRequestHandler; +import org.springframework.web.socket.sockjs.SockJsService; +import org.springframework.web.socket.sockjs.support.AbstractSockJsService; +import org.springframework.web.socket.sockjs.transport.handler.DefaultSockJsService; + +/** + * Auto configuration for websockets (and sockjs in particular). Users should be able to + * just define beans of type {@link WebSocketHandler}. If spring-websocket is + * detected on the classpath then we add a {@link DefaultSockJsService} and an MVC handler + * mapping to /<beanName>/** for all of the + * WebSocketHandler beans that have a bean name beginning with "/". + * + * @author Dave Syer + */ +@Configuration +@ConditionalOnClass({ WebSocketHandler.class }) +@AutoConfigureBefore(EmbeddedServletContainerAutoConfiguration.class) +public class WebSocketAutoConfiguration { + + private static class WebSocketEndpointPostProcessor implements BeanPostProcessor { + + private Map prefixes = new HashMap(); + + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) + throws BeansException { + return bean; + } + + @Override + public Object postProcessAfterInitialization(Object bean, String beanName) + throws BeansException { + if (bean instanceof WebSocketHandler && beanName.startsWith("/")) { + this.prefixes.put(beanName, (WebSocketHandler) bean); + } + return bean; + } + + public WebSocketHandler getHandler(String prefix) { + return this.prefixes.get(prefix); + } + + public String[] getPrefixes() { + return this.prefixes.keySet().toArray(new String[this.prefixes.size()]); + } + + } + + @Bean + public WebSocketEndpointPostProcessor webSocketEndpointPostProcessor() { + return new WebSocketEndpointPostProcessor(); + } + + @Bean + @ConditionalOnMissingBean(SockJsService.class) + public DefaultSockJsService sockJsService() { + DefaultSockJsService service = new DefaultSockJsService(sockJsTaskScheduler()); + service.setSockJsClientLibraryUrl("https://cdn.sockjs.org/sockjs-0.3.4.min.js"); + service.setWebSocketsEnabled(true); + return service; + } + + @Bean + public SimpleUrlHandlerMapping handlerMapping(SockJsService sockJsService, + Collection handlers) { + + WebSocketEndpointPostProcessor processor = webSocketEndpointPostProcessor(); + Map urlMap = new HashMap(); + for (String prefix : webSocketEndpointPostProcessor().getPrefixes()) { + urlMap.put(prefix + "/**", new SockJsHttpRequestHandler(sockJsService, + processor.getHandler(prefix))); + } + + if (sockJsService instanceof AbstractSockJsService) { + ((AbstractSockJsService) sockJsService).setValidSockJsPrefixes(processor + .getPrefixes()); + } + SimpleUrlHandlerMapping handlerMapping = new SimpleUrlHandlerMapping(); + handlerMapping.setOrder(-1); + handlerMapping.setUrlMap(urlMap); + + return handlerMapping; + } + + @Bean + @ConditionalOnMissingBean(name = "sockJsTaskScheduler") + public ThreadPoolTaskScheduler sockJsTaskScheduler() { + ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler(); + taskScheduler.setThreadNamePrefix("SockJS-"); + return taskScheduler; + } + + @Configuration + @ConditionalOnClass(name = "org.apache.tomcat.websocket.server.WsSci") + protected static class TomcatWebSocketConfiguration { + @Bean + public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() { + TomcatEmbeddedServletContainerFactory factory = new TomcatEmbeddedServletContainerFactory() { + @Override + protected void postProcessContext(Context context) { + context.addServletContainerInitializer( + (ServletContainerInitializer) BeanUtils + .instantiate(ClassUtils.resolveClassName( + "org.apache.tomcat.websocket.server.WsSci", + null)), null); + } + }; + return factory; + } + } + +} diff --git a/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories b/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories index 311bbdf2311..1c157bdde39 100644 --- a/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories +++ b/spring-boot-autoconfigure/src/main/resources/META-INF/spring.factories @@ -12,4 +12,5 @@ org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\ org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration,\ org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration,\ org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration,\ -org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration +org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration,\ +org.springframework.boot.autoconfigure.websocket.WebSocketAutoConfiguration diff --git a/spring-boot-cli/src/test/java/org/springframework/boot/cli/SampleIntegrationTests.java b/spring-boot-cli/src/test/java/org/springframework/boot/cli/SampleIntegrationTests.java index 13cba880ada..17dd6fad976 100644 --- a/spring-boot-cli/src/test/java/org/springframework/boot/cli/SampleIntegrationTests.java +++ b/spring-boot-cli/src/test/java/org/springframework/boot/cli/SampleIntegrationTests.java @@ -29,6 +29,7 @@ import org.junit.BeforeClass; import org.junit.Rule; import org.junit.Test; import org.springframework.boot.OutputCapture; +import org.springframework.boot.cli.command.CleanCommand; import org.springframework.boot.cli.command.RunCommand; import static org.junit.Assert.assertEquals; @@ -66,8 +67,9 @@ public class SampleIntegrationTests { } @Before - public void setup() { + public void setup() throws Exception { System.setProperty("disableSpringSnapshotRepos", "true"); + new CleanCommand().run("org.springframework"); } @After diff --git a/spring-boot-dependencies/pom.xml b/spring-boot-dependencies/pom.xml index 8aa48721708..ee7a9e4ee06 100644 --- a/spring-boot-dependencies/pom.xml +++ b/spring-boot-dependencies/pom.xml @@ -7,7 +7,7 @@ 0.5.0.BUILD-SNAPSHOT pom - 4.0.0.M2 + 4.0.0.BUILD-SNAPSHOT 3.2.0.M2 2.2.4.RELEASE 2.2.0.RELEASE @@ -273,6 +273,11 @@ spring-tx ${spring.version} + + org.springframework + spring-websocket + ${spring.version} + org.springframework spring-web diff --git a/spring-boot-integration-tests/pom.xml b/spring-boot-integration-tests/pom.xml index 926a00f73f8..0889a50fb8a 100644 --- a/spring-boot-integration-tests/pom.xml +++ b/spring-boot-integration-tests/pom.xml @@ -119,6 +119,10 @@ src/it/settings.xml ${main.basedir}/spring-boot-samples/ + + + spring-boot-sample-websocket/pom.xml + ${project.build.directory}/local-repo ${skipTests} diff --git a/spring-boot-samples/pom.xml b/spring-boot-samples/pom.xml index b0d670ebc24..2615b09a9a4 100644 --- a/spring-boot-samples/pom.xml +++ b/spring-boot-samples/pom.xml @@ -26,6 +26,7 @@ spring-boot-sample-traditional spring-boot-sample-web-static spring-boot-sample-web-ui + spring-boot-sample-websocket spring-boot-sample-xml diff --git a/spring-boot-samples/spring-boot-sample-websocket/_om.xml b/spring-boot-samples/spring-boot-sample-websocket/pom.xml similarity index 64% rename from spring-boot-samples/spring-boot-sample-websocket/_om.xml rename to spring-boot-samples/spring-boot-sample-websocket/pom.xml index 8651ffbc395..eac5cc7fe4a 100644 --- a/spring-boot-samples/spring-boot-sample-websocket/_om.xml +++ b/spring-boot-samples/spring-boot-sample-websocket/pom.xml @@ -14,8 +14,8 @@ 1.7 - 8.0-SNAPSHOT - org.springframework.boot.samples.websocket.config.ApplicationConfiguration + 8.0.0-RC1 + org.springframework.boot.samples.websocket.config.SampleWebSocketsApplication @@ -23,14 +23,10 @@ org.springframework.boot spring-boot-starter-websocket - ${spring.boot.version} - - org.eclipse.jetty.websocket - websocket-client - 9.0.3.v20130506 - test + org.springframework.boot + spring-boot-starter-actuator @@ -43,17 +39,4 @@ - - - tomcat-snapshots - https://repository.apache.org/content/repositories/snapshots - - true - - - false - - - - diff --git a/spring-boot-samples/spring-boot-sample-websocket/src/main/java/org/springframework/boot/samples/websocket/config/SampleWebSocketsApplication.java b/spring-boot-samples/spring-boot-sample-websocket/src/main/java/org/springframework/boot/samples/websocket/config/SampleWebSocketsApplication.java index f8f2aad3a56..2adde70e439 100644 --- a/spring-boot-samples/spring-boot-sample-websocket/src/main/java/org/springframework/boot/samples/websocket/config/SampleWebSocketsApplication.java +++ b/spring-boot-samples/spring-boot-sample-websocket/src/main/java/org/springframework/boot/samples/websocket/config/SampleWebSocketsApplication.java @@ -16,38 +16,24 @@ package org.springframework.boot.samples.websocket.config; -import java.util.HashMap; -import java.util.Map; - -import org.apache.catalina.Context; -import org.apache.catalina.startup.Tomcat; -import org.apache.tomcat.websocket.server.WsSci; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory; import org.springframework.boot.samples.websocket.client.GreetingService; import org.springframework.boot.samples.websocket.client.SimpleGreetingService; import org.springframework.boot.samples.websocket.echo.DefaultEchoService; import org.springframework.boot.samples.websocket.echo.EchoService; import org.springframework.boot.samples.websocket.echo.EchoWebSocketHandler; import org.springframework.boot.samples.websocket.snake.SnakeWebSocketHandler; -import org.springframework.boot.web.SpringServletInitializer; +import org.springframework.boot.web.SpringBootServletInitializer; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; -import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; -import org.springframework.web.servlet.DispatcherServlet; -import org.springframework.web.servlet.handler.SimpleUrlHandlerMapping; import org.springframework.web.socket.WebSocketHandler; -import org.springframework.web.socket.server.support.WebSocketHttpRequestHandler; -import org.springframework.web.socket.sockjs.SockJsService; -import org.springframework.web.socket.sockjs.support.DefaultSockJsService; -import org.springframework.web.socket.sockjs.support.SockJsHttpRequestHandler; import org.springframework.web.socket.support.PerConnectionWebSocketHandler; @Configuration -public class SampleWebSocketsApplication extends SpringServletInitializer { - +@EnableAutoConfiguration +public class SampleWebSocketsApplication extends SpringBootServletInitializer { + @Override protected Class[] getConfigClasses() { return new Class[] { SampleWebSocketsApplication.class }; @@ -57,22 +43,6 @@ public class SampleWebSocketsApplication extends SpringServletInitializer { SpringApplication.run(SampleWebSocketsApplication.class, args); } - @ConditionalOnClass(Tomcat.class) - @Configuration - @EnableAutoConfiguration - protected static class InitializationConfiguration { - @Bean - public TomcatEmbeddedServletContainerFactory tomcatEmbeddedServletContainerFactory() { - TomcatEmbeddedServletContainerFactory factory = new TomcatEmbeddedServletContainerFactory() { - @Override - protected void postProcessContext(Context context) { - context.addServletContainerInitializer(new WsSci(), null); - } - }; - return factory; - } - } - @Bean public EchoService echoService() { return new DefaultEchoService("Did you say \"%s\"?"); @@ -83,47 +53,14 @@ public class SampleWebSocketsApplication extends SpringServletInitializer { return new SimpleGreetingService(); } - @Bean - public SimpleUrlHandlerMapping handlerMapping() { - - SockJsService sockJsService = new DefaultSockJsService(sockJsTaskScheduler()); - - Map urlMap = new HashMap(); - - urlMap.put("/echo", new WebSocketHttpRequestHandler(echoWebSocketHandler())); - urlMap.put("/snake", new WebSocketHttpRequestHandler(snakeWebSocketHandler())); - - urlMap.put("/sockjs/echo/**", new SockJsHttpRequestHandler(sockJsService, echoWebSocketHandler())); - urlMap.put("/sockjs/snake/**", new SockJsHttpRequestHandler(sockJsService, snakeWebSocketHandler())); - - SimpleUrlHandlerMapping handlerMapping = new SimpleUrlHandlerMapping(); - handlerMapping.setOrder(-1); - handlerMapping.setUrlMap(urlMap); - - return handlerMapping; - } - - @Bean - public DispatcherServlet dispatcherServlet() { - DispatcherServlet servlet = new DispatcherServlet(); - servlet.setDispatchOptionsRequest(true); - return servlet; - } - - @Bean + @Bean(name = "/echo") public WebSocketHandler echoWebSocketHandler() { return new PerConnectionWebSocketHandler(EchoWebSocketHandler.class); } - @Bean + @Bean(name = "/snake") public WebSocketHandler snakeWebSocketHandler() { return new SnakeWebSocketHandler(); } - @Bean - public ThreadPoolTaskScheduler sockJsTaskScheduler() { - ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler(); - taskScheduler.setThreadNamePrefix("SockJS-"); - return taskScheduler; - } } diff --git a/spring-boot-samples/spring-boot-sample-websocket/src/main/resources/static/echo.html b/spring-boot-samples/spring-boot-sample-websocket/src/main/resources/static/echo.html index cbaef719dd3..42081d4e196 100644 --- a/spring-boot-samples/spring-boot-sample-websocket/src/main/resources/static/echo.html +++ b/spring-boot-samples/spring-boot-sample-websocket/src/main/resources/static/echo.html @@ -49,6 +49,7 @@ margin: 0; } +