1. 程式人生 > >《Spring 5 官方文件》22. WebSocket Support

《Spring 5 官方文件》22. WebSocket Support

原文連結  譯者資訊:Dan  QQ:903585177

22. WebSocket 支援

參考文件的這一部分涵蓋了Spring框架對Web應用程式中WebSocket風格訊息傳遞的支援,包括使用STOMP作為應用程式級WebSocket子協議。

Section 22.1, “Introduction” 建立一個WebSocket的大致框架,涵蓋應用挑戰,設計考慮以及何時適合的想法。

22.1 介紹

對於web應用程式,WebSocket 協議RFC 6455定義了一個很重要的功能:全雙工,客戶端與伺服器之間的雙向通訊. 這是一個令人興奮的新功能,在漫長的技術歷史上,使Web更具互動性,包括Java Applet,XMLHttpRequest,Adobe
Flash,ActiveXObject,各種Comet 技術,伺服器傳送的事件等.

WebSocket協議的介紹超出了本文件的範圍。但是,至少要了解,HTTP僅用於初始握手,這依賴於內置於HTTP中的機制來請求協議升級(或在這種情況下為協議交換機),伺服器可以使用HTTP狀態101對其進行響應 (切換協議)如果它同意。
假設握手成功,HTTP升級請求下面的TCP套接字保持開啟,客戶端和伺服器都可以使用它來彼此傳送訊息.

Spring Framework 4包括一個全新的WebSocket支援的spring-websocket模組。它與Java WebSocket API標準(JSR-356)相容,並且還提供額外的附加值,如在其餘介紹中所述。

22.1.1 WebSocket 後備選項

採用WebSocket 一個重要的挑戰是在一些瀏覽器中缺乏對其的支援,值得注意的是, IE 第一個支援WebSocket 的版本是10 (詳情請參照http://caniuse.com/websockets ). 更多,一些限制性代理可以配置為阻止嘗試執行HTTP升級或在一段時間後斷開連線,因為它已經打開了太久. InfoQ的文章e“How HTML5 Web Sockets Interact With Proxy Servers”中提供了Peter Lubbers對此主題的一個很好的概述。

因此,為了今天構建一個WebSocket應用程式,需要後備選項才能在必要時模擬WebSocket API。 Spring Framework提供了基於SockJS協議的透明後備選項。 這些選項可以通過配置啟用,不需要修改應用程式.

22.1.2 訊息架構

除了中短期面臨的挑戰之外,使用WebSocket可以提出重要的設計注意事項,這對於早期的認識至關重要,特別是與我們今天建立Web應用程式相關的知識。

今天REST風格在Web應用中廣受歡迎,這是一種依賴於許多URL(資源),少數HTTP方法(動詞)以及諸如使用超媒體(連結),以及無狀態架構。.

相比之下,WebSocket應用程式可能僅使用單個URL進行初始HTTP握手。此後,所有訊息共享並在相同的TCP連線上流動。這指向一個完全不同的,非同步的,事件驅動的訊息架構。更接近於傳統訊息傳遞應用 (如:JMS,AMQP).

Spring Framework 4包括一個新的 spring-messaging 模組 ,其中包含Spring Integration 專案的的關鍵抽象,例如 Message, MessageChannel, MessageHandler以及其他可以座位訊息架構的基礎, 該模組還包括一組用於將訊息對映到方法的註釋,類似於基於Spring MVC註釋的程式設計模型。

22.1.3 WebSocket中的子協議支援

WebSocket確實建立了訊息架構,但並不要求使用任何特定的訊息協議。它是一個非常窄的TCP層,將位元組流轉換為訊息流(文字或二進位制),而不是更多。應用來解釋訊息的含義。

不同於HTTP(它是應用程式級協議),在WebSocket協議中,框架或容器的傳入訊息中沒有足夠的資訊來知道如何路由或處理它。因此,WebSocket可以說是太低級別,只是一個非常簡單的應用程式。可以做到這一點,但它可能會導致在頂部建立一個框架。這與目前使用Web框架而不是單獨使用Servlet
API的大多數Web應用程式相當。

為此,WebSocket RFC定義了子協議的使用。在握手期間,客戶端和伺服器可以使用頭部Sec-WebSocket協議來同意子協議,即較高的應用級協議使用。不需要使用子協議,即使不使用子協議,應用程式仍然需要選擇客戶端和伺服器可以理解的訊息格式。該格式可以是自定義,框架特定或標準訊息傳遞協議。

Spring框架支援使用STOMP – 一種簡單的訊息傳遞協議,最初建立用於指令碼語言,並由HTTP啟發的框架。 STOMP被廣泛支援,非常適合在WebSocket和Web上使用。

22.1.4 我應該使用WebSocket?

有關使用WebSocket的所有設計考慮,思考“什麼時候使用?”是合理的。

WebSocket最適合在Web應用程式中,客戶端和伺服器需要以高頻率和低延遲交換事件。優選的專案類別包括但不限於在金融,遊戲,合作等方面的應用。這種應用對時間延遲非常敏感,並且還需要以高頻率交換各種各樣的訊息。

但是,對於其他應用程式型別,可能並非如此。例如,一個新聞或社交軟體顯示突發新聞,因為它可用可能是完全可以的簡單的輪詢一次每隔幾分鐘。這裡的延遲很重要,但是如果新聞需要幾分鐘的時間就可以接受。

即使在延遲至關重要的情況下,如果訊息量相對較低(例如監控網路故障),則長時間輪詢的使用應被視為一種相對簡單的替代方案,其可靠性可靠,並且在效率方面是可比較的的訊息相對較低)。

