SpringMVC 教程 - WebSocket

简介

WebSocket提供了在客户端和服务端通过单一TCP连接建立全双工双向通信的通道。它是和HTTP不同的TCP协议,但是却建立在HTTP之上,使用80,443端口并且允许重用防火墙规则。 WebSocket通过HTTP请求的Upgrade头开启交互,如下:

GET /spring-websocket-portfolio/portfolio HTTP/1.1
Host: localhost:8080
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: Uc9l9TMkWGbHFD2qnFHltg==
Sec-WebSocket-Protocol: v10.stomp, v11.stomp
Sec-WebSocket-Version: 13
Origin: http://localhost:8080

服务端返回一个如下信息:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: 1qVdfYHU9hPOl4JYYNXF623Gzn0=
Sec-WebSocket-Protocol: v10.stomp

关于WebSocket的详细信息可以参考RFC 6455。 注意:如果websocket服务器运行在web服务器(例如nginx)之后,需要在web服务器开启websockt以便升级请求能够到到websocket服务器,如果使用云服务,需要确定云服务提供商提供这项功能。

HTTP VS WebSocket

虽然WebSocket的设计是HTTP兼容的并且也是以一个HTTP请求开始,但是它和HTTP有完全不同的架构和编程模型。 在HTTP和REST中,应用有许多URL,客户端通过不同的URL,以请求-响应的模式和服务器交互。服务器通过请求的URL,方法,头将服务路由到合适的处理器。 与之相反,WebSocket通常只有一个URL用来初始化链接。剩下的应用消息都通过同样的TCP连接交流。是一个完全不同的异步,事件驱动,消息架构。 与HTTP协议不同,websocket并不固定内容的任何语义。 WebSocket的客户端和服务端通过Sec-WebSocket-Protocol头可以协商使用高级消息协议(例如STOMP)。

WebSocket API
WebSocketHandler

只需要继承WebSocketHandler就可以创建一个WebSocket的服务器,或者更具体一点,可以继承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) {
        // ...
    }

}

配置WebSocket的映射URL: Java配置:

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();
    }

}

XML 配置:

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:websocket="http://www.springframework.org/schema/websocket"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/websocket
        http://www.springframework.org/schema/websocket/spring-websocket.xsd">

    <websocket:handlers>
        <websocket:mapping path="/myHandler" handler="myHandler"/>
    </websocket:handlers>

    <bean id="myHandler" class="org.springframework.samples.MyHandler"/>

</beans>
WebSocket 握手

初始化WebSocket握手最简单的方式是通过HandshakeInterceptor。它暴露了握手开始前后的两个方法。这样的拦截器可以用来阻止握手或者将有用的信息添加到WebSocketSession中。例如,内置的HttpSessionHandshakeInterceptor就可以将HTTP Session的属性添加到WebSocket session中。 Java 配置

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(new MyHandler(), "/myHandler")
            .addInterceptors(new HttpSessionHandshakeInterceptor());
    }

}

XML 配置:

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:websocket="http://www.springframework.org/schema/websocket"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/websocket
        http://www.springframework.org/schema/websocket/spring-websocket.xsd">

    <websocket:handlers>
        <websocket:mapping path="/myHandler" handler="myHandler"/>
        <websocket:handshake-interceptors>
            <bean class="org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor"/>
        </websocket:handshake-interceptors>
    </websocket:handlers>

    <bean id="myHandler" class="org.springframework.samples.MyHandler"/>

</beans>

可以通过继承DefaultHandshakeHandler实现定制握手实现,包括客户端域检查,协商子协议等。

部署

Java WebSocket API(JSR-356)提供了两种部署方式

  1. Servlet容器启动是调用classpath扫描
  2. Servlet容器初始化是注册API

以上这两种方式都不适合只有一个前端控制器的设计模式。 由于JSR-356的限制,Spring使用RequestUpgradeStrategy策略部署,Tomcat,Jetty,GlassFish,WebLogic,WebSphere和Undertow都支持这个策略。 JSR-356的另一个缺点是需要在启动的时候进行classpath扫描,这会明显拖慢容器启动速度。

Server 配置

对于Tomcat,WildFly和GlassFish可以配置ServletServerContainerFactoryBean Java 配置:

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    @Bean
    public ServletServerContainerFactoryBean createWebSocketContainer() {
        ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
        container.setMaxTextMessageBufferSize(8192);
        container.setMaxBinaryMessageBufferSize(8192);
        return container;
    }

}

XML 配置:

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:websocket="http://www.springframework.org/schema/websocket"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/websocket
        http://www.springframework.org/schema/websocket/spring-websocket.xsd">

    <bean class="org.springframework...ServletServerContainerFactoryBean">
        <property name="maxTextMessageBufferSize" value="8192"/>
        <property name="maxBinaryMessageBufferSize" value="8192"/>
    </bean>

