diff --git a/spring-boot-autoconfigure/pom.xml b/spring-boot-autoconfigure/pom.xml index 36e9e72a6c..f4e3fc4631 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 0000000000..6887a8f8db --- /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 311bbdf231..1c157bdde3 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-dependencies/pom.xml b/spring-boot-dependencies/pom.xml index af55b82e01..ee7a9e4ee0 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 diff --git a/spring-boot-samples/spring-boot-sample-websocket/pom.xml b/spring-boot-samples/spring-boot-sample-websocket/pom.xml index b897c0e878..eac5cc7fe4 100644 --- a/spring-boot-samples/spring-boot-sample-websocket/pom.xml +++ b/spring-boot-samples/spring-boot-sample-websocket/pom.xml @@ -15,8 +15,7 @@ 1.7 8.0.0-RC1 - 4.0.0.BUILD-SNAPSHOT - org.springframework.boot.samples.websocket.config.ApplicationConfiguration + org.springframework.boot.samples.websocket.config.SampleWebSocketsApplication @@ -29,13 +28,6 @@ org.springframework.boot spring-boot-starter-actuator - - - org.eclipse.jetty.websocket - websocket-client - 9.0.3.v20130506 - test - @@ -47,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 6269a7e005..2adde70e43 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,16 +16,8 @@ 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; @@ -35,19 +27,13 @@ import org.springframework.boot.samples.websocket.snake.SnakeWebSocketHandler; 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.SockJsHttpRequestHandler; -import org.springframework.web.socket.sockjs.SockJsService; -import org.springframework.web.socket.sockjs.transport.handler.DefaultSockJsService; import org.springframework.web.socket.support.PerConnectionWebSocketHandler; @Configuration +@EnableAutoConfiguration public class SampleWebSocketsApplication extends SpringBootServletInitializer { - + @Override protected Class[] getConfigClasses() { return new Class[] { SampleWebSocketsApplication.class }; @@ -57,22 +43,6 @@ public class SampleWebSocketsApplication extends SpringBootServletInitializer { 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 SpringBootServletInitializer { 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 cbaef719dd..42081d4e19 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; } +