前端使用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;这个是设置白名单,优先使用这些方式。
相关推荐
html5的websocket代码示例包括错误解决方案
完整的WebSocketDemo,适合新人下载来学习。包括服务器端和前端程序。稍作更改即可实现基于web的实时通信。
主要为大家详细介绍了Spring WebSocket 404错误的解决方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
1、断开原因 WebSocket断开的原因有很多,最好在WebSocket断开时,将错误打印出来。 ws.onclose = function (e) { console.log('websocket 断开: ' + e.code + ' ' + e.reason + ' ' + e.wasClean) console....
spring boot demo,整合netty5实现高并发websocket,并引入slf4g+lombok,采用maven形式; 直接导入运行,有测试页面也有实现代码及详细注释,src/main/webapps/TestNettyWebSocket.html里第十行改成 ws://localhost...
使用微信小程序开发工具,直接连接是会报域名安全错误的,因为工具内部做了限制,对安全域名才会允许连接。所以同样的,这里我们也继续改下工具的源码,把相关的行改掉就行修改方式如下: 找到asdebug.js的这一行,...
这是一个基于 Gin 框架和 WebSocket 实现的简单聊天室应用程序。它使用了 MySQL 数据库保存用户信息和聊天记录,并使用 Gorilla WebSocket ...* 错误处理和日志记录 * 安全防范措施,如防止跨站脚本攻击和 SQL 注入攻击
完美解决C/S和B/S的通信间问题,可是直接C/S和B/S实时通讯
主要介绍了Spring集成webSocket页面访问404问题的解决方法,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
此次主要了解,整合的是spring websocket。在最开始的时候,碰到挺多问题的,不是连接错误就是一直连接不成功。整的我的小心脏都受不了,蛋疼。百度,谷歌一大堆demo,整到工程里能跑起来,但是就是连接不成功,放弃...
它不包括诸如侦听器和隐式错误处理之类的便利操作。 文献资料 正在安装 首选的安装方式是 。 composer require textalk/websocket 当前版本支持PHP版本^7.2|8.0 。 对于PHP 7.1支持,请使用1.4版。 对于PHP ^...
主要给大家介绍了关于WebSocket部署到服务器出现连接失败问题的分析与解决方法,文中给出了详细的介绍供大家参考学习,文末也给出了demo下载地址,需要的朋友们可以下载学习,下面随着小编来一起学习学习吧。
提供了一种获取WebSocket的基础原始套接字进行配置的方法, 提供了一种用于的方法, 提供一个利用javax.net.SocketFactory接口的工厂类, 提供丰富的侦听器接口来挂接WebSocket事件, 具有细粒度的错误代码,可精确...
添加Error.toJSON(),以便我们可以轻松地对错误进行字符串化,例如,通过websocket发送错误时 // just do it before anything else require('error-tojson'); 该软件包与MongoDB本机驱动程序不兼容。 每当mongo...
websocketd是一个小型命令行工具,它将包装现有的命令行界面程序,并允许通过WebSocket访问它。
本例中还f缺少服务端对socket连接的认证和socket的管理控制等功能,还缺少对消息的分类发送能力,客户端也缺少对消息的筛选功能,感兴趣的同学可以自行拓展进行二次开发,如例子中有错误欢迎大家批评指正!
weex调试时需要使用websock进行链接,但是官网websocket文件有错误。这个是自适应okhttp3.8.1的版本
同时还需要设置 autoAcceptConnections ,对客服端发送的数据进行一个监控,以及关闭连接的监控,此外还可以进行其他的监控像错误,以及控制台的输入等操作。 原文章地址:...
打开与WebSocket的多个连接-保存通常用于加速开发的有效负载-多个选项卡可让您快速发送不同的有效负载-管理和切换从一个界面在多个项目之间切换-断开时自动重新连接到服务器的选项,以加快开发速度请求新功能或提交...
前端错误、性能、安全日志收集系统,涉及到nodejs、mongodb、websocket等等内容.zip