低延遲和高頻率的訊息可以使WebSocket協議的使用成為關鍵。即使在這樣的應用程式中,選擇仍然是所有客戶端 –
伺服器通訊是否應該通過WebSocket訊息完成,而不是使用HTTP和REST。答案將因應用而異;然而,有可能某些功能可以通過WebSocket和REST API來暴露,以便為客戶提供替代方案。此外,REST
API呼叫可能需要向通過WebSocket連線的感興趣的客戶端廣播訊息。

Spring Framework允許@Controller@RestController類具有HTTP請求處理和WebSocket訊息處理方法。此外,Spring MVC請求處理方法或任何應用方法可以輕鬆地向所有感興趣的WebSocket客戶端或特定使用者廣播訊息。

22.2 WebSocket API

The Spring架構提供的WebSocket API 被設計成應用於各類WebSocket 引擎. 當前這個列表包括WebSocket 執行時 ,例如 Tomcat 7.0.47+, Jetty 9.1+, GlassFish 4.1+, WebLogic 12.1.3+, and Undertow 1.0+ (and WildFly 8.0+). 隨著更多的WebSocket執行時可用,可能會新增額外的支援。

22.2.1 建立和配置一個 WebSocketHandler

建立WebSocket伺服器與實現WebSocketHandler一樣簡單,或者更有可能擴充套件TextWebSocketHandlerBinaryWebSocketHandler

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-configXML名稱空間支援將上述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的幫助下 ,將WebSocketHttpRequestHandler整合到其他HTTP服務環境中相對簡單.

22.2.2 自定義he WebSocket 握手

自定義初始HTTP WebSocket握手請求的最簡單的方法是通過HandshakeInterceptor,它將握手方法之前的“before”和“after”。 這樣的攔截器可以用於阻止握手或使任何屬性可用於WebSocketSession

例如,有一個內建攔截器將HTTP會話屬性傳遞給WebSocket會話:

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(new MyHandler(), &quot;/myHandler&quot;)
            .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>

更高階的選項是擴充套件執行WebSocket握手步驟的DefaultHandshakeHandler,包括驗證客戶端,協商子協議等。
如果需要配置自定義RequestUpgradeStrategy以適應不支援的WebSocket伺服器引擎和版本,應用程式也可能需要使用此選項(有關此主題的更多資訊,請參見第22.2.4節“部署注意事項”))。
Java-config和XML名稱空間都可以配置自定義HandshakeHandler。

22.2.3 WebSocketHandler 裝飾

Spring提供了一個WebSocketHandlerDecorator基類,可用於使用附加行為來裝飾WebSocketHandler

在使用WebSocket Java-config或XML名稱空間時,預設情況下提供並添加了日誌記錄和異常處理實現。

ExceptionWebSocketHandlerDecorator捕獲從任何WebSocketHandler方法引發的所有未捕獲的異常,並關閉表示伺服器錯誤的狀態1011的WebSocket會話。

22.2.4 部署注意事項

Spring WebSocket API易於整合到Spring MVC應用程式中,其中DispatcherServlet用於HTTP
WebSocket握手以及其他HTTP請求。通過呼叫WebSocketHttpRequestHandler也很容易整合到其他HTTP處理場景中。這是方便和容易理解。但是,JSR-356執行時可以考慮特殊的考慮因素。

Java WebSocket API(JSR-356)提供了兩個部署機制。第一個涉及啟動時的Servlet容器類路徑掃描(Servlet 3功能);另一個是在Servlet容器初始化時使用的註冊API。這些機制都不可能對所有HTTP處理(包括WebSocket握手和所有其他HTTP請求)使用單個“前端控制器”,例如Spring MVC的DispatcherServlet

即使在JSR-356執行時執行時,通過提供特定於伺服器的RequestUpgradeStrategy,Spring的WebSocket支援的JSR-356的一個重大限制。

第二個考慮因素是具有JSR-356支援的Servlet容器預計將執行ServletContainerInitializer(SCI)掃描,這可能會減慢應用程式啟動速度,在某些情況下會顯著降低。

如果在升級到支援JSR-356的Servlet容器版本之後觀察到重大影響,則可以通過使用Web.XML中的元素來選擇性地啟用或禁用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>

22.2.5 配置WebSocket 引擎

每個底層WebSocket引擎都會公開控制執行時特性的配置屬性,例如訊息緩衝區大小,空閒超時等。

對於Tomcat,WildFly和GlassFish,在您的WebSocket Java配置中新增一個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>

對於Jetty, 你需要通過WebSocket Java config預先配置Jetty WebSocketServerFactory 和外掛注入到Spring’s DefaultHandshakeHandler

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(echoWebSocketHandler(),
            &quot;/echo&quot;).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>

22.2.6允許域名配置

作為 Spring Framework 4.1.5, the default behavior for WebSocket 和SockJS預設的行為僅僅是接收相同的域名請求 . 也可以允許所有或指定的域名列表。 此檢查主要是為瀏覽器客戶端設計的。

三種可能的行為:

  • 只允許相同的域名請求(預設):在此模式下,當啟用SockJS時,Iframe
    HTTP響應頭X-Frame-Options設定為SAMEORIGIN,並且禁用JSONP傳輸,因為它不允許檢查請求的來源。 因此,當啟用此模式時,不支援IE6和IE7。

  • 允許指定的域名列表:每個提供的允許來源必須以http://或https://開頭。 在此模式下,當啟用SockJS時,基於IFrame和JSONP的傳輸均被禁用。因此,啟用此模式時,不支援IE6至IE9。

  • 允許所有域名:啟用此模式,您應該提供*作為允許的原始值。 在這種模式下,所有的運輸都可用
    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(), &quot;/myHandler&quot;).setAllowedOrigins(&quot;http://mydomain.com&quot;);
    }

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

22.3 SockJS後備選項

正如簡介所說,As explained in the introduction,WebSocket is not supported in all browsers yet and may be precluded by restrictive network proxies. This is why
Spring provides fallback options that emulate the WebSocket API as close as possible based on the SockJS protocol (version 0.3.3).