Add WebSocketAutoConfiguration

Opinionated defaults for WebSockets:

* If spring-websocket is on the classpath and so is
the Tomcat WSci initializer then it is added to the context
* A DefaultSockJsService is added if none is present
* User has only to define @Beans of type WebSocketHandler with
name starting "/"
* Each one is converted to a SockJsHttpRequestHandler and
mapped to "/<beanName>/**"
pull/50/head
Dave Syer 11 years ago committed by Phillip Webb
parent 6e8cbbde3b
commit 767aa43e31

@ -71,6 +71,11 @@
<artifactId>spring-web</artifactId> <artifactId>spring-web</artifactId>
<optional>true</optional> <optional>true</optional>
</dependency> </dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
<optional>true</optional>
</dependency>
<dependency> <dependency>
<groupId>org.springframework</groupId> <groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId> <artifactId>spring-webmvc</artifactId>

@ -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 <code>spring-websocket</code> is
* detected on the classpath then we add a {@link DefaultSockJsService} and an MVC handler
* mapping to <code>/&lt;beanName&gt;/**</code> for all of the
* <code>WebSocketHandler</code> 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<String, WebSocketHandler> prefixes = new HashMap<String, WebSocketHandler>();
@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<WebSocketHandler> handlers) {
WebSocketEndpointPostProcessor processor = webSocketEndpointPostProcessor();
Map<String, Object> urlMap = new HashMap<String, Object>();
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;
}
}
}

@ -12,4 +12,5 @@ org.springframework.boot.autoconfigure.thymeleaf.ThymeleafAutoConfiguration,\
org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration,\ org.springframework.boot.autoconfigure.web.EmbeddedServletContainerAutoConfiguration,\
org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration,\ org.springframework.boot.autoconfigure.web.ServerPropertiesAutoConfiguration,\
org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration,\ org.springframework.boot.autoconfigure.web.MultipartAutoConfiguration,\
org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration,\
org.springframework.boot.autoconfigure.websocket.WebSocketAutoConfiguration

@ -7,7 +7,7 @@
<version>0.5.0.BUILD-SNAPSHOT</version> <version>0.5.0.BUILD-SNAPSHOT</version>
<packaging>pom</packaging> <packaging>pom</packaging>
<properties> <properties>
<spring.version>4.0.0.M2</spring.version> <spring.version>4.0.0.BUILD-SNAPSHOT</spring.version>
<spring.security.version>3.2.0.M2</spring.security.version> <spring.security.version>3.2.0.M2</spring.security.version>
<spring.integration.version>2.2.4.RELEASE</spring.integration.version> <spring.integration.version>2.2.4.RELEASE</spring.integration.version>
<spring.batch.version>2.2.0.RELEASE</spring.batch.version> <spring.batch.version>2.2.0.RELEASE</spring.batch.version>

@ -15,8 +15,7 @@
<properties> <properties>
<java.version>1.7</java.version> <java.version>1.7</java.version>
<tomcat.version>8.0.0-RC1</tomcat.version> <tomcat.version>8.0.0-RC1</tomcat.version>
<spring.version>4.0.0.BUILD-SNAPSHOT</spring.version> <start-class>org.springframework.boot.samples.websocket.config.SampleWebSocketsApplication</start-class>
<start-class>org.springframework.boot.samples.websocket.config.ApplicationConfiguration</start-class>
</properties> </properties>
@ -29,13 +28,6 @@
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId> <artifactId>spring-boot-starter-actuator</artifactId>
</dependency> </dependency>
<!-- For SockJS -->
<dependency>
<groupId>org.eclipse.jetty.websocket</groupId>
<artifactId>websocket-client</artifactId>
<version>9.0.3.v20130506</version>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
<build> <build>
@ -47,17 +39,4 @@
</plugins> </plugins>
</build> </build>
<repositories>
<repository>
<id>tomcat-snapshots</id>
<url>https://repository.apache.org/content/repositories/snapshots</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
<releases>
<enabled>false</enabled>
</releases>
</repository>
</repositories>
</project> </project>

@ -16,16 +16,8 @@
package org.springframework.boot.samples.websocket.config; 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.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; 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.GreetingService;
import org.springframework.boot.samples.websocket.client.SimpleGreetingService; import org.springframework.boot.samples.websocket.client.SimpleGreetingService;
import org.springframework.boot.samples.websocket.echo.DefaultEchoService; import org.springframework.boot.samples.websocket.echo.DefaultEchoService;
@ -35,17 +27,11 @@ import org.springframework.boot.samples.websocket.snake.SnakeWebSocketHandler;
import org.springframework.boot.web.SpringBootServletInitializer; import org.springframework.boot.web.SpringBootServletInitializer;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; 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.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; import org.springframework.web.socket.support.PerConnectionWebSocketHandler;
@Configuration @Configuration
@EnableAutoConfiguration
public class SampleWebSocketsApplication extends SpringBootServletInitializer { public class SampleWebSocketsApplication extends SpringBootServletInitializer {
@Override @Override
@ -57,22 +43,6 @@ public class SampleWebSocketsApplication extends SpringBootServletInitializer {
SpringApplication.run(SampleWebSocketsApplication.class, args); 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 @Bean
public EchoService echoService() { public EchoService echoService() {
return new DefaultEchoService("Did you say \"%s\"?"); return new DefaultEchoService("Did you say \"%s\"?");
@ -83,47 +53,14 @@ public class SampleWebSocketsApplication extends SpringBootServletInitializer {
return new SimpleGreetingService(); return new SimpleGreetingService();
} }
@Bean @Bean(name = "/echo")
public SimpleUrlHandlerMapping handlerMapping() {
SockJsService sockJsService = new DefaultSockJsService(sockJsTaskScheduler());
Map<String, Object> urlMap = new HashMap<String, Object>();
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
public WebSocketHandler echoWebSocketHandler() { public WebSocketHandler echoWebSocketHandler() {
return new PerConnectionWebSocketHandler(EchoWebSocketHandler.class); return new PerConnectionWebSocketHandler(EchoWebSocketHandler.class);
} }
@Bean @Bean(name = "/snake")
public WebSocketHandler snakeWebSocketHandler() { public WebSocketHandler snakeWebSocketHandler() {
return new SnakeWebSocketHandler(); return new SnakeWebSocketHandler();
} }
@Bean
public ThreadPoolTaskScheduler sockJsTaskScheduler() {
ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
taskScheduler.setThreadNamePrefix("SockJS-");
return taskScheduler;
}
} }

@ -49,6 +49,7 @@
margin: 0; margin: 0;
} }
</style> </style>
<script src="http://cdn.sockjs.org/sockjs-0.3.min.js"></script>
<script type="text/javascript"> <script type="text/javascript">
var ws = null; var ws = null;
@ -60,15 +61,7 @@
function connect() { function connect() {
var target = document.getElementById('target').value; var target = document.getElementById('target').value;
target = "ws://" + window.location.host + target ws = new SockJS(target);
if ('WebSocket' in window) {
ws = new WebSocket(target);
} else if ('MozWebSocket' in window) {
ws = new MozWebSocket(target);
} else {
alert('WebSocket is not supported by this browser.');
return;
}
ws.onopen = function () { ws.onopen = function () {
setConnected(true); setConnected(true);
log('Info: WebSocket connection opened.'); log('Info: WebSocket connection opened.');

@ -49,6 +49,7 @@
margin: 0; margin: 0;
} }
</style> </style>
<script src="http://cdn.sockjs.org/sockjs-0.3.min.js"></script>
</head> </head>
<body> <body>
<noscript><h2 style="color: #ff0000">Seems your browser doesn't support Javascript! Websockets rely on Javascript being enabled. Please enable <noscript><h2 style="color: #ff0000">Seems your browser doesn't support Javascript! Websockets rely on Javascript being enabled. Please enable
@ -110,11 +111,7 @@
} }
} }
}, false); }, false);
if (window.location.protocol == 'http:') { Game.connect();
Game.connect('ws://' + window.location.host + '/snake');
} else {
Game.connect('wss://' + window.location.host + '/snake');
}
}; };
Game.setDirection = function(direction) { Game.setDirection = function(direction) {
@ -185,15 +182,8 @@
}; };
})(); })();
Game.connect = (function(host) { Game.connect = (function() {
if ('WebSocket' in window) { Game.socket = new SockJS("/snake");
Game.socket = new WebSocket(host);
} else if ('MozWebSocket' in window) {
Game.socket = new MozWebSocket(host);
} else {
Console.log('Error: WebSocket is not supported by this browser.');
return;
}
Game.socket.onopen = function () { Game.socket.onopen = function () {
// Socket open.. start the game loop. // Socket open.. start the game loop.

@ -34,7 +34,6 @@ import org.springframework.boot.samples.websocket.client.GreetingService;
import org.springframework.boot.samples.websocket.client.SimpleClientWebSocketHandler; import org.springframework.boot.samples.websocket.client.SimpleClientWebSocketHandler;
import org.springframework.boot.samples.websocket.client.SimpleGreetingService; import org.springframework.boot.samples.websocket.client.SimpleGreetingService;
import org.springframework.boot.samples.websocket.config.SampleWebSocketsApplication; import org.springframework.boot.samples.websocket.config.SampleWebSocketsApplication;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
@ -45,7 +44,7 @@ public class SampleWebSocketsApplicationTests {
private static Log logger = LogFactory.getLog(SampleWebSocketsApplicationTests.class); private static Log logger = LogFactory.getLog(SampleWebSocketsApplicationTests.class);
private static final String WS_URI = "ws://localhost:8080/echo"; private static final String WS_URI = "ws://localhost:8080/echo/websocket";
private static ConfigurableApplicationContext context; private static ConfigurableApplicationContext context;
@ -72,8 +71,10 @@ public class SampleWebSocketsApplicationTests {
@Test @Test
public void runAndWait() throws Exception { public void runAndWait() throws Exception {
ApplicationContext context = SpringApplication.run(ClientConfiguration.class, "--spring.main.web_environment=false"); ConfigurableApplicationContext context = (ConfigurableApplicationContext) SpringApplication.run(ClientConfiguration.class, "--spring.main.web_environment=false");
assertEquals(0, context.getBean(ClientConfiguration.class).latch.getCount()); long count = context.getBean(ClientConfiguration.class).latch.getCount();
context.close();
assertEquals(0, count);
} }
@Configuration @Configuration
@ -84,7 +85,7 @@ public class SampleWebSocketsApplicationTests {
@Override @Override
public void run(String... args) throws Exception { public void run(String... args) throws Exception {
logger.info("Waiting for response: latch=" + latch.getCount()); logger.info("Waiting for response: latch=" + latch.getCount());
latch.await(); latch.await(10, TimeUnit.SECONDS);
logger.info("Got response: latch=" + latch.getCount()); logger.info("Got response: latch=" + latch.getCount());
} }

Loading…
Cancel
Save