spring webSocket (1)
spring webSocket (1)
流光韶逝 发表于8个月前
spring webSocket (1)
  • 发表于 8个月前
  • 阅读 66
  • 收藏 1
  • 点赞 0
  • 评论 0

腾讯云 技术升级10大核心产品年终让利>>>   

摘要: 基于TCP的协议,不同http等应用层协议.能更好进行客户端和服务端的信息交互.

某些浏览器的不支持是对其流行的一个巨大挑战.ie浏览器10才开始支持webSocket.此外,一些限制性的代理会通过进行http升级来阻碍websocket,因为websocket保持连接时间过长,这些代理会断开连接

因此,如今要用websocket,必须要使你的websocket API工作.spring框架通过使用SocketJS 协议提供了透明的可回退的选项.这些选项可以通过配置完成,不需要额外改变项目.

###26.1.2 A messaging Architecture (一个消息框架)

除了短期的适配挑战,使用websocket带来的重要的挑战在于对于早期的认知的思考,尤其是我们今天对构建web应用认知.

今天rest是一个广泛接受,理解和支持的构建web应用的架构.它依赖于很多URLs,少数的http方法,还有其他的原则,如超链接,保持无状态,等

但是一个webSocket应用或许只用一个简单的url用于初始的http握手.此后所有的消息通过同一个TCP链接进行分享和传输.它指向一个完全不同的,异步的,事件驱动,基于消息的架构.它更加接近传统的消息应用(例如,jms,AMQP).

spring4通过对spring Integer 项目的抽象而引进了一个新的spring消息模块,这个项目包括Message,MessageChannel,MessageHandler,还有其他能作为消息架构基础的东西.这个模块还引用了一系列吧消息映射到方法的注解,和spring mvc一样是基于编程模式的.

###26.1.3 WebSocket下的子协议支持

webSocket作为一个消息架构,不强制使用任何特定的消息协议.它是在TCP层之上的一个非常薄的层,并将字节流输入到消息流(无论是文本还是字节)中,而不做其他.它依赖应用来拦截消息的含义.

不像http是一个应用层协议,在webSocket协议下的输入消息没有足够的信息让一个框架或容器来知道如何回路或者处理它.因此认为WebSocket是一个没有价值的应用会很低级.它或许是,但它还能在这之上创建一个框架.就象现在大部分web应用都是使用web框架而不单单使用servlet API.

基于这个原因webSocket RFC定义了子协议的使用.在握手期间,客户端和服务端可以使用标头的Sec-WebSocket-protocol协议来对子协议达成一致,即更高级的额应用层协议的使用.子协议不是必须的,但如果不使用,应用需要选择一个能让客户端和服务端都理解的消息格式.这个格式可以是自定义的,框架特定的,或者一个标准的消息协议.

spring通过STOMP来支持,它是一个简单的,基于http的脚本语言框架使用的使用而产生的http协议.STOMP被webSocket和web广泛的支持和适配. simple text oriented message protocol 简单文本消息协议.

##26.1.4 我该使用webSocket吗

当所有的设计讨围绕着WebSocket使用时,你肯定会问,'我该何时使用它';

对于应用来最好的场景如下,该服务端和客户端进行高频率,低延迟的事件传送.主要场景,但不限于,如财务,游戏,协作应用等.这些应用对时间延迟比较敏感,且需要高频率的交互大量的信息.

但是对于其他类型的应用,这并不是原因.例如,一个新闻或社区认为这样就可以--几分钟一次的简单轮询.这里延迟很重要,但是可以接受几分钟的延迟.

即使在延迟比较低的情况下,如果消息量相对比较低(例如监控网络失败),那么长轮询是一个比较简单的可靠高效的选择(还是认为信息量相对较低).

低延迟高并发消息的混合体应该能很好的利用webSocket协议.即使在这些应用中,在客户端和服务端间的通信应使用webSocket反对使用http和rest.但是,这个答案会因为应用而改变,为了给客户端更多的选择,应用更倾向于使用webSocket和REST API一起来暴露一些功能.另外,一个rest API调用可能需要通过webSocket来广播一个消息.

spring框架容许带有@Controller或@RestController的类拥有既能处理http的又能处理WebSocket请求的方法.另外,一个spring mvc请求处理方法,或者其他与之相关的方法,可以轻易的向所有webSocket客户或特别用户广播一个消息.

