Spring Chapter4 WebSocket 胡乱翻译 (一)
4. WebSocket
包含了Servlet stack,原生WebSocket交互,通过SockJS模拟,并且通过STOMP在WebSocket之上订阅、发布消息。
4.1 简介
不扯了,看到这个地方的都已经知道WebSocket是干啥的,用在啥地方。
4.2 WebSocket API
Spring框架提供了WebSocket的API,可以用来写处理WebSocket消息的client和server的程序。
4.2.1 WebSocketHandler
创建一个WebSocket服务器很简单,实现WebSockHandler接口就完事了。或者更具体点,继承TextWebSocketHandler或者BinaryWebSocketHandler:
import org.springframework.web.socket.WebSocketHandler; import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.TextMessage;
public class MyHandler extends TextWebSocketHandler { @Override public void handleTextMessage(WebSocketSession session, TextMessage message) { // ... } }
通过下面的java代码将上面的Handler配置到具体的URL上:
import org.springframework.web.socket.config.annotation.EnableWebSocket; import org.springframework.web.socket.config.annotation.WebSocketConfigurer; import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
@Configuration @EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer { @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(myHandler(), "/myHandler"); }
@Bean public WebSocketHandler myHandler() { return new MyHandler(); } }
在SpringMVC应用里,上面的配置可以可以包含在DispatcherServlet的配置中。然而,Spring WebSocket可以独立于SpringMVC。通过WebSocketHttpRequestHandler可以把WebSocketHandler集成到其他的HTTP服务环境中。
4.2.2 WebSocket握手
自定义初始HTTP WebSocket握手请求的最简单的方法是通过HandShakeInterceptor,它提供了“before”和“after”握手方法。这些拦截器可以用来阻止握手或者让WebSocketSession得到一下属性。例如,有一个内置的拦截器,可以用来向WebSocket的session传递HTTP的session的属性。
@Configuration @EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer {
@Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(new MyHandler(), "/myHandler") .addInterceptors(new HttpSessionHandshakeInterceptor()); } }
更高级的选项是继承DefaultHandshakeHandler,它执行WebSocket握手步骤,包括验证客户端来源,协商子协议等。如果应用程序需要配置自定义RequestUpgradeStrategy以适应WebSocket服务器引擎和尚不支持的版本,则应用程序可能还需要使用此选项(有关此主题的更多信息,请参阅部署)。
4.2.3 部署
Spring WebSocket API易于集成到Spring MVC应用程序中,其中DispatcherServlet同时提供HTTP WebSocket握手以及其他HTTP请求。 通过调用WebSocketHttpRequestHandler,也可以轻松地集成到其他HTTP处理场景中。 这很方便易懂。 但是,JSR-356 runtimes有些特殊注意事项。
Java WebSocket API(JSR-356)提供了两种部署机制。
- 第一个涉及启动时的Servlet容器classpath扫描(Servlet 3功能);
- 另一个是在Servlet容器初始化时使用的注册API。
这些机制都不能使用单个“前端控制器”进行所有HTTP处理 - 包括WebSocket握手和所有其他HTTP请求 - 例如Spring MVC的DispatcherServlet。
这是JSR-356的一个重要限制,Spring的WebSocket支持即使在JSR-356运行时运行时也能解决特定于服务器的RequestUpgradeStrategy问题。 Tomcat,Jetty,GlassFish,WebLogic,WebSphere和Undertow(以及WildFly)目前存在这样的策略。 (这段没看太明白,WEBSOCKET_SPEC-211可以解决这个问题)
第二个考虑因素是具有JSR-356支持的Servlet容器应该执行ServletContainerInitializer(SCI)扫描,这可能会减慢应用程序启动速度,在某些情况下会显着降低。 如果在升级到支持JSR-356的Servlet容器版本后观察到重大影响,则应该可以通过在web.xml中使用<absolute-ordering />元素来选择性地启用或禁用Web片段(和SCI扫描).
4.2.4 Server配置
每个底层WebSocket引擎都公开控制运行时特性的配置属性,例如消息缓冲区大小,空闲超时等。
对于Tomcat,WildFly和GlassFish,将ServletServerContainerFactoryBean添加到WebSocket Java配置中:
@Configuration @EnableWebSocket public class WebSocketConfig implements WebSocketConfigurer { @Bean public ServletServerContainerFactoryBean createWebSocketContainer() { ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean(); container.setMaxTextMessageBufferSize(8192); container.setMaxBinaryMessageBufferSize(8192); return container; } }
对于client端,使用ContainerProvider.getWebSocketContainer()来配置。(XML中用WebSocketContainerFactoryBean)
4.2.5 Allowed origins
hmmm,暂时没啥用,用的时候网上一搜一大堆~~
4.3 SockJS方案
模拟器,在原生WebSocket API不能用的情况下,通过SockJS继续实现功能。
4.3.1 概要
就那回事吧,不写了。
4.3.2 启用SockJS
很简单,通过java代码即可:
@Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(myHandler(), "/myHandler").withSockJS(); }
在浏览器里,程序可以使用sockjs-client来搞事情。
4.3.3 IE8,9
跳过这节,现在用Ubuntu呢:)
4.3.4 心跳
SockJS需要服务器发送心跳信号来防止连接挂掉。Spring SockJS配置有个属性叫做heartbeatTime,可以用来自定义心跳频率。默认在没有通讯25秒之后跳。
Spring SockJS也支持配置一个TaskScheduler来调度心跳任务。
4.3.5 Client disconnects
客户端断开了,但是服务器不知道。得注意。
4.3.6 SockJS和CORS
这个暂时也用不上。
4.3.7 SockJS Client
可以提供测试。一个Java的SockJS的Client。
4.4 STOMP
WebSocket只定义了2种消息:Test和Binary,但是没定义它们的内容。这是和STOMP出现了,定义了消息的种类,格式和内容。
4.4.1 概述
客户端可以使用SEND或SUBSCRIBE命令发送或订阅消息以及“目标”标头,该标头描述消息的内容以及应由谁接收消息。这启用了一个简单的发布 - 订阅机制,可用于通过代理将消息发送到其他连接的客户端,或者向服务器发送消息以请求执行某些工作。
使用Spring的STOMP支持时,Spring WebSocket应用程序充当客户端的STOMP代理。消息被路由到@Controller消息处理方法或者路由到一个简单的内存代理,该代理跟踪订阅并向订阅用户广播消息。您还可以将Spring配置为使用专用的STOMP代理(例如RabbitMQ,ActiveMQ等)来实现消息的实际广播。在这种情况下,Spring维护与代理的TCP连接,向其中继消息,并将消息从其传递到连接的WebSocket客户端。因此,Spring Web应用程序可以依赖于基于HTTP的统一安全性,通用验证以及熟悉的编程模型消息处理工作。
这是订阅接收股票报价的客户的示例,服务器可以周期性地发出该报价。通过计划任务通过SimpMessagingTemplate将消息发送到代理:
SUBSCRIBE id:sub-1 destination:/topic/price.stock.*
^@
以下是客户端发送交易请求的示例,服务器可以通过@MessageMapping方法处理该交易请求,稍后在执行后,向客户端广播交易确认消息和详细信息:
SEND destination:/queue/trade content-type:application/json content-length:44 {"action":"BUY","ticker":"MMM","shares",44}^@
在STOMP规范中故意将目的地的含义保持不透明。 它可以是任何字符串,完全取决于STOMP服务器,以定义它们支持的目标语义和语法。 然而,很常见的是,目标是类似路径的字符串,其中“/ topic / ..”表示发布 - 订阅(一对多),“/ queue /”表示点对点(一对一) -one)消息交换。
使用MESSAGE来广播消息到所有客户端:
MESSAGE message-id:nxahklf6-1 subscription:sub-1 destination:/topic/price.stock.MMM {"ticker":"MMM","price":129.45}^@
知道服务器无法发送未经请求的消息非常重要。 来自服务器的所有消息必须是特定的客户端订阅的响应,并且服务器消息的“subscription-id”头必须与客户端订阅的“id”头匹配。 以上概述旨在提供对STOMP协议的最基本的了解。 建议完整地查看协议规范。
4.4.2 好处
不用多说了,不用再造轮子了,直接开干。
4.4.3 启用STMOP
spring-messaging和spring-websocket模块里有STOMP的支持。你可以使用STOMP的endpoint:
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker; import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
@Configuration @EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/portfolio").withSockJS(); //① } @Override public void configureMessageBroker(MessageBrokerRegistry config) { config.setApplicationDestinationPrefixes("/app"); //② config.enableSimpleBroker("/topic", "/queue"); //③ } }
客户端,可以使用JSteunou/webstomp-client:
var socket = new SockJS("/spring-websocket-portfolio/portfolio"); var stompClient = webstomp.over(socket);
stompClient.connect({}, function(frame) { }
或者通过WebSocket(没有SockJS)
var socket = new WebSocket("/spring-websocket-portfolio/portfolio"); var stompClient = Stomp.over(socket); stompClient.connect({}, function(frame) { }