Spring Framework 參考文檔(WebSocket STOMP)

WebSocket STOMP

WebSocket協議定義了兩種類型的消息(文本和二進制),可是它們的內容沒有定義。協議定義了一種機制,供客戶端和服務器協商子協議(即更高級別的消息傳遞協議),以便在WebSocket上使用它來定義每一個消息能夠發送哪些類型、格式是什麼、每一個消息的內容等等。子協議的使用是可選的,但不管如何,客戶端和服務器都須要就一些定義消息內容的協議達成一致。html

概述

STOMP(簡單的面向文本的消息傳遞協議)最初是爲腳本語言(如Ruby、Python和Perl)建立的,用於鏈接到企業消息代理,它被設計用於處理經常使用消息傳遞模式的最小子集,STOMP能夠用於任何可靠的雙向流網絡協議,如TCP和WebSocket,雖然STOMP是一個面向文本的協議,但消息payload能夠是文本或二進制。java

STOMP是基於幀的協議,其幀是基於HTTP建模的,下面的清單顯示了一個STOMP幀的結構:git

COMMAND
header1:value1
header2:value2

Body^@

客戶端可使用SENDSUBSCRIBE命令發送或訂閱消息,以及描述消息是關於什麼而且誰應該接收它的destination header。這支持一種簡單的發佈-訂閱機制,你可使用該機制將消息經過代理髮送到其餘鏈接的客戶端,或將消息發送到服務器,以請求執行某些工做。github

當你使用Spring的STOMP支持時,Spring WebSocket應用程序充當客戶端的STOMP代理,消息被路由到@Controller消息處理方法或一個跟蹤訂閱並向訂閱用戶廣播消息的簡單的內存代理。你還能夠將Spring配置爲使用專用的STOMP代理(例如RabbitMQ、ActiveMQ和其餘的)來實際廣播消息,在這種狀況下,Spring維護到代理的TCP鏈接,將消息傳遞給代理,並將消息從代理向下傳遞到鏈接的WebSocket客戶端。所以,Spring web應用程序能夠依賴於統一的基於http的安全性、公共驗證和熟悉的消息處理編程模型。web

下面的示例顯示了訂閱股票報價的客戶端,服務器可能按期發出股票報價(例如,經過調度任務使用SimpMessagingTemplate向代理髮送消息)。spring

SUBSCRIBE
id:sub-1
destination:/topic/price.stock.*

^@

下面的示例顯示了發送交易請求的客戶端,服務器能夠經過@MessageMapping方法處理該請求:編程

SEND
destination:/queue/trade
content-type:application/json
content-length:44

{"action":"BUY","ticker":"MMM","shares",44}^@

執行以後,服務器能夠向客戶端廣播交易確認消息和詳細信息。json

destination的含義在STOMP規範中故意保持不透明,它能夠是任何字符串,徹底由STOMP服務器來定義它們支持的destination的語義和語法。然而,destination一般是像路徑字符串,其中包含/topic/..意味着發佈-訂閱(一對多)和/queue/意味着點對點(一對一)消息交換。segmentfault

STOMP服務器可使用MESSAGE命令向全部訂閱者廣播消息,下面的示例顯示了服務器向訂閱的客戶端發送股票報價:api

MESSAGE
message-id:nxahklf6-1
subscription:sub-1
destination:/topic/price.stock.MMM

{"ticker":"MMM","price":129.45}^@

服務器不能發送未經請求的消息,來自服務器的全部消息都必須響應特定的客戶端訂閱,服務器消息的subscription-id header必須與客戶端訂閱的id header匹配。

前面的概述旨在提供對STOMP協議的最基本的理解,咱們建議全面審查協議規範

優勢