##26.2 WebSocket API

spring 框架提供了一个webSocket来适配各种webSocket引擎.主流的包含websocket的运行环境列表包括Tomcat7.0.47,Jetty9.1+,GlassFish4.1+,Weblogic 12.1.3+,Undertow 1.0+(WildFly 8.0+).其他的支持会添加,更多的webSocket运行环境会得到.

如前面介绍的,直接使用WebSocket API对于应该用来说太低级了,直到他由消息格式组成,且有个别框架通过注解来拦截消息或回路他们.这也是使用spring STOMP的原因.

使用一个高级别的协议,那么WebSocket API的细节会变得弱相关,就像在使用http时,TCP交互的细节不会暴露给用户一样.除非这个环节包含了直接使用webSocket的细节.

###26.2.1 创建配置一个WebSocketHandler

创建一个WebSocket服务器非常简单,只要实现WebSocketHandler或者扩展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 的基于java或xml命名空间的配置来支持将上面的webSocket配置到一个特定的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();
    }

}

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>

以上都是在spring mvc引用中,并包含了DispatcherServlet的配置.但是,spring 的webSocket的支持并不依赖Spring mvc.通过webSocketHttpRequestHandler的帮助,可以很轻松的将一个WebSocketHandler集成到其他Http服务环境里.

###26.2.2 Customizing the WebSocket HandShake 自定义webSocket握手

最简单的定义初始Http WebSocket握手请求是通过一个HandshakeInterceptor,它暴露了握手前后的方法.这个拦截器可以排除握手或者使WebSocketSession中的属性不可用.例如,这里有一个内置的拦截器,将http的Session属性转换为webSocket的session.

@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,它推动webSocket 握手的步骤,包括验证客户端,协同子协议,以及其他.当应用需要配置一个自定义的RequestUpgradeStrategy时,也会采用这种方法,会的是适配webSocket服务引擎或没有支持的版本(详情见26.2.4部署思考这节).java配置和xml命名空间都能配置一个自定义的HandleshakeHandler.

###26.2.3 WebSocketHandler Decoration

spring提供了一个webSocketHandlerDecorator基础类可以通过额外的行为来装饰WebSocketHandler.日志和异常处理默认通过webSocket的java配置或xml命名空间来提供并添加.ExceptionWebSocketHandlerDecorator捕获所有的从WebSocketHandler方法中抛出的未被捕获异常并关闭webSocket session,还通过一个1011的状态来显示服务器错误.

###26.2.4 Deployment Considerations(部署思考)

webSocket可以轻易的同springmvc集成.也可以轻易的通过webSocketHttpRequestHandler同其他http处理场景集成.这个非常容易理解.但是还有些有关于JSR-356运行环境的思考.

java webSocket Api(JSR-356)提供了两种部署机制.一种是在启动时Servlet容器进行路径扫描(Servlet 3的功能),另一种是在Servlet初始化时使用注册API.但这两种方法都不能使用一个单个的前端控制器来处理所有的http进程,包括webSocket握手或者其他的http请求.,例如spring mvc的 DispatcherServlet.

这是JSR-356的一个明显的局限性,所以即使在jsr-356运行环境中,spring通过提供一个服务器特定的RequestUpgradeStrategy来支持其申明.

可以通过WebSocket_SPEC_211来克服这个.另外tomcat和jetty已经提供了其原生的API备选项来轻易的克服这些局限.希望其他服务器跟进.

第二思考的地方,支持jsr-356的servlet容器需要执行ServletContainerInitilizer(SCI)扫描会降低应用启动速度,在一些情况下特别明显.当容器支持JSR-356的升级后,这个影响非常明显.这个可以通过使用web.xml里的<absolute-ordering/>来可用或不可用web框架和SCI扫描.

<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">

    <absolute-ordering/>
</web-app>

如有必要,你可以通过名称来选择性是web框架可用,例如spring自己的SpringServletContainerInitializer来支持Servlet 3的java初始化API.

<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">

    <absolute-ordering>
        <name>spring_web</name>
    </absolute-ordering>

</web-app>

###26.2.5 Configuring the WebSocket Engine 配置webSocket引擎

