Spring Boot實現STOMP協議的WebSocket


關注公眾號:鍋外的大佬

每日推送國外優秀的技術翻譯文章,勵志幫助國內的開發者更好地成長!
在這里插入圖片描述

WebSocket協議是應用程序處理實時消息的方法之一。最常見的替代方案是長輪詢(long polling)和服務器推送事件(server-sent events)。這些解決方案中的每個都有其優缺點。在本文中,我將向您展示如何使用Spring Boot實現WebSocket。我將介紹服務器端和客戶端設置,使用WebSocket協議之上的STOMP進行相互通信。

服務器端將完全用Java編碼。但是,就客戶端而言,我將展示用JavaJavaScript(SockJS)編寫的片段,因為通常,WebSocket客戶端嵌入在前端應用程序中。代碼示例將演示如何使用pub-sub模型向多個用戶廣播消息以及如何僅向單個用戶發送消息。在本文的另一部分中,我將簡要討論WebSocket安全問題以及如何確保即使環境不支持WebSocket協議,基於WebSocket的解決方案也能運行。

注意,WebSocket安全話題僅在此處簡要介紹,因為這是一個非常復雜的問題,可以單獨撰寫一篇文章。由於這個原因,以及我在文章最后一節WebSocket in production?中提及的因素,我建議在生產中先對安全設置進行修改,直到生產就緒,安全措施到位為止。

1.WebSocket和STOMP協議

WebSocket協議允許應用程序之間實現雙向通信。重要的是要知道HTTP僅用於初始握手。初次握手之后,HTTP連接將升級為被WebSocket使用的新TCP/IP連接。

WebSocket協議是一種相當低級的協議。它定義了如何將字節流轉換為幀。幀可以包含文本或二進制消息。由於消息本身不提供有關如何路由或處理它的任何其他信息,因此很難在不編寫其他代碼的情況下實現更復雜的應用程序。幸運的是,WebSocket規范允許在更高的應用程序級別上使用子協議。STOMP是其中之一,由Spring Framework支持。

STOMP是一種簡單的基於文本的消息傳遞協議,最初是為RubyPythonPerl等腳本語言創建的,用於連接企業級消息代理。由於STOMP,使不同語言開發的客戶端和代理可以相互發送和接收消息。WebSocket協議有時稱為Web TCP。以此類推,STOMP被稱為Web HTTP。它定義了一些映射到WebSocket幀的幀類型,例如CONNECTSUBSCRIBEUNSUBSCRIBEACKSEND。一方面,這些命令非常便於管理通信,另一方面,它們允許我們實現具有更復雜功能的解決方案,如消息確認。

2.服務端:Spring Boot和WebSocket

為了構建WebSocket服務器端,我們將利用Spring Boot框架,該框架使得在Java中開發獨立程序和Web應用程序更快。 Spring Boot包含spring-WebSocket模塊,該模塊與Java WebSocket API標准(JSR-356)兼容。

使用Spring Boot實現WebSocket服務器端並不是一項非常復雜的任務,只包含幾個步驟,我們將逐一介紹。

*步驟1:*首先,添加WebSocket庫依賴項。

<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> 

如果計划使用JSON格式傳輸消息,則可能還需要包含GSONJackson依賴項。您還可能還需要一個安全框架,例如Spring Security

*步驟2:*然后,可以配置Spring啟用WebSocketSTOMP消息傳遞。

Configuration
@EnableWebSocketMessageBroker public class WebSocketConfig implements WebSocketMessageBrokerConfigurer { @Override public void registerStompEndpoints(StompEndpointRegistry registry) { registry.addEndpoint("/mywebsockets") .setAllowedOrigins("mydomain.com").withSockJS(); } @Override public void configureMessageBroker(MessageBrokerRegistry config){ config.enableSimpleBroker("/topic/", "/queue/"); config.setApplicationDestinationPrefixes("/app"); } } 