使用STOMP做爲子協議,可讓Spring Framework和Spring Security提供比使用原始WebSockets更豐富的編程模型,關於HTTP與原始TCP的區別,以及它如何讓Spring MVC和其餘web框架提供豐富的功能,能夠提出一樣的觀點,如下是一些好處:

  • 無需建立自定義消息傳遞協議和消息格式。
  • STOMP客戶端,包含一個Spring Framework中的Java客戶端。
  • 你能夠(可選地)使用消息代理(如RabbitMQ、ActiveMQ等)來管理訂閱和廣播消息。
  • 應用程序邏輯能夠組織在任意數量的@Controller實例中,能夠基於STOMP destination header將消息路由到它們,而沒必要針對給定鏈接使用單個WebSocketHandler處理原始WebSocket消息。
  • 你可使用Spring Security來基於STOMP destination和消息類型保護消息。

啓用STOMP

spring-messagingspring-websocket模塊提供WebSocket支持的STOMP,一旦有了這些依賴項,就能夠經過WebSocket使用SockJS Fallback公開STOMP端點,以下例所示:

import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/portfolio").withSockJS();  
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.setApplicationDestinationPrefixes("/app"); 
        config.enableSimpleBroker("/topic", "/queue"); 
    }
}
  • /portfolio是WebSocket(或SockJS)客戶端爲WebSocket握手須要鏈接到的端點的HTTP URL。
  • /app開頭的destination header的STOMP消息被路由到@Controller類中的@MessageMapping方法。
  • 使用內置的消息代理進行訂閱和廣播,並將destination header 以/topic/queue開頭的消息路由到代理。

下面的示例顯示了與前面示例等價的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:message-broker application-destination-prefix="/app">
        <websocket:stomp-endpoint path="/portfolio">
            <websocket:sockjs/>
        </websocket:stomp-endpoint>
        <websocket:simple-broker prefix="/topic, /queue"/>
    </websocket:message-broker>

</beans>
對於內置的簡單代理, /topic/queue前綴沒有任何特殊含義,它們只是區分發布-訂閱和點對點消息傳遞(也就是說,許多訂閱者和一個消費者)的一種約定,當你使用外部代理時,請檢查代理的STOMP頁面,以瞭解它支持哪一種STOMP destination和前綴。

要從瀏覽器鏈接SockJS,可使用sockjs-client,對於STOMP,許多應用程序已經使用了jmesnil/stomp-websocket庫(也稱爲stomp.js),該庫功能齊全,已經在生產中使用多年,但已再也不維護。目前,JSteunou/webstomp-client是該庫最積極維護和發展的繼承者,下面的示例代碼就是基於它編寫的:

var socket = new SockJS("/spring-websocket-portfolio/portfolio");
var stompClient = webstomp.over(socket);

stompClient.connect({}, function(frame) {
}

或者,若是你經過WebSocket鏈接(沒有SockJS),你可使用如下代碼:

var socket = new WebSocket("/spring-websocket-portfolio/portfolio");
var stompClient = Stomp.over(socket);

stompClient.connect({}, function(frame) {
}

注意,前面示例中的stompClient不須要指定loginpasscode header,即便有,它們也會在服務器端被忽略(或者更確切地說,被覆蓋),有關身份驗證的更多信息,請參閱鏈接到代理和身份驗證。

更多的示例代碼:

消息流

一旦公開了STOMP端點,Spring應用程序就成爲鏈接的客戶端的STOMP代理,本節描述服務器端的消息流。

spring-messaging模塊包含對源自Spring Integration的消息傳遞應用程序的基本支持,而且後來被提取併合併到Spring Framework中,以便在許多Spring項目和應用程序場景中獲得更普遍的使用。下面的列表簡要描述了一些可用的消息傳遞抽象:

Java配置(即@EnableWebSocketMessageBroker)和XML命名空間配置(即<websocket:message-broker>)都使用前面的組件組裝消息工做流,下圖顯示了啓用簡單內置消息代理時使用的組件:

message-flow-simple-broker.png

前面的圖顯示了三個消息通道:

  • clientInboundChannel:傳遞從WebSocket客戶端收到的消息
  • clientOutboundChannel:用於向WebSocket客戶端發送服務器消息。
  • brokerChannel:用於從服務器端應用程序代碼中向消息代理髮送消息。

下一個圖顯示了配置外部代理(例如RabbitMQ)來管理訂閱和廣播消息時使用的組件:

message-flow-broker-relay.png

前兩個圖的主要區別是使用「代理轉播」將消息經過TCP傳遞到外部STOMP代理,並將消息從代理向下傳遞到訂閱客戶端。

當從WebSocket鏈接接收到消息時,它們被解碼到STOMP幀,轉換爲Spring Message表示,併發送到clientInboundChannel進行進一步處理。例如,以/app開頭的destination header的STOMP消息能夠路由到帶註解的控制器中的@MessageMapping方法,而/topic/queue消息能夠直接路由到消息代理。

處理來自客戶端的STOMP消息的@Controller註解能夠經過brokerChannel向消息代理髮送消息,而代理經過clientOutboundChannel向匹配的訂閱者廣播消息。相同的控制器也能夠對HTTP請求進行相同的響應,所以客戶端能夠執行HTTP POST,而後@PostMapping方法能夠向消息代理髮送消息,以便向訂閱的客戶端廣播消息。

咱們能夠經過一個簡單的示例跟蹤流,考慮下面的示例,它設置了一個服務器:

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/portfolio");
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.setApplicationDestinationPrefixes("/app");
        registry.enableSimpleBroker("/topic");
    }

}