每一个WebSocket引擎暴露了配置属性可以控制运行时功能,例如消息缓冲容量,空闲超时,其他等等.

对于Tomcat,WildFly,GlassFish来说,需要在你的WebSocket的配置class中添加一个ServletServerContainerFactoryBean.

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

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

}

或者WebSocket 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>

对于客户端的webSocket配置,你可以使用WebSocketContainerFactoryBean(XML)或者基于java的ContainerProvider.getWebSocketContainer()来配置

对于jetty来言,你需要提供一个前置的jetty的WebSocketServerFactory,通过你的webSocket的java配置将它插入到DefaultHandshakeHandler.

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

}

webSocket 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>

###26.2.6 Configuring allowed origins 配置许可的请求源

从spring4.1.5开始,WebSocket和SockJs的默认只接受相同来源的请求.它还允许接受所有或者特定来源列表.这个检验是为浏览器客户端设计的.这里不需要通过修改来源头的值来阻止其他类型的客户端.

有以下三个可能的行为:

WebSocket和SockJs允许如下配置请求头:


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>

##26.3 SockJs Fallback Options SockJs撤回选项

如同介绍的解释的,WebSocket不支持所有的浏览器,还可能被严格的网络代理拒绝.因此spring提供了回退选项,以使WebSocket基于SockJs 协议尽可能关闭.

###26.3.1 Overview of SockJs

SockJs的目标就是能使项目使用WebSocket API,并且在不支持的运行环境,如ie中安全回退而不改变应用代码.

SockJs由以下组成:

  • 以执行测试形式定义的SockJs协议

  • SockJs的js客户端,供浏览器使用的客户端类库

  • SockJs服务器实现,包括在spring框架的spring-webSocket模块

  • spring-websocket 4.1也提供了SockJs的java客户端

SockJs主要在浏览器中使用.它使用了大量的技术来尽可能的支持各种浏览器.你可以在SockJs客户端也了解所有的SockJS传输支持的类型和浏览器.运输一般以以下3种形式:WebSocket,Http Streaming,HTTP Long Polling(http长连接).可以在该博客里了解这三种形式.

SockJs客户端开始会发送"GET /info"来获取服务器基本的信息.在这之后,它必须决定使用那种传输方式.尽可能使用WebSocket.如果不是,浏览器会使用http流,或者http长轮询.

所有的传输请求都需要使用一次以下URL 结构:

http://host:port/myApp/myEndpoint/{server-id}/{session-id}/{transport}

  • {server-id} 在集群回路请求非常有用,不需要使用其他.

  • {session-id} 相关的http请求都属于一个SockJS 会话

  • {transport} 表明传输的类型,例如'websocket','xhr-streaming'等

WebSocket传输只需要做一个http 请求就能进行webSocket握手.之后所有的消息都会通过socket交换

http传输需要更多的请求.例如ajax/xhr 流依赖于一个长期运行的请求(以获取服务端到客户端的消息),还需要额外的http Post请求来获取客户端-服务端的消息.长轮询也是一样的,除非服务器端-客户端的消息发送完,它会结束了当前的请求

SockJS添加了最简消息框架.例如,服务器刚开始发送字母"o";消息是以['message1','message2']形式发送的(JSON数组),字母h则是心跳架构,如果25秒内没有收到消息.字母c(close 框架)用来关闭session.

