4.3 SockJS Fallback
在公共Internet上,控件外的限制性代理可能会阻止WebSocket交互,这可能是因为它们未配置为传递Upgrade标头,或者是因为它们关闭了长期处于空闲状态的连接。
解决此问题的方法是WebSocket仿真,即先尝试使用WebSocket,然后尝试使用基于HTTP的技术来模拟WebSocket交互并公开相同的应用程序级API。
在Servlet堆栈上,Spring框架为SockJS协议提供服务器(和客户端)支持。
4.3.1 Overview
SockJS的目标是让应用程序使用WebSocket API,但在运行时必要时使用非WebSocket替代方案,而无需更改应用程序代码。
SockJS包括:
SockJS协议以可执行叙述测试的形式定义。
SockJS JavaScript客户端-一种在浏览器中使用的客户端库。
SockJS服务器实现,包括Spring框架spring-websocket模块中的一个。
spring-websocket模块中的SockJS Java客户端(从4.1版开始)。
SockJS设计用于浏览器。它使用多种技术来支持各种浏览器版本。有关SockJS传输类型和浏览器的完整列表,请参见SockJS客户端页面。传输分为三大类:WebSocket,HTTP流和HTTP长轮询。有关这些类别的概述,请参阅此博客文章。
SockJS客户端首先发送GET / info以从服务器获取基本信息。在那之后,它必须决定使用哪种交通工具。如果可能,请使用WebSocket。如果没有,在大多数浏览器中,至少有一个HTTP流选项。如果不是,则使用HTTP(长)轮询。
所有传输请求都具有以下URL结构:
http://host:port/myApp/myEndpoint/{server-id}/{session-id}/{transport}
这里:
{server-id}对于在集群中路由请求很有用,但否则不使用。
{session-id}关联属于SockJS会话的HTTP请求。
{transport}表示传输类型(例如,websocket,xhr-streaming等)。
WebSocket传输仅需要单个HTTP请求即可进行WebSocket握手。此后所有消息在该套接字上交换。
HTTP传输需要更多请求。例如,Ajax / XHR流依赖于对服务器到客户端消息的一个长时间运行的请求,以及对客户端到服务器消息的其他HTTP POST请求。长轮询类似于长轮询,只是它在每次服务器到客户端发送后结束当前请求。
SockJS添加了最少的消息框架。例如,服务器最初发送字母o(“open”帧),消息以a [“ message1”,“ message2”](JSON编码数组)发送,如果没有消息,则以字母h(“heartbeat”帧)发送持续25秒(默认情况下),然后使用字母c(“close”框)关闭会话。
要了解更多信息,请在浏览器中运行示例并查看HTTP请求。 SockJS客户端允许修复传输列表,因此可以一次查看每个传输。 SockJS客户端还提供了调试标志,该标志可在浏览器控制台中启用有用的消息。在服务器端,您可以为org.springframework.web.socket启用TRACE日志记录。有关更多详细信息,请参见SockJS协议旁白测试。
4.3.2 Enabling SockJS
您可以通过Java配置启用SockJS,如以下示例所示:
下面的示例显示与前面的示例等效的XML配置:
前面的示例用于Spring MVC应用程序,应包含在DispatcherServlet的配置中。 但是,Spring的WebSocket和SockJS支持不依赖于Spring MVC。 在SockJsHttpRequestHandler的帮助下,将其集成到其他HTTP服务环境中相对简单。
在浏览器端,应用程序可以使用sockjs-client(版本1.0.x)。 它模拟W3C WebSocket API,并与服务器通信以选择最佳的传输选项,具体取决于运行它的浏览器。 请参阅sockjs-client页面和浏览器支持的传输类型列表。 客户端还提供了几个配置选项,例如用于指定要包括的传输。
4.3.3 IE 8 and 9
Internet Explorer 8和9仍在使用。它们是拥有SockJS的关键原因。本节涵盖有关在这些浏览器中运行的重要注意事项。
SockJS客户端通过使用Microsoft的XDomainRequest在IE 8和9中支持Ajax / XHR流。跨域有效,但不支持发送Cookie。 Cookies对于Java应用程序通常是必不可少的。但是,由于SockJS客户端可用于多种服务器类型(而不仅仅是Java类型),因此它需要知道cookie是否重要。如果是这样,则SockJS客户端更喜欢Ajax / XHR进行流传输。否则,它依赖于基于iframe的技术。
SockJS客户端发出的第一个/ info请求是对可能影响客户端选择传输方式的信息的请求。这些详细信息之一是服务器应用程序是否依赖Cookie(例如,出于身份验证目的或使用粘性会话进行群集)。 Spring对SockJS的支持包括一个名为sessionCookieNeeded的属性。由于大多数Java应用程序都依赖JSESSIONID cookie,因此默认情况下启用此功能。如果您的应用程序不需要它,则可以关闭此选项,然后SockJS客户端应在IE 8和9中选择xdr-streaming。
如果您确实使用基于iframe的传输,请记住,可以通过将HTTP响应标头X-Frame-Options设置为DENY,SAMEORIGIN或ALLOW-FROM <origin>来指示浏览器阻止在给定页面上使用iframe 。这用于防止点击劫持。
Spring Security 3.2+提供了对每个响应设置X-Frame-Options的支持。 默认情况下,Spring Security Java配置将其设置为DENY。 在3.2中,Spring Security XML名称空间默认情况下不设置该标头,但可以配置为这样做。 将来,它可能会默认设置。
有关如何配置X-Frame-Options标头的设置的详细信息,请参见Spring Security文档的Default Security Headers。 您也可以查看SEC-2501以获取更多背景信息。
如果您的应用程序添加了X-Frame-Options响应标头(应如此!)并依赖于基于iframe的传输,则需要将标头值设置为SAMEORIGIN或ALLOW-FROM <origin>。 Spring SockJS支持还需要知道SockJS客户端的位置,因为它是从iframe加载的。 默认情况下,iframe设置为从CDN位置下载SockJS客户端。 最好将此选项配置为使用与应用程序源相同的URL。
以下示例显示了如何在Java配置中执行此操作:
XML名称空间通过<websocket:sockjs>元素提供了类似的选项。
在初始开发过程中,请启用SockJS客户端开发模式,以防止浏览器缓存本应缓存的SockJS请求(如iframe)。 有关如何启用它的详细信息,请参见SockJS客户端页面。
4.3.4 Heartbeats
SockJS协议要求服务器发送心跳消息,以防止代理断定连接已挂起。 Spring SockJS配置具有一个名为heartbeatTime的属性,可用于自定义频率。 默认情况下,假设没有其他消息在该连接上发送,则心跳将在25秒后发送。 这个25秒的值符合以下IETF对公共Internet应用程序的建议。
在WebSocket和SockJS上使用STOMP时,如果STOMP客户端和服务器协商要交换的心跳,则会禁用SockJS心跳。
Spring SockJS支持还允许您配置TaskScheduler来调度心跳任务。 任务调度程序由线程池支持,其默认设置基于可用处理器的数量。 您应该考虑根据您的特定需求自定义设置。
4.3.5 Client Disconnects
HTTP流和HTTP长轮询SockJS传输要求连接保持打开的时间比平常更长。有关这些技术的概述,请参见此博客文章。
在Servlet容器中,这是通过Servlet 3异步支持完成的,该支持允许退出Servlet容器线程,处理请求并继续写入另一个线程的响应。
一个特定的问题是,Servlet API不会为已离开的客户端提供通知。请参阅eclipse-ee4j / servlet-api#44。但是,Servlet容器在随后尝试写入响应时会引发异常。由于Spring的SockJS服务支持服务器发送的心跳信号(默认情况下,每25秒发送一次),这意味着通常会在该时间段内(或更早,如果消息发送频率更高)检测到客户端断开连接。
结果,由于客户端已断开连接,可能会发生网络I / O故障,从而可能在日志中填充不必要的堆栈跟踪。 Spring会尽最大努力找出代表客户端断开连接(特定于每个服务器)的网络故障,并通过使用专用日志类别DISCONNECTED_CLIENT_LOG_CATEGORY(在AbstractSockJsSession中定义)来记录最少的消息。如果需要查看堆栈跟踪,可以将该日志类别设置为TRACE。
4.3.6 SockJS and CORS
如果允许跨域请求(请参阅“允许的起源”),则SockJS协议将CORS用于XHR流和轮询传输中的跨域支持。因此,除非在响应中检测到CORS标头的存在,否则将自动添加CORS标头。因此,如果已经将应用程序配置为提供CORS支持(例如,通过Servlet过滤器),则Spring的SockJsService会跳过这一部分。
也可以通过在Spring的SockJsService中设置preventCors属性来禁用添加这些CORS标头。
SockJS需要以下标头和值:
Access-Control-Allow-Origin:从Origin请求标头的值初始化。
Access-Control-Allow-Credentials:始终设置为true。
Access-Control-Request-Headers:从等效请求标头中的值初始化。
Access-Control-Allow-Methods:传输支持的HTTP方法(请参阅TransportType枚举)。
Access-Control-Max-Age:设置为31536000(1年)。
有关确切的实现,请参见AbstractSockJsService中的addCorsHeaders和源代码中的TransportType枚举。
另外,如果CORS配置允许,请考虑排除带有SockJS端点前缀的URL,从而让Spring的SockJsService处理它。
4.3.7 SockJsClient
Spring提供了一个SockJS Java客户端,无需使用浏览器即可连接到远程SockJS端点。当需要通过公用网络在两个服务器之间进行双向通信时(即,网络代理可能会阻止使用WebSocket协议),这特别有用。 SockJS Java客户端对于测试目的也非常有用(例如,模拟大量并发用户)。
SockJS Java客户端支持websocket,xhr-streaming和xhr-polling传输。其余的仅在浏览器中有意义。
您可以使用以下命令配置WebSocketTransport:
JSR-356运行时中的StandardWebSocketClient。
通过使用Jetty 9+本机WebSocket API来创建JettyWebSocketClient。
Spring的WebSocketClient的任何实现。
根据定义,XhrTransport支持xhr-streaming和xhr-polling,因为从客户端的角度来看,除了用于连接服务器的URL之外没有其他区别。当前有两种实现:
RestTemplateXhrTransport使用Spring的RestTemplate进行HTTP请求。
JettyXhrTransport使用Jetty的HttpClient进行HTTP请求。
以下示例显示了如何创建SockJS客户端并连接到SockJS端点:
SockJS对消息使用JSON格式的数组。 默认情况下,使用Jackson 2,并且需要在类路径上。 或者,您可以配置SockJsMessageCodec的自定义实现,并在SockJsClient上对其进行配置。
要使用SockJsClient模拟大量并发用户,您需要配置基础HTTP客户端(用于XHR传输)以允许足够数量的连接和线程。 以下示例显示了如何使用Jetty进行操作:
以下示例显示了与服务器端SockJS相关的属性(有关详细信息,请参见javadoc),您还应考虑自定义:
最后更新于