@Controller
public class GreetingController {

    @MessageMapping("/greeting") {
    public String handle(String greeting) {
        return "[" + getTimestamp() + ": " + greeting;
    }

}

前面的示例支持如下流:

  1. 客戶端鏈接到http://localhost:8080/portfolio,一旦創建了WebSocket鏈接,STOMP幀就開始在其上流動。
  2. 客戶端發送帶有/topic/greeting的destination header的SUBSCRIBE幀,接收和解碼後,消息被髮送到clientInboundChannel,而後被路由到消息代理,消息代理存儲客戶端訂閱。
  3. 客戶端向/app/greeting發送一個SEND幀,/app前綴有助於將其路由到帶註解的控制器。去掉/app前綴後,destination剩餘的/greeting部分映射到GreetingController中的@MessageMapping方法。
  4. GreetingController返回的值被轉換爲一個Spring Message,其payload基於返回值和/topic/greeting的默認destination header(由/app替換爲/topic的輸入destination派生而來)。
  5. 消息代理找到全部匹配的訂閱者,並經過clientOutboundChannel向每一個訂閱者發送MESSAGE幀,消息從該通道編碼爲STOMP幀並在WebSocket鏈接上發送。

下一節將詳細介紹帶註解的方法,包括支持的參數和返回值。

帶註解的控制器

應用程序可使用帶註解的@Controller類來處理來自客戶端的消息,這些類能夠聲明@MessageMapping@SubscribeMapping@ExceptionHandler方法,以下面的主題所述:

@MessageMapping

你可使用@MessageMapping來註解路由基於它們的destination消息的方法,在方法級別和類型級別都支持它。在類型級別,@MessageMapping用於表達控制器中全部方法之間的共享映射。

默認狀況下,映射值是Ant風格的路徑模式(例如/thing*/thing/**),包括對模板變量的支持(例如/thing/{id}),這些值能夠經過@DestinationVariable方法參數引用,應用程序還能夠爲映射切換到點分隔的destination約定,點做爲分隔符章節進行了解釋。

支持的方法參數

下表描述了方法參數:

方法參數 描述
Message 用於訪問完整的消息

發送消息

若是但願從應用程序的任何部分向鏈接的客戶端發送消息,該怎麼辦?任何應用程序組件均可以向brokerChannel發送消息,最簡單的方法是注入SimpMessagingTemplate並使用它發送消息,一般,你將按類型注入它,以下面的示例所示:

@Controller
public class GreetingController {

    private SimpMessagingTemplate template;

    @Autowired
    public GreetingController(SimpMessagingTemplate template) {
        this.template = template;
    }

    @RequestMapping(path="/greetings", method=POST)
    public void greet(String greeting) {
        String text = "[" + getTimestamp() + "]:" + greeting;
        this.template.convertAndSend("/topic/greetings", text);
    }

}

可是,若是存在另外一個相同類型的bean,也能夠經過它的名稱(brokerMessagingTemplate)來限定它。


上一篇:SockJS Fallback

相關文章
相關標籤/搜索