要了解更多,需要在浏览器运行一个例子并观察其http请求.SockJS客户端允许固定传输列表所以可以一次查看每一个传输.SockJS客户端还提供了一个debug标志,它可以在浏览器控制台上打印有用信息.在服务器侧启用TRACE,它可以记录org.springframework.web.socket.更多的细节查看Socket协议的narrated test[https://sockjs.github.io/sockjs-protocol/sockjs-protocol-0.3.3.html].

###26.3.2 启用SockJS 通过java配置,SockJS很容易启用

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

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

    @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:sockjs/>
    </websocket:handlers>

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

</beans>

上面如果是spring mvc,它还需要引入到DispatcherServlet配置中.虽然,spring的webSocket和SockJS不依赖spring mvc.通过SockJsHttpRequestHandler可以很简单的将其整合到http服务环境里.

在浏览器端,应用可以使用sockjs-client(version1.0x)来模拟W3C webSocket API和交互,服务器根据运行的浏览器来选择最佳的传输选项.查看sockjs-client页,查看浏览器支持的传输方式列表.客户端还提供了几种配置选项,例如,指定要包含哪些传输.

###26.3.3 IE8,9的http 流,Ajax/xhr vs IFrame

ie8,9在一段时间内都会保持通用.他们是使用SockJS的关键因素.这节介绍了关于些浏览器的思考.

SockJs客户端通过微软的XDomainRequest来支持ie8,9的ajax/XHR流.它可以跨域工作,但不支持发送cookies.Cookies对于java项目来言一般都是必需的.既然SockJS客户端可以被很多服务器类型(不光是java)使用,它就需要知道cookies是否重要.如果这样的话SockJS客户端选择ajax/xhr流,则需要另外选择基于iframe的技术.

第一个从SockJS客户端发起的"/info"请求,它的信息将影响客户端传输选择.其中细节之一是服务器程序是否需要cookies,用来做权限验证或者粘性会话集群.spring SockJS支持引进一个叫sessionCookieNedded的属性.因为大多数java项目依赖JESSIONID cookie,默认是启动的.如果你的应用不需要这个,你可用关闭这个选项,这样SockJS客户端会在IE8,9上选择xdr-streaming.

如果你选择使用基于iframe的传输,在任何情况下,你最好知道浏览器当返回头的X-Frame-Options设置为DENY,SAMEORIGIN或者ALLOW-FROM<orgin>时,会被通知在特定的页面来阻塞Iframes的使用.它被用来阻止clickjacking. 一种ui adress attack.

Spring Security 3.2+提供了对任何回应设置X-Frame-Options的支持.spring Security的java配置默认将它设置为DENY.其命名空间默认没有设置,但你可以自己加上该配置,以后它可能会改为默认配置.

查看 spring安全文档7.1'默认安全头'来查看更多细节来知道如何配置x-frame-Options头.额外情况,你还可以检查或观察SEC-2501

如果你 的项目添加了x-Frame-Options的返回头,且依赖iframe-based传输,那么你需要设置该项的值为SAMEORIGIN 或者SLLOW_FROM<origin>.尽管Spring SockJS支持但仍需知道SockJS客户端的位置,因为它被iframe加载的.iframe默认会从一个CDN地址来下载SockJS客户端.你最好将该选项配置为该应用相同的请求元的url.

在java配置中你可用这么做.xml的命名空间也通过<websocket:sockjs>元素提供了一个相同的选项

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/portfolio").withSockJS()
                .setClientLibraryUrl("http://localhost:8080/myapp/js/sockjs-client.js");
    }

    // ...

}

在初始开始接口,可用把SockJs客户端社会中为devel模式,这样可以阻止浏览器缓存SockJS请求(例如iframe),更多细节可以查看 SockJs client页面.

###26.3.4 心跳消息 SockJS协议要求服务端发送心跳信息来排除代理是否中断连接.spring SockJS配置有一个属性叫做heartbeatTime可以用来自定义这个频率.默认如果连接没有发送信息,这个频率是25秒符合ietf对公共网略应用的建议.

当你通过WebSocket/SockJs来使用STOMP,那么STOMP客户端和服务端将协商交互的心跳,这个SockJs的心跳会禁用.

Spring SockJS支持也可以配置TaskScheduler来安排心跳任务.这个任务定时器被一个默认配置的基于有限进程数量任务池支持.应用可以通过自己的特定需要来自定义这些配置

###26.3.5 Servlet 3 Async Requests servlet3异步请求

http流和http长轮询的SockJS传输需要一个比以前更长的连接.查看这些技术简介请看博客

在Servlet容器里这个是通过Servlet3异步得到支持的,它允许Servlet容器的线程处理一个请求并持续的写出从其他线程里得到回应.

有一个特定的问题,Servlet Api没有提供了一个客户端已经离线的通知,查看Servlet_SPEC_44. 但是,Servlet容器在随后尝试写入回应时会抛出异常.因为spring的SockJs服务支持服务端发送的心跳(25秒一次),这意味客户端的失联通常在此期间是可检测的,如果频繁发送会更早发现.