configureMessageBroker主要做兩件事情:

  • 創建內存中的消息代理,其中包含一個或多個用於發送和接收消息的目標。在上面的示例中,定義了兩個目標地址前綴:topicqueue。它們遵循以下慣例:通過pub-sub模型將以topic為前綴的消息傳遞到所有訂閱客戶端的目標地址。另一方面,私有消息的目標地址通常以queue為前綴。
  • 定義前綴app,用於過濾目標地址,這些地址在Controller中被@MessageMapping修飾的方法處理。

image

服務器端如何處理消息

回到上面的代碼段 - 可能你已經注意到對方法withSockJS()的調用——它啟用了SockJS后備選項。簡而言之,即使互聯網瀏覽器不支持WebSocket協議,它也會讓我們的WebSockets工作。我將進一步詳細討論這個主題。

還有一件事需要澄清——為什么我們在端點上調用setAllowedOrigins()方法。一般是必需的,因為WebSocketSockJS的默認行為是僅接受同源請求。因此,如果客戶端和服務端處於不同的域,則需要調用此方法允許它們之間的通信。

*步驟3:*實現處理用戶請求的控制器
它將向訂閱特定主題的所有用戶廣播收到的消息。

這是一個將消息發送到目標地址/topic/news的示例方法。

@MessageMapping("/news") @SendTo("/topic/news") public void broadcastNews(@Payload String message) { return message; } 

也可以使用SimpMessagingTemplate而不是注解@SendTo,您可以在控制器內自動裝配(Autowired)。

@MessageMapping("/news") public void broadcastNews(@Payload String message) { this.simpMessagingTemplate.convertAndSend("/topic/news", message) } 

在后面的步驟中,可能需要添加一些其他類來保護端點,例如Spring Security框架中的ResourceServerConfigurerAdapterWebSecurityConfigurerAdapter。此外,實現消息模型通常是有益的,這樣傳輸的JSON可以映射成對象。

3.WebSocket客戶端構建

客戶端實現是一項更簡單的任務。

*步驟1:*裝配Spring STOMP客戶端

@Autowired private WebSocketStompClient stompClient; 

步驟2: 打開連接

StompSessionHandler sessionHandler = new CustmStompSessionHandler(); StompSession stompSession = stompClient.connect(loggerServerQueueUrl, sessionHandler).get(); 

此操作完成后,可以將消息發送到目的地。該消息將發送給所有訂閱主題的用戶。

stompSession.send("topic/greetings", "Hello new user"); 

以下方法也可以訂閱消息

session.subscribe("topic/greetings", this); @Override public void handleFrame(StompHeaders headers, Object payload) { Message msg = (Message) payload; logger.info("Received : " + msg.getText()+ " from : " + msg.getFrom()); } 

有時需要向特定用戶發送消息(例如,在實現聊天時)。然后,客戶端和服務器端必須使用專用於此私人會話的單獨目標地址。可以通過將唯一標識符附加到通用地址來創建目標地址的名稱,例如/queue/chat-user123HTTP會話或STOMP會話標識符可用於此目的。

Spring使發送私人消息變得更加容易。我們只需要使用@SendToUser注釋Controller的方法。然后,目標地址將由UserDestinationMessageHandler處理,它依賴於會話標識符。在客戶端,當客戶端訂閱以/user為前綴的目標地址時,此目標地址將轉換為此用戶唯一的目標地址。在服務器端,根據用戶的Principal解析用戶目標地址。

服務器端使用@SendToUser注解示例代碼:

@MessageMapping("/greetings") @SendToUser("/queue/greetings") public String reply(@Payload String message, Principal user) { return "Hello " + message; } 

或者可以使用SimpMessagingTemplate

String username = ...
this.simpMessagingTemplate.convertAndSendToUser();
   username, "/queue/greetings", "Hello " + username);

現在讓我們看看如何實現一個能夠接收私有消息的JavaScript(SockJS)客戶端,該客戶端可以接收上面的示例中的Java代碼發送的消息。值得一提的是,WebSocketsHTML5規范的一部分,並且受到大多數現代瀏覽器的支持(從版本10開始,Internet Explorer支持它們)。

function connect() { var socket = new SockJS('/greetings'); stompClient = Stomp.over(socket); stompClient.connect({}, function (frame) { stompClient.subscribe('/user/queue/greetings', function (greeting) { showGreeting(JSON.parse(greeting.body).name); }); }); } function sendName() { stompClient.send("/app/greetings", {}, $("#name").val()); } 

您可能已經注意到,要接收私人消息,客戶端需要訂閱前綴為/user的目標地址/queue/greetings。它不必擔心任何唯一標識符。但是,在客戶端登錄應用程序之前,服務器端必須初始化Principal對象。

4.WebSocket安全

許多Web應用程序使用基於cookie的身份驗證,例如,我們可以使用Spring Security限制已登錄的用戶訪問某些頁面或控制器限制。然后,通過基於cookie的HTTP會話維護用戶上下文安全,該會話稍后與為該用戶創建的WebSocketSockJS會話相關聯。 WebSocket端點可以像任何其他請求一樣受到保護,例如,在SpringWebSecurityConfigurerAdapter中的實現。

如今,Web應用程序通常使用REST API作為后端,使用OAuth/JWT令牌進行用戶身份驗證和授權。 WebSocket協議未描述服務器在HTTP握手期間如何對客戶端進行身份驗證。實際上,標准HTTP頭(例如,授權)用於此目的。不幸的是,並非所有STOMP客戶端都支持它。 SpringSTOMP客戶端允許為握手設置標頭:

WebSocketHttpHeaders handshakeHeaders = new WebSocketHttpHeaders(); handshakeHeaders.add(principalRequestHeader, principalRequestValue); 

但是SockJS的JavaScript客戶端不支持使用SockJS請求發送授權請求頭(Authorization)。但是,它允許發送可用於傳遞令牌的查詢參數。此方法需要在服務器端編寫自定義代碼,該代碼將從查詢參數中讀取令牌並對其進行驗證。特別重要的是確保令牌不與請求一起記錄(或日志受到良好保護),因為這可能會導致嚴重的安全違規。

5.SockJS后備選項

WebSocket的集成可能並不總是盡如人意。某些瀏覽器(例如,IE 9)不支持WebSocket。更重要的是,限制性代理可能使HTTP升級變得不可能,或者它們切斷了打開太久的連接。在這種情況下,SockJS就會伸出援手。

SockJS傳輸分為三大類:WebSocketHTTP StreamingHTTP Long Polling。通信從SockJS發送GET /info以從服務器獲取基本信息開始。SockJS根據響應決定使用的哪種傳輸方式。第一個選擇是WebSocket。如果不支持,則盡可能使用Streaming。如果Streaming也不可用,則選擇輪詢作為傳輸方法。

6.生產中使用WebSocket

雖然這種設置有效,但它並不是“最佳”。Spring Boot允許您使用任何具有STOMP協議的完整消息系統(例如,ActiveMQ,RabbitMQ),並且外部代理可以支持更多STOMP操作(例如,確認,租借)而不是我們使用的簡單代理。 STOMP Over WebSocket提供有關WebSocketSTOMP協議的信息。它列出了處理STOMP協議的消息傳遞系統,可能是在生產中使用的更好的解決方案。特別是由於請求數量很大,消息代理需要進行集群(Spring的簡單消息代理不適合集群)。然后,不需要在WebSocketConfig中啟用簡單代理,而是需要啟用Stomp代理中繼,該中繼將消息轉發到外部消息代理和從外部消息代理轉發消息。總而言之,外部消息代理可以幫助您構建更具伸縮性和可靠性的解決方案。

作者:Tomasz Dąbrowski

譯者:Emma


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM