Spring websocket瀏覽器連線時出現404錯誤

Gin.p發表於2017-02-12

1、場景

在用websocket做一個簡單的資料匯入頁面同步顯示後臺進度功能的時候,瀏覽器出現連線不上的錯誤:
WebSocket connection to 'ws://localhost:8080/project/marco' failed: Error during WebSocket handshake: Unexpected response code: 404

網上有很種導致404的情況,但是都沒有用,我在這裡記錄一下我的情況。

2、解決過程

spring-websocket.xml的配置:

<websocket:handlers>
<websocket:mapping handler="messageHandler" path="/marco" />
<websocket:handshake-interceptors>
  <bean class="com.project.websocket.HandshakeInterceptor"/>
</websocket:handshake-interceptors>
</websocket:handlers>

<bean id="messageHandler" class="com.project.websocket.MessageHandler"/>

檢視列印的INFO級別日誌,根據spring的啟動資訊我們可以看到:

2017-02-09 21:57:40,559 INFO  [springframework.web.servlet.handler.SimpleUrlHandlerMapping] - Mapped URL path [/marco] onto handler of type [class org.springframework.web.socket.server.support.WebSocketHttpRequestHandler]

WebSocketHttpRequestHandler通過SimpleUrlHandlerMapping註冊了/marco地址,那應該是沒錯的啊,難道是客戶端js的錯誤?

var websocket = new WebSocket('ws://' + window.location.host + '/project/marco');

地址好像也沒錯啊,和Spring in Action4裡面的例子一樣的啊。
為什麼地址沒有對映到/marco?先看看DispatchServlet的handlerMappings有沒有/marco先吧。

斷點DispatchServlet的doDispatch()方法,隨便發起一個請求:

protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
    ......
    // Determine handler for the current request.
    mappedHandler = getHandler(processedRequest);
    if (mappedHandler == null || mappedHandler.getHandler() == null) {
        noHandlerFound(processedRequest, response);
        return;
    }
    ......
}

進入getHandler(processedRequest)

protected HandlerExecutionChain getHandler(HttpServletRequest request) throws Exception {
    for (HandlerMapping hm : this.handlerMappings) {
        if (logger.isTraceEnabled()) {
            logger.trace(
                    "Testing handler map [" + hm + "] in DispatcherServlet with name '" + getServletName() + "'");
        }
        HandlerExecutionChain handler = hm.getHandler(request);
        if (handler != null) {
            return handler;
        }
    }
    return null;
}

我們看一下handlerMappings裡面的內容,不知道你們有沒有找到/marco,反正我是沒找到。
言下之意,/marco沒註冊到?

沒辦法,我們斷點SimpleUrlHandlerMapping的registerHandlers()方法和DispatchServlet的initHandlerMappings()方法。

我們看到SimpleUrlHandlerMapping的registerHandlers()先被執行。

最後退出registerHandlers()方法,F6一路下來,從WebApplicationObjectSupport的initApplicationContext()方法中可以看到:

  • context.beanFactory.beanDefintionMap中包含[org.springframework.web.servlet.handler.SimpleUrlHandlerMapping#0->RootBeanDefintion]
  • 而且context.beanFactory.singletonObjects也包含了註冊了/marco的SimpleUrlHandlerMapping例項。
  • 此時的context.configLocations是classpath:spring-context.xml

DispatchServlet的initHandlerMappings()後被執行,我們來看這段:

// Find all HandlerMappings in the ApplicationContext, including ancestor contexts.
Map<String, HandlerMapping> matchingBeans =
    BeanFactoryUtils.beansOfTypeIncludingAncestors(context, HandlerMapping.class, true, false);

然而,在matchingBeans沒有找到我們要的註冊了/marco的SimpleUrlHandlerMapping。此時的context.configLocations是classpath*:spring-servlet.xml

來到這裡,你大概知道就是你的spring-websocket.xml放錯位置啦。

3、解決辦法

如果你的web.xml分開載入spring-context和spring-servlet的話,請將spring-websocket的資原始檔放到spring-servlet上載入。
注意:在spring-context載入的時候,你的messageHandler應該是還沒有初始化,請不要注入到某個service上。

相關文章