spirng專案中整合webSocket踩到的坑
花費了將近一週的時間終於將webSocket 整合到了原來的springmvc+spring +mybatis 專案中了,一路心酸一路的淚水啊,網上的技術文件都沒有解決我的問題,(很多文章都是你抄我的我抄你的)。
先來說一下我的環境:
windows 10, myeclipse 2014,orcale11 ,jdk 1.7.0.79 ,spring 4.3.8 springmvc 4.2.4.RELEASE
websocket jar
<dependency> <groupId>javax.websocket</groupId> <artifactId>javax.websocket-api</artifactId> <version>1.0</version> <scope>provided</scope> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-websocket</artifactId> <version>4.2.4.RELEASE</version> </dependency> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-messaging</artifactId> <version>4.2.4.RELEASE</version> </dependency>
下面就記錄一下自己遇到的問題,以及如何解決的問題
想貼上自己的程式碼
SpringWebSocketConfig.java 檔案
/** * * * @Description: TODO <p>SpringWebSocketConfig.java</p> * @作者: 王彥寶 * @時間: 2018年10月24日下午3:52:57 * @version V1.0 * @see java.lang.Class * @since JDK{jdk1.7} */ //@EnableWebMvc//測試這個註解可以不要 @Configuration @EnableWebSocket public class SpringWebSocketConfig extends WebMvcConfigurerAdapter implements WebSocketConfigurer{ @Override public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) { registry.addHandler(webSocketHandler(),"/webSocketIMServer.json").addInterceptors(myInterceptor()).setAllowedOrigins("*"); registry.addHandler(webSocketHandler(), "/sockjs/webSocketIMServer.json").withSockJS(); } @Bean public SpringWebSocketHandler webSocketHandler(){ return new SpringWebSocketHandler(); } @Bean public SpringWebSocketHandlerInterceptor myInterceptor(){ return new SpringWebSocketHandlerInterceptor(); } }
SpringWebSocketHandler.java 檔案
import java.io.IOException; import java.util.ArrayList; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.web.socket.CloseStatus; import org.springframework.web.socket.TextMessage; import org.springframework.web.socket.WebSocketSession; import org.springframework.web.socket.handler.TextWebSocketHandler; /** * * * @Description: TODO <p>SpringWebSocketHandler.java</p> * @作者: 王彥寶 * @時間: 2018年10月24日下午3:55:49 * @version V1.0 * @see java.lang.Class * @since JDK{jdk1.7} */ public class SpringWebSocketHandler extends TextWebSocketHandler{ private static final Logger LOGGER = LoggerFactory.getLogger(SpringWebSocketHandler.class); private static final ArrayList<WebSocketSession> users;//這個會出現效能問題,最好用Map來儲存,key用userid static { users = new ArrayList<WebSocketSession>(); } public SpringWebSocketHandler() { // TODO Auto-generated constructor stub } /** * 連線成功時候,會觸發頁面上onopen方法 */ public void afterConnectionEstablished(WebSocketSession session) throws Exception { LOGGER.info("connect to the websocket success......當前數量:"+users.size()); users.add(session); //這塊會實現自己業務,比如,當用戶登入後,會把離線訊息推送給使用者,這裡需要查詢資料庫把待發送的訊息傳送給醫生 TextMessage returnMessage = new TextMessage("你將收到的離線"); session.sendMessage(returnMessage); } /** * 關閉連線時觸發 */ public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception { String username= (String) session.getAttributes().get("WEBSOCKET_EDU_SESSION_USERID"); LOGGER.info("使用者"+username+"已退出!"); users.remove(session); LOGGER.info("剩餘線上使用者"+users.size()); } /** * 給某個使用者傳送訊息 * @param userAndHosId * @param message * @作者: 王彥寶 * @時間: 2018年11月30日下午1:51:32 * @返回 void */ public void sendMessageToUser(String username, TextMessage message) { LOGGER.info("傳送訊息:"+message); for (WebSocketSession user : users) { if (user.getAttributes().get("WEBSOCKET_SESSION_USERID").equals(username)) { try { if (user.isOpen()) { LOGGER.info("傳送訊息:"+message); user.sendMessage(message); } } catch (IOException e) { e.printStackTrace(); } break; } } } /** * 群發所有線上使用者訊息 * * @param userName * @param message */ public void sendMessage(TextMessage message) { LOGGER.info("傳送訊息:"+message); for (WebSocketSession user : users) { try { if (user.isOpen()) { LOGGER.info("傳送訊息:"+message); user.sendMessage(message); } } catch (IOException e) { e.printStackTrace(); } } } /** * js呼叫websocket.send時候,會呼叫該方法 */ @Override protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception { super.handleTextMessage(session, message); /** * 收到訊息,自定義處理機制,實現業務 */ System.out.println("伺服器收到訊息:"+message); if(message.getPayload().startsWith("#anyone#")){ //單發某人 sendMessageToUser((String)session.getAttributes().get("WEBSOCKET_SESSION_USERID"), new TextMessage("伺服器單發:" +message.getPayload())) ; }else if(message.getPayload().startsWith("#everyone#")){ sendMessage(new TextMessage("伺服器群發:" +message.getPayload())); }else{ } } }
SpringWebSocketHandlerInterceptor.java 檔案
import java.util.Map;
import javax.servlet.http.HttpSession;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;
/**
*
* WebSocket攔截器
* @Description: TODO <p>SpringWebSocketHandlerInterceptor.java</p>
* @作者: 王彥寶
* @時間: 2018年10月24日下午3:58:34
* @version V1.0
* @see java.lang.Class
* @since JDK{jdk1.7}
*/
public class SpringWebSocketHandlerInterceptor extends HttpSessionHandshakeInterceptor{
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
Map<String, Object> attributes) throws Exception {
// TODO Auto-generated method stub
System.out.println("Before Handshake");
if (request instanceof ServletServerHttpRequest) {
ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
HttpSession session = servletRequest.getServletRequest().getSession(false);
if (session != null) {
//使用userName區分WebSocketHandler,以便定向傳送訊息
String username = (String) session.getAttribute("SESSION_USERID");
if (username==null) {
username="default-system";
}
attributes.put("WEBSOCKET_SESSION_USERID",username);
}
}
return super.beforeHandshake(request, response, wsHandler, attributes);
}
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
Exception ex) {
// TODO Auto-generated method stub
super.afterHandshake(request, response, wsHandler, ex);
}
}
這就是核心的程式碼了。在spring 的配置檔案中加入 SpringWebSocketConfig 檔案的掃描路徑,然後啟動專案在前端進行訪問
ws://localhost:8081/XXXXX/webSocketIMServer.json"); 這樣訪問就OK 了。
遇到的問題我總結一下:
1、404問題:
在第一次訪問的時候報出了404 的錯誤,找不到路徑,最後發現問題在這裡
registry.addHandler(webSocketHandler(),"/webSocketIMServer.json").addInterceptors(myInterceptor()).setAllowedOrigins("*");
registry.addHandler(webSocketHandler(), "/sockjs/webSocketIMServer.json").withSockJS();
前端訪問的方法必須和後臺寫的這個方法名一致(包括字尾名,如果後臺程式碼沒有後綴,前端訪問時候就不要加字尾,不然就是訪問不到)"/webSocketIMServer.json"。
2、403問題:
上面解決了404 的問題就又報出了403 的問題,然後我就在網上各種搜尋,有各種各樣的方案,都試過了,沒有解決問題,然後搜到了這個
部落格:https://blog.csdn.net/goxidono/article/details/53414897
和我的問題一模一樣,說的挺詳細,spring-webSocket.jar 裡面自動實現了HandshakeInterceptor 介面
public class OriginHandshakeInterceptor implements HandshakeInterceptor {
然後再執行到這個方法的時候,this.interceptors 就會又兩個 interceptors, 第一個是我們自己實現的SpringWebSocketHandlerInterceptor 這個介面實現類,第二個就是 spring-webSocket 自動幫你實現的 OriginHandshakeInterceptor 這個介面實現類,
public boolean applyBeforeHandshake(ServerHttpRequest request, ServerHttpResponse response,
Map<String, Object> attributes) throws Exception {
for (int i = 0; i < this.interceptors.size(); i++) {
HandshakeInterceptor interceptor = this.interceptors.get(i);
if (!interceptor.beforeHandshake(request, response, this.wsHandler, attributes)) {
if (logger.isDebugEnabled()) {
logger.debug(interceptor + " returns false from beforeHandshake - precluding handshake");
}
applyAfterHandshake(request, response, null);
return false;
}
this.interceptorIndex = i;
}
return true;
}
就在這個方法中會被攔截,然後我就改變了寫法,
下面是我剛開始的寫法:
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
//WebIM WebSocket通道
registry.addHandler(webSocketHandler(),"/webSocketIMServer").addInterceptors(myInterceptor());
registry.addHandler(webSocketHandler(), "/sockjs/webSocketIMServer").addInterceptors(myInterceptor()).withSockJS();
}
改變後的寫法:
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
//WebIM WebSocket通道
registry.addHandler(webSocketHandler(),"/webSocketIMServer.json").addInterceptors(myInterceptor()).setAllowedOrigins("*");
registry.addHandler(webSocketHandler(), "/sockjs/webSocketIMServer.json").withSockJS();
}
然後繼續測試,發現還是不行,原因在執行到 applyBeforeHandshake 方法時有兩個攔截器,按照上面的寫法我們自己實現的攔截器時通過了,但是spring-webSocket 實現的攔截器沒有通過,然後又被攔截了報403 ,但是還是沒有解決我的問題。文章裡用的jdk 8 ,tomcat 8 ,和我的問題相似但是環境不同,用不了他的寫法,測試到這裡的時候我用的spirng 的版本 時4.2.4 ,然後我就想想換一個高點的spring 的版本試試看,之後就換了Spirng 4.3.8 的版本,啟動測試,居然OK了。好吧原來時版本的問題 最後的的環境是:
windows 10, myeclipse 2014,orcale11 ,jdk 1.7.0.79 ,spring 4.3.8 springmvc 4.2.4.RELEASE
websocket jar
<dependency>
<groupId>javax.websocket</groupId>
<artifactId>javax.websocket-api</artifactId>
<version>1.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-messaging</artifactId>
<version>4.2.4.RELEASE</version>
</dependency>
這樣就可以了。在此做一個記錄。