</beans>

对于Jetty:

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(echoWebSocketHandler(),
            "/echo").setHandshakeHandler(handshakeHandler());
    }

    @Bean
    public DefaultHandshakeHandler handshakeHandler() {

        WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER);
        policy.setInputBufferSize(8192);
        policy.setIdleTimeout(600000);

        return new DefaultHandshakeHandler(
                new JettyRequestUpgradeStrategy(new WebSocketServerFactory(policy)));
    }

}

Jetty 的xml 配置

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:websocket="http://www.springframework.org/schema/websocket"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/websocket
        http://www.springframework.org/schema/websocket/spring-websocket.xsd">

    <websocket:handlers>
        <websocket:mapping path="/echo" handler="echoHandler"/>
        <websocket:handshake-handler ref="handshakeHandler"/>
    </websocket:handlers>

    <bean id="handshakeHandler" class="org.springframework...DefaultHandshakeHandler">
        <constructor-arg ref="upgradeStrategy"/>
    </bean>

    <bean id="upgradeStrategy" class="org.springframework...JettyRequestUpgradeStrategy">
        <constructor-arg ref="serverFactory"/>
    </bean>

    <bean id="serverFactory" class="org.eclipse.jetty...WebSocketServerFactory">
        <constructor-arg>
            <bean class="org.eclipse.jetty...WebSocketPolicy">
                <constructor-arg value="SERVER"/>
                <property name="inputBufferSize" value="8092"/>
                <property name="idleTimeout" value="600000"/>
            </bean>
        </constructor-arg>
    </bean>

</beans>
跨域

Spring实现的WebSocket和SockJs默认不支持跨域请求。如需跨域请求需要进行设置。 对于跨域处理的三种方式:

  1. 只允许同域下的访问:在这种模式下,SockJS开启,IFrame 响应头X-Frame-Options=SAMEREGION,JSONP被禁止。
  2. 允许指定域访问:这种模式下origin必须以’http://'或者'https://'开头。在这种模式下,SockJS启用,IFrame和JSONP被禁用
  3. 允许所有域访问: 这种模式下origin设置为*,SockJS,JSONP,Iframe都支持。

Java配置:

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").setAllowedOrigins("http://mydomain.com");
    }

    @Bean
    public WebSocketHandler myHandler() {
        return new MyHandler();
    }

}

XML 配置:

<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xmlns:websocket="http://www.springframework.org/schema/websocket"
    xsi:schemaLocation="
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/websocket
        http://www.springframework.org/schema/websocket/spring-websocket.xsd">

    <websocket:handlers allowed-origins="http://mydomain.com">
        <websocket:mapping path="/myHandler" handler="myHandler" />
    </websocket:handlers>

    <bean id="myHandler" class="org.springframework.samples.MyHandler"/>

</beans>

原文发布于微信公众号 - 代码拾遗(gh_8f61e8bcb1b1)

原文发表时间:2018-04-25

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏坚毅的PHP

python日志按时间切分-----TimedRotatingFileHandler

原生loggging类+ TimedRotatingFileHandler类 实现按day hour second 切分 原生loggging类+ Timed...

52160
来自专栏大数据实战演练

ambari的服务启动顺序如何设置

角色是组件的另一个名称(例如:NAMENODE,DATANODE,RESOURCEMANAGER,HBASE_MASTER等)。 顾名思义,可以告诉Ambari...

45320
来自专栏ytkah

laravel dingo/api添加jwt-auth认证

前面我们学了laravel dingo/api创建简单的api,这样api是开放给所有人的,如何查看和限制api的调用呢?可以用jwt-auth来验证,JSON...

17220
来自专栏Java技术栈

Druid-目前最好的连接池

Druid是什么 Druid是阿里开源的连接池,是Java语言中最好的数据库连接池.Druid能够提供强大的监控和扩展功能,是为监控而生的数据库连接池! Git...

379170
来自专栏乱炖

搭建 Harbor 私有镜像仓库

19730
来自专栏代码世界

Python之日志 logging模块

关于logging模块的日志功能 典型的日志记录的步骤是这样的: 创建logger 创建handler 定义formatter 给handler添加format...

27880
来自专栏deed博客

交叉编译安卓busybox

19920
来自专栏JMCui

Spring消息之WebSocket

21430
来自专栏岑玉海

oozie 运行demo

昨晚装好了oozie,能启动了,并且配置了mysql作为数据库,好了,今天要执行oozie自带的demo了,好家伙,一执行就报错!报错很多,就不一一列举了,就...

45880
来自专栏bboysoul

开源堡垒机jumpserver搭建

之前说了国产良心kodexplorer,今天再说一个国内比较好的开源项目jumpserver,除此之外还可以的国内开源项目我觉得就是宝塔面板了。废话不多说上教程...

85730

扫码关注云+社区

领取腾讯云代金券