1. 程式人生 > >Spring WebSocket 404 解決方案

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[] {"/"};
    }

}

重新部署後代開瀏覽器執行成功

客戶端訊息
這裡寫圖片描述

伺服器訊息
這裡寫圖片描述