Spring Framework 參考文檔(SockJS Fallback)

SockJS Fallback

在公共Internet上,在你控制以外的限制性代理可能會妨礙WebSocket交互,要麼是由於它們沒有配置爲傳遞Upgrade header,要麼是由於它們關閉了看起來空閒的長鏈接。php

這個問題的解決方案是WebSocket模擬 - 也就是說,首先嚐試使用WebSocket,而後轉而使用基於http的技術來模擬WebSocket交互並公開相同的應用程序級別的API。html

在Servlet堆棧上,Spring Framework爲SockJS協議提供了服務器(以及客戶端)支持。java

概述

SockJS的目標是讓應用程序使用WebSocket API,但若是在運行時有必要,能夠回退到非WebSocket替代方案,而不須要修改應用程序代碼。git

SockJS包括:github

  • SockJS協議以可執行的敘述性測試的形式定義。
  • SockJS JavaScript客戶端 — 用於瀏覽器的客戶端庫。
  • SockJS服務器實現,包括一個在Spring Framework spring-websocket模塊。
  • spring-websocket模塊中的SockJS Java客戶端(4.1版以來)。

SockJS是爲瀏覽器設計的,它使用各類技術來支持各類瀏覽器版本,有關SockJS傳輸類型和瀏覽器的完整列表,請參閱SockJS客戶端頁面。傳輸分爲三大類:WebSocket、HTTP流媒體以及HTTP長輪詢,有關這些類別的概述,請參閱這篇博客文章web

SockJS客戶端首先發送GET /info以從服務器獲取基本信息,在那以後,它必須決定使用哪一種傳輸方式。若是可能的話,使用WebSocket,若是不是,在大多數瀏覽器中,至少有一個HTTP流媒體選項,若是還不是,則使用HTTP(長)輪詢。spring

全部傳輸請求都具備如下URL結構:json

http://host:port/myApp/myEndpoint/{server-id}/{session-id}/{transport}
  • {server-id}用於在集羣中路由請求,但不用於其餘用途。
  • {session-id}關聯屬於SockJS會話的HTTP請求。
  • {transport}指示傳輸類型(例如websocketxhr-streaming和其餘)。

WebSocket傳輸只須要一個HTTP請求來完成WebSocket握手,此後全部消息都在該socket上交換。segmentfault

HTTP傳輸須要更多的請求,例如,Ajax/XHR流依賴於一個對服務器到客戶端消息的長時間運行的請求,以及對客戶端到服務器消息的額外HTTP POST請求。長輪詢與此相似,只是它在每一個服務器到客戶端發送後結束當前請求。api

SockJS添加了最少的消息框架,例如,服務器最初發送字母o(「open」 frame),消息以["message1","message2"](json編碼數組)的形式發送,若是在25秒內(默認狀況下)沒有消息流,則發送字母h("heartbeat" frame)和字母c("close" frame)來關閉會話。

要了解更多信息,請在瀏覽器中運行一個示例並觀察HTTP請求,SockJS客戶端容許修復傳輸列表,所以能夠一次查看每一個傳輸。SockJS客戶端還提供了一個debug標誌,它在瀏覽器控制檯中啓用有用的消息,在服務器端,你能夠爲org.springframework.web.socket啓用TRACE日誌記錄,有關更多細節,請參閱SockJS協議敘述性測試

啓用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(1.0.x版),它模擬W3C WebSocket API,並與服務器通訊,根據運行它的瀏覽器選擇最佳傳輸選項。請參閱sockjs-client頁面和瀏覽器支持的傳輸類型列表。客戶端還提供了一些配置選項 - 例如,指定要包含哪些傳輸。

IE 8和9

Internet Explorer 8和9仍在使用中,它們是有SockJS的關鍵緣由,本節討論在這些瀏覽器中運行的重要注意事項。

SockJS客戶端使用微軟的XDomainRequest支持IE 8和9中的Ajax/XHR流,這能夠跨域工做,但不支持發送cookie。cookie對於Java應用程序來講一般是必不可少的,可是,因爲SockJS客戶端能夠與許多服務器類型(不只僅是Java類型)一塊兒使用,所以須要知道cookie是否重要。若是是這樣,SockJS客戶端更喜歡使用Ajax/XHR來流媒體,不然,它依賴於基於iframe的技術。

來自SockJS客戶端的第一個/info請求是對信息的請求,這些信息可能會影響客戶端對傳輸的選擇,其中一個細節是,服務器應用程序是否依賴於cookie(例如,用於身份驗證仍是使用具備粘性的會話進行集羣),Spring的SockJS支持包括一個名爲sessionCookieNeeded的屬性,它是默認啓用的,由於大多數Java應用程序都依賴於JSESSIONID cookie。若是你的應用程序不須要它,你能夠關閉這個選項,而後SockJS客戶端應該選擇IE8和IE9中的xdr-streaming

