Spring WebSocket 404 解決方案
近來學習 Spring WebSocket 時按照 Spring IN ACTION 中示例編寫程式碼,執行時瀏覽器報 404 錯誤
WebSocket connection to 'ws://localhost/websocket/marco' failed: Error during WebSocket handshake: Unexpected response code: 404
按照 Spring IN ACTION 中步驟:
首先,繼承 AbstractWebSocketHandler
,過載以下 3 個方法:
- handleTextMessage
– 處理文字型別訊息
- afterConnectionEstablished
-
afterConnectionClosed
– 連線關閉後呼叫
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.AbstractWebSocketHandler;
public class MarcoHandler extends AbstractWebSocketHandler {
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
System.out.println("Received message: " + message.getPayload());
Thread.sleep(2000);
session.sendMessage(new TextMessage("Polo!"));
}
@Override
public void afterConnectionEstablished(WebSocketSession session) {
System.out.println("Connection established!");
}
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
System.out.println("Connection closed. Status: " + status);
}
}
其次,使用 JavaConfig 啟用 WebSocket 並對映訊息處理器
import org.springframework.context.annotation.Bean;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(marcoHandler(), "/marco");
}
@Bean
public MarcoHandler marcoHandler() {
return new MarcoHandler();
}
}
最後,編寫前端 JS 程式碼發起連線請求及後續訊息互動
var url = 'ws://' + window.location.host + '/websocket/marco';
var sock = new WebSocket(url);
sock.onopen = function() {
console.log('Opening');
sock.send('Marco!');
};
sock.onmessage = function(e) {
console.log('Received Message: ', e.data);
setTimeout(function() {
sayMarco()
}, 2000);
};
sock.onclose = function() {
console.log('Closing');
};
function sayMarco() {
console.log('Sending Marco!');
sock.send('Marco!');
}
部署後開啟瀏覽器執行,直接報 404 錯誤
在此自己也做個記錄避免以後遺忘。
WebSocket 實質上借用 HTTP 請求進行握手,啟用 Spring WebSocket 需要在 org.springframework.web.servlet.DispatcherServlet
裡配置攔截此請求。
以下是解決步驟:
首先,修改 WebSocketConfig
類定義,在類上新增 @Configuration
註解,表明該類以 JavaConfig 形式用作 bean 定義的源(相當於 XML 配置中的 <beans>
元素)。
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(marcoHandler(), "/marco");
}
@Bean
public MarcoHandler marcoHandler() {
return new MarcoHandler();
}
}
其次,使用 JavaConfig 配置 DispatcherServlet
,繼承 org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer
,過載以下 3 個方法:
- getRootConfigClasses
– 返回帶有 @Configuration
註解的類將會用來配置 ContextLoaderListener 建立的應用上下文中的 bean
- getServletConfigClasses
– 返回帶有 @Configuration
註解的類將會用來定義 DispatcherServlet
應用上下文中的 bean
- getServletMappings
– 將一個或多個路徑對映到 DispatcherServlet
上
實際上,如果只需要 Spring WebSocket 生效,則只需要在 getServletConfigClasses
方法中返回用來定義 DispatcherServlet
應用上下文中的 bean
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
public class WebSocketInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
@Override
protected Class<?>[] getRootConfigClasses() {
return null;
}
@Override
protected Class<?>[] getServletConfigClasses() {
return new Class<?>[] {WebSocketConfig.class};
}
@Override
protected String[] getServletMappings() {
return new String[] {"/"};
}
}
重新部署後代開瀏覽器執行成功
客戶端訊息
伺服器訊息