网络IO失败的原因可能是很简单的客户端失联,但这会随着不必要的堆栈信息记录.spring做了最大的努力来区分这些代表客户端失联的网络失败并使用了在AbstractSockJsSession中定义特定的日志部分DISCONNECTED_CLIENT_LOG_CATEGORY.如果你需要查看这些堆栈,请在TRACE中设置该日志章节.

###26.3.6 SockJS的CORS头

Cross-Origin Resource Sharing 跨域资源共享

如果你允许交互源请求(查看26.2.6,配置允许的源),该SockJS协议使用CORS来支持XHR流和轮询传输中的跨域进行支持.所以CORS头会被自动加入知道该头在相应中被检测到.所以当一个应用已被配置了支持CORS跨域请求,例如通过一个servlet拦截器,那么spring的SockJsService会自动忽略该部分.

当然可以通过Spring的SockJsService的suppressCos属性来禁止添加CORS头信息.

下面的列表是SockJS期望的头和值信息:

  • "Access-Control-Allow-origin",初始值是"Origin"的请求头

  • "Access-Control-Allow-Credentials",长设置为true.

  • "Access-Control-Request-Headers",初始值是等效的请求头

  • "Access-Control-Allow-Method"这个http方法一个传输支持(查看TransportType枚举)

  • "Acces-control-Max-Age"- 设置为31536000(1年)

对于其他的实现可以想TransportType枚举一样在源码里查看AbstractSockJsService里的addCorsHeaders.

还可以考虑CORS配置允许它配出URLs中SockJs端点前缀而让SockJsService来处理它.

###26.3.7 SockJS Client

SockJs的java客户端是为了不需要使用浏览器就能连接远程SockJs端点而提供.这就两个服务器之间在公网上双向通信很有用,这里网络代理可能会排除WebSocket协议的使用.一个SockJS的java客户端对于测试来说也很有用,例如可以模拟大量的即时用户.

SockJS的java客户端支持"websocket","xhr-streaming",还有"xhr-polling"传输.剩下的只有在浏览器中使用才有意义.

WebSocketTransport可以被如下配置:

  • StandardWebSocketClient ,在JSR-356运行环境中

  • JettyWebSocketClient 可以在jetty9+中使用的原生WebSocket API

  • Any implementation of Spring's WebSocketClient

XhrTransport被定义为支持"xhr-Streaming"和"xhr-polling",因为在客户端上来说,这两者在通过URL连接服务器上没有区别.目前有两种实现:

  • RestTemplateXhrTransport 使用spring的RestTemplate做http请求.

  • JettyXhrTransport使用Jetty的HttpClient来发起Http请求.

下面的例子展示如何创建一个SockJS客户端和通过SockJS端点连接.

List<Transport> transports = new ArrayList<>(2);
transports.add(new WebSocketTransport(new StandardWebSocketClient()));
transports.add(new RestTemplateXhrTransport());

SockJsClient sockJsClient = new SockJsClient(transports);
sockJsClient.doHandshake(new MyWebSocketHandler(), "ws://example.com:8080/sockjs");

SockJs使用JSON格式的数组来发送信息.默认使用Jackson2并需要他在项目路径下.其他选择是你可以配置SockJsMessageCodec的自定义实现,并在SockJsClient中配置它.

要使用SockJsClient来模拟大量的即时用户,你需要配置下层的HTTp 客户端来允许大量的连接和线程.例如jetty:

ttpClient jettyHttpClient = new HttpClient();
jettyHttpClient.setMaxConnectionsPerDestination(1000);
jettyHttpClient.setExecutor(new QueuedThreadPool(1000));

也可以配置服务器端的SockJs相关属性来实现,(具体细节看javadoc)

@Configuration
public class WebSocketConfig extends WebSocketMessageBrokerConfigurationSupport {

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/sockjs").withSockJS()
            .setStreamBytesLimit(512 * 1024)
            .setHttpMessageCacheSize(1000)
            .setDisconnectDelay(30 * 1000);
    }

    // ...

}
标签: WebSocket sockJS
共有 人打赏支持
粉丝 18
博文 88
码字总数 124661
×
流光韶逝
如果觉得我的文章对您有用,请随意打赏。您的支持将鼓励我继续创作!
* 金额(元)
¥1 ¥5 ¥10 ¥20 其他金额
打赏人
留言
* 支付类型
微信扫码支付
打赏金额:
已支付成功
打赏金额: