springboot 版本爲 2.7,利用websocket向前端推送數據,配置如下;
1、添加依賴:
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-websocket</artifactId> </dependency>
項目中其他依賴自動安裝了 org.apache.tomcat.embed:tomcat-embed-websocket:9.0.26 依賴包,無需手動添加。如果你的項目中沒有這個依賴,則需要手動添加到 pom 中。
2、配置類中添加一個Bean
@Bean WebSocketConfigurer createWebSocketConfigurer(@Autowired MessageHandler messageHandler, @Autowired MessageHandshakeInterceptor messageHandshakeInterceptor) { return new WebSocketConfigurer() { @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(messageHandler, "/websocket").addInterceptors(messageHandshakeInterceptor).setAllowedOrigins("*"); } }; }
其中自動注入了兩個對象: messageHandler 和 messageHandshakeInterceptor,其中 messageHandler 中用於消息的初始化、監聽、和連接的銷燬,messageHandshakeInterceptor 用於會話攔截,從httpSession中獲取用戶信息,包括登陸、權限相關。
3、messageHandler 對象
@Component public class MessageHandler extends TextWebSocketHandler { // 保存所有會話實例 private Map<String, WebSocketSession> clients = new ConcurrentHashMap<>(); @Autowired Logger logger; @Override public void afterConnectionEstablished(WebSocketSession webSocketSession) throws IOException { //新會話放入Map clients.put(webSocketSession.getId(), webSocketSession); String httpSessionId = (String) webSocketSession.getAttributes().get("httpSessionId"); logger.info(httpSessionId); logger.info("websocket: {} 連接成功", webSocketSession.getId()); webSocketSession.sendMessage(new TextMessage("welcome")); } @Override public void afterConnectionClosed(WebSocketSession webSocketSession, CloseStatus status) { clients.remove(webSocketSession.getId()); logger.info("websocket: {} 斷開連接", webSocketSession.getId()); } public void handleTextMessage(WebSocketSession webSocketSession, TextMessage message) { String s = message.getPayload(); System.out.println(s); } }
messageHandler 從 TextWebSocketHandler 繼承,因爲發送的是文本消息。如果是二進制消息,可以從 BinaryWebSocketHandler 繼承。覆寫的 afterConnectionEstablished 方法 和 afterConnectionClosed 方法,顧名思義,用於websocket連接建立和銷燬的回調。handleTextMessage 方法用於消息監聽。
如果是主動發送消息,可以從 clients 中保存的websocketSession 會話集合中,取出對應的 websocketSession,調用其 sendMessage方法,如果是羣發消息,則直接廣播數據即可。注意消息需要 TextMessage 包裝。
for (String id : clients.keySet()) { WebSocketSession session = clients.get(id); session.sendMessage(new TextMessage("welcome")); }
問題來了,如果是給特定的客戶端發送消息,該如何區分客戶端?一般在登陸驗證後,將驗證信息保存到 httpSession中,websocketSession沒有用戶登陸信息,如果這時有一條消息過來,該發送給哪個 websocketSession?所以需要在websocketSession中拿到httpSession。這就是 messageHandshakeInterceptor 對象需要乾的事情。
4、messageHandshakeInterceptor 對象
@Component public class MessageHandshakeInterceptor extends HttpSessionHandshakeInterceptor { public MessageHandshakeInterceptor () { super(); } }
添加了這個攔截器之後,就可以訪問httpSession中保存的信息。測試一下:
在httpSession中放入httpSessionId:
httpSession.setAttribute("httpSessionId", httpSession.getId());
在 websocket中拿到對應的 httpSessionId:
String httpSessionId = (String) webSocketSession.getAttributes().get("httpSessionId");
logger.info(httpSessionId);
觀察 clients 中保存的key 是 websocketSession.getId() ,與 httpSeesion.getId() 是不同的。如果使用httpSeesion中的id作爲key,還可以直接從登陸會話中直接拿到對應參數,進而從clients 直接取出 websocketSession 會話,發送消息。
5、setAllowedOrigins
如果直接使用上述方案同城websocket連接是會報錯的,原因在於瀏覽器對於 websocket的跨域。所以 setAllowedOrigins 非常重要。
registry.addHandler(messageHandler, "/websocket").addInterceptors(messageHandshakeInterceptor).setAllowedOrigins("*");
6、注意
這裏的websocket 與 http 服務公用了一個端口 80 或443。實際項目中使用nginx作爲反向代理,將請求發送到實際的內網服務器上。但是nginx默認情況下不會轉發Connection
和Upgrade
首部(Header),導致WebSocket無法正常工作,nginx官方文檔裏已經說明了如何通過配置來解決。同時肯定會遇到連接60s後自動斷開的問題,因爲nginx默認60秒發現沒有數據傳送,就會關閉連接,可以通過proxy_read_timeout指令把這一時間延長。
參考鏈接:https://fookwood.com/spring-boot-tutorial-15-websocket#respond