若是你確實使用基於iframe的傳輸,請記住,能夠經過設置HTTP響應頭X-Frame-OptionsDENYSAMEORIGINALLOW-FROM <origin>來指示瀏覽器阻止在給定頁面上使用IFrames,這是用來防止點擊劫持

Spring Security 3.2+支持在每一個響應上設置 X-Frame-Options,默認狀況下,Spring Security Java配置將其設置爲 DENY,在3.2中,Spring Security XML命名空間默認不設置該header,但能夠配置爲這樣作,未來,它可能會默認設置它。

有關如何配置X-Frame-Options header設置的詳細信息,請參閱Spring Security文檔的默認Security Headers,你還能夠查看SEC-2501瞭解其餘背景信息。

若是你的應用程序添加了X-Frame-Options響應header(它應該這樣作!)並依賴於基於iframe的傳輸,你須要將header值設置爲SAMEORIGINALLOW-FROM <origin>。Spring SockJS支持還須要知道SockJS客戶端的位置,由於它是從iframe加載的,默認狀況下,iframe被設置爲從CDN位置下載SockJS客戶端,將此選項配置爲使用與應用程序相同源的URL是個好主意。

下面的示例展現瞭如何在Java配置中實現這一點:

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

    // ...

}

XML命名空間經過<websocket:sockjs>元素提供了相似的選項。

在初始開發期間,啓用SockJS客戶端 devel模式,以防止瀏覽器緩存SockJS請求(好比iframe),不然會被緩存,有關如何啓用它的詳細信息,請參閱 SockJS客戶端頁面。

心跳

SockJS協議要求服務器發送心跳消息,以防止代理判定鏈接掛起,Spring SockJS配置有一個名爲heartbeatTime的屬性,你可使用它來定製頻率。默認狀況下,在25秒後發送心跳,假設在該鏈接上沒有發送其餘消息,這個25秒的值符合如下對公共互聯網應用程序的IETF建議

在WebSocket和SockJS上使用STOMP時,若是STOMP客戶端和服務器協商交換心跳,那麼SockJS心跳就被禁用了。

Spring SockJS支持還容許你配置TaskScheduler來調度心跳任務,任務調度程序由線程池支持,其默認設置基於可用處理器的數量,你應該考慮根據你的特定須要定製設置。

客戶端斷開鏈接

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

SockJS和CORS

若是容許跨源請求(請參閱容許的源),那麼SockJS協議將使用CORS來支持XHR流和輪詢傳輸中的跨域支持,所以,除非檢測到響應中存在CORS headers,不然將自動添加CORS headers,所以,若是應用程序已經配置爲提供CORS支持(例如,經過Servlet過濾器),Spring的SockJsService就跳過了這一部分。

還能夠經過在Spring的SockJsService中設置suppressCors屬性來禁用這些CORS headers的添加。

SockJS指望如下headers和值:

  • Access-Control-Allow-Origin:從Origin請求header的值初始化。
  • Access-Control-Allow-Credentials:老是設爲true
  • Access-Control-Request-Headers:從等效請求header的值初始化。
  • Access-Control-Allow-Methods:傳輸支持的HTTP方法(參見TransportType枚舉)。
  • Access-Control-Max-Age:設置爲31536000(1年)。

有關確切的實現,請參閱源代碼中AbstractSockJsServiceTransportType枚舉中的addCorsHeaders

或者,若是CORS配置容許,考慮使用SockJS端點前綴排除URL,從而讓Spring的SockJsService處理它。

SockJsClient

Spring提供了一個SockJS Java客戶端來鏈接到遠程SockJS端點,而無需使用瀏覽器,當須要經過公共網絡在兩臺服務器之間進行雙向通訊時(也就是說,網絡代理可能會阻止WebSocket協議的使用),這一點尤爲有用。對於測試目的(例如,模擬大量併發用戶),SockJS Java客戶端也很是有用。

SockJS Java客戶端支持websocketxhr-streamingxhr-polling傳輸,剩下的一個只有在瀏覽器中使用纔有意義。

你能夠配置WebSocketTransport使用:

  • 在JSR-356運行時中的StandardWebSocketClient
  • 使用Jetty 9+原生WebSocket API的JettyWebSocketClient
  • Spring的WebSocketClient的任何實現。

根據定義,XhrTransport同時支持xhr-streamingxhr-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格式的數組來處理消息,默認狀況下,使用Jackson 2並須要在類路徑中,或者,你能夠配置 SockJsMessageCodec的自定義實現,並在 SockJsClient上配置它。

要使用SockJsClient模擬大量併發用戶,須要配置底層HTTP客戶端(用於XHR傳輸),以容許足夠數量的鏈接和線程,下面的例子展現瞭如何使用Jetty:

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

    // ...
}
  • streamBytesLimit屬性設置爲512KB(默認爲128KB => 128 * 1024)。
  • httpMessageCacheSize屬性設置爲1000(默認爲100)。
  • disconnectDelay屬性設置爲30秒(默認爲5秒 => 5 * 1000)。

上一篇:WebSocket API

下一篇:STOMP

相關文章
相關標籤/搜索