WebSocket的spring实现

Spring Framework 提供了一个 WebSocket API,您可以使用它来编写处理 WebSocket 消息的 client-和 server-side applications。

WebSocket API

1. WebSocketHandler

创建 WebSocket 服务器就像实现WebSocketHandler一样简单,或者更有可能扩展TextWebSocketHandler或BinaryWebSocketHandler。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
public class MyHandler implements WebSocketHandler {
private static final Logger logger = Logger.getLogger(MyHandler.class);
private static final AtomicInteger connectionIds = new AtomicInteger(0);
private Map<String, WebSocketSession> connections = new ConcurrentHashMap<>();
private String userId;

// 建立连接时候触发
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
logger.debug("连接成功!");
this.userId = "user" + connectionIds.getAndIncrement();
session.getAttributes().put(userId, userId);
connections.putIfAbsent(userId, session);
}

// 处理消息
public void handleMessage(WebSocketSession webSocketSession, WebSocketMessage<?> webSocketMessage)
throws Exception {
// 防止中文乱码
String msg = URLDecoder.decode(webSocketMessage.getPayload().toString(), "utf-8");
// 简单模拟群发消息
TextMessage reply = new TextMessage(userId + " : " + msg);
connections.forEach((s, session) -> {
try {
session.sendMessage(reply);
} catch (IOException e) {
e.printStackTrace();
}
});

}

public void handleTransportError(WebSocketSession webSocketSession, Throwable throwable) throws Exception {
if (webSocketSession.isOpen()) {
webSocketSession.close();
}
logger.debug("连接出错...关闭!");

}

// 关闭连接时候触发
public void afterConnectionClosed(WebSocketSession webSocketSession, CloseStatus closeStatus) throws Exception {
connections.remove(userId);
logger.debug("连接关闭!" + closeStatus.toString());
}

@Override
public boolean supportsPartialMessages() {
return false;
}
}

使用xml或者Java配置方式配置Bean。

2.WebSocket 握手

自定义初始 HTTP WebSocket 握手请求的最简单方法是通过HandshakeInterceptor,它在握手之前“之前”和“之后”暴露方法。您可以使用此类拦截器来阻止握手或使WebSocketSession可以使用任何属性。以下 example 使用 built-in 拦截器将 HTTP session 属性传递给 WebSocket session:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* 配置WebSocket 也可以使用xml配置
*
* Spring 的 WebSocket 支持不依赖于 Spring MVC。在WebSocketHttpRequestHandler的帮助下将WebSocketHandler集成到其他 HTTP 服务环境中相对简单。
*/
@Configuration
@EnableWebSocket // 开启websocket
public class WebSocketConfig implements WebSocketConfigurer {

/**
* 自定义初始 HTTP WebSocket 握手请求的最简单方法是通过HandshakeInterceptor,它将握手方法“之前”和“之后”。
* 这样的拦截器可用于排除握手或使WebSocketSession可用的任何属性。用于将 HTTP session 属性传递给 WebSocket session:
* @param registry
*/
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(myHandler(), "/myHandler");
}

@Bean
public WebSocketHandler myHandler() {
return new MyHandler();
}
}
//HttpSessionHandshakeInterceptor拦截器,也可以选择使用。

3. 部署

web.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
version="3.0">
<display-name>websocket</display-name>

<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

<servlet>
<servlet-name>springmvc</servlet-name>
<servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
<init-param>
<param-name>contextConfigLocation</param-name>
<param-value>classpath:springMVCConfig.xml</param-value>
</init-param>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet-mapping>
<servlet-name>springmvc</servlet-name>
<url-pattern>/</url-pattern>
</servlet-mapping>

<!--处理乱码-->
<filter>
<filter-name>characterEncodingFilter</filter-name>
<filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
<init-param>
<param-name>encoding</param-name>
<param-value>UTF-8</param-value>
</init-param>
<init-param>
<param-name>forceEncoding</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>characterEncodingFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
</web-app>

4. 服务器 Configuration

每个底层 WebSocket 引擎都会公开控制运行时特征的 configuration properties,例如消息缓冲区大小,idle 超时等等。

对于 Tomcat,WildFly 和 GlassFish,您可以将ServletServerContainerFactoryBean添加到 WebSocket Java 配置中

1
2
3
4
5
6
7
@Bean
public ServletServerContainerFactoryBean createWebSocketContainer() {
ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
container.setMaxTextMessageBufferSize(8192);
container.setMaxBinaryMessageBufferSize(8192);
return container;
}

5.跨域问题

从 Spring Framework 4.1.5 开始,WebSocket 和 SockJS 的默认行为是仅接受 same-origin 请求。也可以允许所有或指定的起源列表。

三种情况:

  • 仅允许 same-origin 个请求(默认):在此模式下,启用 SockJS 时,Iframe HTTP 响应头X-Frame-Options设置为SAMEORIGIN,并且禁用 JSONP 传输,因为它不允许检查请求的来源。因此,启用此模式时不支持 IE6 和 IE7。
  • 允许指定的原始列表:每个允许的原点必须以http://或https://开头。在此模式下,启用 SockJS 时,将禁用 IFrame 传输。因此,启用此模式时,不支持 IE6 到 IE9。
  • 允许所有来源:要启用此模式,您应提供*作为允许的原始值。在此模式下,所有传输都可用。

配置 WebSocket 和 SockJS 允许的源:

1
2
3
4
 @Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(myHandler(), "/myHandler").setAllowedOrigins("http://mydomain.com");
}

SockJS

有一些浏览器中缺少对WebSocket的支持,而SockJS是一个浏览器的JavaScript库,它提供了一个类似于网络的对象,SockJS提供了一个连贯的,跨浏览器的JavaScriptAPI,它在浏览器和Web服务器之间创建了一个低延迟、全双工、跨域通信通道。SockJS的一大好处在于提供了浏览器兼容性。即优先使用原生WebSocket,如果浏览器不支持WebSocket,会自动降为轮询的方式。

启用 SockJS

1
2
3
4
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(myHandler(), "/myHandler").withSockJS();
}