`

Websocket出现的错误

 
阅读更多

前端使用sockjs,后台使用spring的websocket框架

结果在一个网络较慢的地方,发现tomcat报错信息:

复制代码
Oct 28, 2015 10:10:43 AM org.apache.catalina.core.StandardWrapperValve invoke
SEVERE: Servlet.service() for servlet [mvc-dispatcher] in context with path [/rscc] threw exception [Request processing failed; nested exception is org.springframework.web.socket.sockjs.SockJsException: Uncaught failure in SockJS request, uri=http://xxx/user/854/qckzogtf/xhr_streaming; nested exception is org.springframework.web.socket.sockjs.SockJsException: Uncaught failure for request http://xxx/user/854/qckzogtf/xhr_streaming; nested exception is java.lang.IllegalArgumentException: Async support must be enabled on a servlet and for all filters involved in async request processing. This is done in Java code using the Servlet API or by adding "<async-supported>true</async-supported>" to servlet and filter declarations in web.xml. Also you must use a Servlet 3.0+ container] with root cause
java.lang.IllegalArgumentException: Async support must be enabled on a servlet and for all filters involved in async request processing. This is done in Java code using the Servlet API or by adding "<async-supported>true</async-supported>" to servlet and filter declarations in web.xml. Also you must use a Servlet 3.0+ container
    at org.springframework.util.Assert.isTrue(Assert.java:65)
    at org.springframework.http.server.ServletServerHttpAsyncRequestControl.<init>(ServletServerHttpAsyncRequestControl.java:59)
    at org.springframework.http.server.ServletServerHttpRequest.getAsyncRequestControl(ServletServerHttpRequest.java:202)
    at org.springframework.web.socket.sockjs.transport.session.AbstractHttpSockJsSession.initRequest(AbstractHttpSockJsSession.java:238)
    at org.springframework.web.socket.sockjs.transport.session.AbstractHttpSockJsSession.handleInitialRequest(AbstractHttpSockJsSession.java:203)
    at org.springframework.web.socket.sockjs.transport.session.StreamingSockJsSession.handleInitialRequest(StreamingSockJsSession.java:54)
    at org.springframework.web.socket.sockjs.transport.handler.AbstractHttpSendingTransportHandler.handleRequestInternal(AbstractHttpSendingTransportHandler.java:66)
    at org.springframework.web.socket.sockjs.transport.handler.AbstractHttpSendingTransportHandler.handleRequest(AbstractHttpSendingTransportHandler.java:58)
    at org.springframework.web.socket.sockjs.transport.TransportHandlingSockJsService.handleTransportRequest(TransportHandlingSockJsService.java:254)
    at org.springframework.web.socket.sockjs.support.AbstractSockJsService.handleRequest(AbstractSockJsService.java:317)
    at org.springframework.web.socket.sockjs.support.SockJsHttpRequestHandler.handleRequest(SockJsHttpRequestHandler.java:88)
    at org.springframework.web.servlet.mvc.HttpRequestHandlerAdapter.handle(HttpRequestHandlerAdapter.java:51)
    at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:938)
    at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:870)
    at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:961)
    at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:863)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:646)
    at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:837)
    at javax.servlet.http.HttpServlet.service(HttpServlet.java:727)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:303)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
    at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:88)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:108)
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:220)
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:122)
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:501)
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:171)
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:102)
    at org.apache.catalina.valves.AccessLogValve.invoke(AccessLogValve.java:950)
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:116)
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:408)
    at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1040)
    at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:607)
    at org.apache.tomcat.util.net.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:316)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
    at java.lang.Thread.run(Thread.java:745)
复制代码

 根据报错信息来看,应该是缺少了<async-supported>true</async-supported>这个配置,这个是3.0开始支持的,async的请求需要开启async-supported。

但是该项目在我们本地从来没有出现过这个问题,情况说明及解决分析过程如下:

 

一、前端连接情况:ws = new SockJS( 'url', undefined, {});

  不加参数,sockjs默认会选择最优方式来连接,情况如下:

  

  关于协议选择:在第一种连接方式超时时,sockjs会选择次优方式进行连接:

复制代码
var _all_protocols = ['websocket',
                      'xdr-streaming',
                      'xhr-streaming',
                      'iframe-eventsource',
                      'iframe-htmlfile',
                      'xdr-polling',
                      'xhr-polling',
                      'iframe-xhr-polling',
                      'jsonp-polling'];
所有协议(连接方式)
复制代码

  检测所有可用协议并按优先级排序

复制代码
utils.probeProtocols = function() {
    var probed = {};
    for(var i=0; i<_all_protocols.length; i++) {
        var protocol = _all_protocols[i];
        // User can have a typo in protocol name.
        probed[protocol] = SockJS[protocol] &&
                           SockJS[protocol].enabled();
    }
    return probed;
};

utils.detectProtocols = function(probed, protocols_whitelist, info) {
    var pe = {},
        protocols = [];
    if (!protocols_whitelist) protocols_whitelist = _all_protocols;
    for(var i=0; i<protocols_whitelist.length; i++) {
        var protocol = protocols_whitelist[i];
        pe[protocol] = probed[protocol];
    }
    var maybe_push = function(protos) {
        var proto = protos.shift();
        if (pe[proto]) {
            protocols.push(proto);
        } else {
            if (protos.length > 0) {
                maybe_push(protos);
            }
        }
    }

    // 1. Websocket
    if (info.websocket !== false) {
        maybe_push(['websocket']);
    }

    // 2. Streaming
    if (pe['xhr-streaming'] && !info.null_origin) {
        protocols.push('xhr-streaming');
    } else {
        if (pe['xdr-streaming'] && !info.cookie_needed && !info.null_origin) {
            protocols.push('xdr-streaming');
        } else {
            maybe_push(['iframe-eventsource',
                        'iframe-htmlfile']);
        }
    }

    // 3. Polling
    if (pe['xhr-polling'] && !info.null_origin) {
        protocols.push('xhr-polling');
    } else {
        if (pe['xdr-polling'] && !info.cookie_needed && !info.null_origin) {
            protocols.push('xdr-polling');
        } else {
            maybe_push(['iframe-xhr-polling',
                        'jsonp-polling']);
        }
    }
    return protocols;
}
复制代码

  连接时若超时切换协议的代码

复制代码
while(1) {
        var protocol = that.protocol = that._protocols.shift();
        if (!protocol) {
            return false;
        }
        // Some protocols require access to `body`, what if were in
        // the `head`?
        if (SockJS[protocol] &&
            SockJS[protocol].need_body === true &&
            (!_document.body ||
             (typeof _document.readyState !== 'undefined'
              && _document.readyState !== 'complete'))) {
            that._protocols.unshift(protocol);
            that.protocol = 'waiting-for-load';
            utils.attachEvent('load', function(){
                that._try_next_protocol();
            });
            return true;
        }
//下面的to = 就是计算连接超时时间的,调用delay,在连接超时时关闭这个协议的连接。
        if (!SockJS[protocol] ||
              !SockJS[protocol].enabled(that._options)) {
            that._debug('Skipping transport:', protocol);
        } else {
            var roundTrips = SockJS[protocol].roundTrips || 1;
            var to = ((that._options.rto || 0) * roundTrips) || 5000;
            that._transport_tref = utils.delay(to, function() {
                if (that.readyState === SockJS.CONNECTING) {
                    // I can't understand how it is possible to run
                    // this timer, when the state is CLOSED, but
                    // apparently in IE everythin is possible.
                    that._didClose(2007, "Transport timeouted");
                }
            });

            var connid = utils.random_string(8);
            var trans_url = that._base_url + '/' + that._server + '/' + connid;
            that._debug('Opening transport:', protocol, ' url:'+trans_url,
                        ' RTO:'+that._options.rto);
            that._transport = new SockJS[protocol](that, trans_url,
                                                   that._base_url);
            return true;
        }
    }
复制代码

   观察上面报错信息中的uri:uri=http://xxx/user/854/qckzogtf/xhr_streaming; 因为正常情况下连接为websocket连接,这个uri应该为ws=http://xxx/user/854/qckzogtf/websocket,由此可见,这里应该是切换为xhr_streaming协议了

  前面也说过,他们网络环境较慢,所以才想,应该是连接超时导致的,有上面可知,to为超时时间,计算公式为

   var to = ((that._options.rto || 0) * roundTrips) || 5000;

  关键在于that._options.rto,这个是new的时候的设置项,所以可以设置rto超时长一点。

  结果发现设置中只有

  that._options = {devel: false, debug: false, protocols_whitelist: [],info: undefined, rtt: undefined};

  这几项,并没有rto,于是继续看

复制代码
var SockJS = function(url, dep_protocols_whitelist, options) {
    if (this === _window) {
        // makes `new` optional
        return new SockJS(url, dep_protocols_whitelist, options);
    }
    
    var that = this, protocols_whitelist;
    that._options = {devel: false, debug: false, protocols_whitelist: [],
                     info: undefined, rtt: undefined};
    if (options) {
        utils.objectExtend(that._options, options);
    }
    that._base_url = utils.amendUrl(url);
    that._server = that._options.server || utils.random_number_string(1000);
    if (that._options.protocols_whitelist &&
        that._options.protocols_whitelist.length) {
        protocols_whitelist = that._options.protocols_whitelist;
    } else {
        // Deprecated API
        if (typeof dep_protocols_whitelist === 'string' &&
            dep_protocols_whitelist.length > 0) {
            protocols_whitelist = [dep_protocols_whitelist];
        } else if (utils.isArray(dep_protocols_whitelist)) {
            protocols_whitelist = dep_protocols_whitelist
        } else {
            protocols_whitelist = null;
        }
        if (protocols_whitelist) {
            that._debug('Deprecated API: Use "protocols_whitelist" option ' +
                        'instead of supplying protocol list as a second ' +
                        'parameter to SockJS constructor.');
        }
    }
    that._protocols = [];
    that.protocol = null;
    that.readyState = SockJS.CONNECTING;
    that._ir = createInfoReceiver(that._base_url);
    that._ir.onfinish = function(info, rtt) {
        that._ir = null;
        if (info) {
            if (that._options.info) {
                // Override if user supplies the option
                info = utils.objectExtend(info, that._options.info);
            }
            if (that._options.rtt) {
                rtt = that._options.rtt;
            }
            that._applyInfo(info, rtt, protocols_whitelist);
            that._didClose();
        } else {
            that._didClose(1002, 'Can\'t connect to server', true);
        }
    };
};
复制代码

utils.objectExtend(that._options, options);这一句比较可疑是转移属性的,

that._applyInfo(info, rtt, protocols_whitelist);比较可疑,看一下

复制代码
SockJS.prototype._applyInfo = function(info, rtt, protocols_whitelist) {
    var that = this;
    that._options.info = info;
    that._options.rtt = rtt;
    that._options.rto = utils.countRTO(rtt);
    that._options.info.null_origin = !_document.domain;
    var probed = utils.probeProtocols();
    that._protocols = utils.detectProtocols(probed, protocols_whitelist, info);
};
复制代码

rto设置找到了!that._options.rto = utils.countRTO(rtt);

复制代码
utils.countRTO = function (rtt) {
    var rto;
    if (rtt > 100) {
        rto = 3 * rtt; // rto > 300msec
    } else {
        rto = rtt + 200; // 200msec < rto <= 300msec
    }
    return rto;
}
复制代码

好了,只用设置rtt既可以设置rto了。。。就这样,连接超时的问题算是解决了。

 

二、但是还没完,就算websocket连接超时导致协议切换为xhr_streaming,也不会导致后台报错的情况出现,下面解决这个问题:

报错处的代码:在AbstractHttpSockJsSession类中:

复制代码
private void initRequest(ServerHttpRequest request, ServerHttpResponse response,
            SockJsFrameFormat frameFormat) {

        Assert.notNull(request, "Request must not be null");
        Assert.notNull(response, "Response must not be null");
        Assert.notNull(frameFormat, "SockJsFrameFormat must not be null");

        this.response = response;
        this.frameFormat = frameFormat;
        this.asyncRequestControl = request.getAsyncRequestControl(response);
    }
复制代码

最后一句报错咯,看代码:

复制代码
public ServerHttpAsyncRequestControl getAsyncRequestControl(ServerHttpResponse response) {
        if (this.asyncRequestControl == null) {
            Assert.isInstanceOf(ServletServerHttpResponse.class, response);
            ServletServerHttpResponse servletServerResponse = (ServletServerHttpResponse) response;
            this.asyncRequestControl = new ServletServerHttpAsyncRequestControl(this, servletServerResponse);//这一句报错了
        }
        return this.asyncRequestControl;
    }
复制代码

new ServletServerHttpAsyncRequestControl时报错

复制代码
public ServletServerHttpAsyncRequestControl(ServletServerHttpRequest request, ServletServerHttpResponse response) {

        Assert.notNull(request, "request is required");
        Assert.notNull(response, "response is required");

        Assert.isTrue(request.getServletRequest().isAsyncSupported(),
                "Async support must be enabled on a servlet and for all filters involved " +
                "in async request processing. This is done in Java code using the Servlet API " +
                "or by adding \"<async-supported>true</async-supported>\" to servlet and " +
                "filter declarations in web.xml. Also you must use a Servlet 3.0+ container");

        this.request = request;
        this.response = response;
    }
复制代码

  最长那一句断言报的错,说的是web.xml的servlet和filter中要加入<async-supported>true</async-supported>

  看下web.xml中,filter中确实没有这一句,之所以一定要在filter中加入这一句,是因为websocket的切换协议请求,是通过filter拦截的,如果不在filter中配置async,则切换协议的请求将不是async的,所以上面就报错了。

  filter中加上之后,就好了。

  若为了让controller也支持async则需要在dispatcher中这样配置

<mvc:annotation-driven>
<!--  可不设置,使用默认的超时时间 -->
    <mvc:async-support default-timeout="3000"/>
</mvc:annotation-driven>

 

  再返回上面initRequest方法,只有该类AbstractHttpSockJsSession的public void handleInitialRequest(ServerHttpRequest request, ServerHttpResponse response,SockJsFrameFormat frameFormat)方法与

public void handleSuccessiveRequest(ServerHttpRequest request,ServerHttpResponse response, SockJsFrameFormat frameFormat)中有调用,而调用这两个方法的只有AbstractHttpSockJsSession的实现类:StreamingSockJsSession才有,就是spring-websocket为sockjs支持的xhr-streaming方式的实现类,而平时使用websocket的则是另外一个实现类:

这就导致了之前一直没有报错,上了一个网络较差的地方就报错了,原因分析完毕。

 

上面用的sockjs 0.3.4,最新的是1.0.3有所改变,但是大致相同

补充:相同个毛线,1.0.3的rtt不再是通过参数传进去的,而是计算出来的

复制代码
function InfoAjax(url, AjaxObject) {
  EventEmitter.call(this);

  var self = this;
  var t0 = +new Date();
  this.xo = new AjaxObject('GET', url);

  this.xo.once('finish', function(status, text) {
    var info, rtt;
    if (status === 200) {
      rtt = (+new Date()) - t0;
      if (text) {
        try {
          info = JSON3.parse(text);
        } catch (e) {
          debug('bad json', text);
        }
      }

      if (!objectUtils.isObject(info)) {
        info = {};
      }
    }
    self.emit('finish', info, rtt);
    self.removeAllListeners();
  });
}
复制代码

也就是说,超时时间控制不了的...什么鬼

this._transportsWhitelist = options.transports;这个是设置白名单,优先使用这些方式。

分享到:
评论
1 楼 Araxis 2017-03-13  
也遇到了楼主的问题,我用的sockjs.0.3.4,升级版本到1.1.2后问题消失。谢谢分享经历。

相关推荐

    小程序websocket接入

    5. `wx.onSocketError()`: WebSocket出现错误时的回调函数,用于捕获错误信息。 6. `wx.closeSocket()`: 关闭WebSocket连接。 三、小程序WebSocket接入流程 1. 初始化:调用`wx.connectSocket()`,设置WebSocket...

    html5的websocket代码示例包括错误解决方案

    - 在WebSocket的事件处理程序中添加适当的错误处理代码,以便在出现问题时给出反馈或进行恢复操作。 在"不是一个人在战斗"这个文件中,可能包含了实现WebSocket通信的示例代码,包括错误处理的案例,可以帮助开发者...

    WebSocketDemo

    WebSocket是一种在客户端和服务器之间建立持久连接的协议,它允许双方进行全双工通信,即数据可以在两个方向上同时传输,极大地提高了Web应用的实时性。WebSocketDemo是一个用于教学目的的示例项目,包含了服务器端...

    C语言实现的websocket

    WebSocket是一种在客户端和服务器之间建立持久连接的网络通信协议,它允许双向通信,即服务器和客户端都可以主动发送数据。在Web开发中,WebSocket为实时应用提供了高效、低延迟的解决方案,比如在线聊天、股票交易...

    WebSocket消息实时提醒

    6. **错误处理**:在开发过程中,需要处理各种可能出现的错误,如连接失败、数据解析错误、网络中断等,并提供适当的反馈和重试机制。 7. **负载均衡与集群**:对于大型实时应用,可能需要考虑WebSocket的负载均衡...

    websocket断线重连 websocket JS框架

    描述中提到的"websocket的JS框架"可能是为简化WebSocket编程而设计的库,这类框架通常会提供更高级别的抽象,包括自动重连、错误处理、消息编码解码等功能。使用这样的框架可以使开发更加便捷,减少重复工作。 例如...

    MFC websocket server | MFC websocket服务器

    WebSocket是一种在客户端和服务器之间建立持久连接的网络协议,它允许双方进行全双工通信,即数据可以在两个方向上同时传输。MFC(Microsoft Foundation Classes)是微软提供的一套C++类库,用于构建Windows应用程序...

    websocket-sharp 范例

    此外,它提供了一些高级特性,如心跳检测、错误处理和SSL/TLS加密,以增强安全性。 在WebSocketTest项目中,可能包含了使用WebSocketSharp实现的一些示例代码,如简单的聊天应用、数据推送服务等。通过分析和运行...

    websocket_assistant.rar_MFC websocket_mfc+websocket_websocket_w

    4. **错误处理**:捕获和处理可能出现的连接错误,如网络中断、超时或服务器关闭。 5. **关闭连接**:当不再需要WebSocket连接时,确保正确关闭连接,释放相关资源。 6. **事件驱动编程**:在MFC中,通常会利用...

    websocket测试工具,网络助手

    6. **错误检测**:当服务器端或客户端出现错误时,工具能够及时给出提示,帮助开发者快速定位问题。 7. **日志记录**:记录所有发送和接收的数据,便于后期分析和问题排查。 在使用"WebSocketMan-v1.0.9-win32"时,...

    MFC实现WebSocket通信

    例如,可以使用MFC的消息映射机制,处理WebSocket连接的打开、关闭、错误等事件。 6. **用户界面交互**:将WebSocket通信与MFC对话框或其他UI元素结合。例如,通过按钮触发连接操作,显示接收到的信息,或者在...

    简单实现了websocket功能:websocket客户端、winformsocket客户端

    6. **错误处理**:在实现中,要考虑到网络中断、服务器崩溃等情况,确保错误处理机制健全,能及时关闭和重新建立连接。 7. **性能优化**:对于大量并发连接的场景,服务器需要有效地管理内存和线程,避免资源耗尽。...

    C++ 实现WebSocket 服务器

    6. **错误处理**:处理各种可能出现的异常情况,如网络中断、协议错误等。 7. **资源释放**:当连接关闭时,确保释放所有相关资源,如关闭TCP套接字,清理内存等。 ### 总结 通过C++和libuv库,我们可以构建一个...

    html页面测试websocket

    在`site.js`中,开发者会实例化一个WebSocket对象,指定服务器URL,然后监听连接的打开、关闭、错误和消息事件。例如,`WebSocket.onopen`事件处理函数通常用于显示连接成功的提示,`WebSocket.onmessage`则用于接收...

    unty websocket 客户端 服务端 通信

    ws.OnError += (sender, e) =&gt; { Debug.LogError("WebSocket错误:" + e.Message); }; ``` 3. 连接与断开:调用`Connect()`方法启动连接,使用`Close()`方法关闭连接。 ```csharp ws.Connect(); // 通信完成后 ws....

    C# WinForm客户端连接 WebSocket

    在实际应用中,可能还需要考虑错误处理、心跳机制、线程安全等因素,以确保客户端的稳定性和可靠性。 总结,通过结合C# WinForm的强大UI功能和WebSocket的实时通信能力,我们可以创建出高效的桌面应用。在VS2019中...

    WebSocket_jar包

    - **协议处理逻辑**:封装了WebSocket帧的编码和解码,以及错误处理等细节。 - **事件驱动模型**:通过监听器接口,开发者可以方便地处理连接建立、关闭、数据接收等各种事件。 使用WebSocket_jar包进行开发时,...

    websocket++库

    7. **协议实现**:WebSocket++库不仅实现了WebSocket的基本握手和数据帧传输,还处理了协议级别的错误检测和恢复,如关闭帧的解析和错误处理。 8. **示例代码**:库附带了多个示例程序,涵盖了从简单的客户端到复杂...

    vue websocket 与 delphi10.2 websocket通讯

    为了提高应用程序的健壮性,需要在Vue组件中处理WebSocket连接可能出现的错误,并实现断线重连机制。在Delphi服务器端,也需要处理客户端断开连接的情况,可能需要设置超时或者重试策略。 8. **示例代码片段**: ...

    websocket_websocket客户端_websocket_

    例如,`onopen`事件表示连接已建立,`onmessage`事件处理接收到的数据,`onerror`处理错误,`onclose`则在连接关闭时触发。 5. **IP地址修改**:在WebSocket客户端代码中,通常需要指定服务器的IP地址和端口号。...

Global site tag (gtag.js) - Google Analytics