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;这个是设置白名单,优先使用这些方式。
Websocket出现的错误的更多相关文章
- 解决nginx转发websocket报400错误
解决nginx转发websocket报400错误 说明 由于个人服务器上面有多个项目,配置了二级域名,需要对二级域名进行转发,在转发工作这快采取了大名鼎鼎的nginx.在这之前所有的项目运行转发都没问 ...
- websocket通信1009错误,
问题说明: springboot继承 WebSocketConfigurer实现websocket通信服务,服务器端报错,"The decoded text message was too ...
- Spring WebSocket中403错误解决
最近测试了一下spring的websocket,遇到了一个比较恶心的问题,在这记录一下. 问题源自之前开发的一个h5项目,这个项目在80端口下一直放着,就顺便在里面随便加了几行代码测试websocke ...
- websocket报400错误
解决方案看了下讨论区说的方案,问题出现在nginx的配置文件,需要修改nginx.conf文件.在linux终端中敲入vim /etc/nginx/nginx.conf,找到location这个位置, ...
- webSocket错误收集
关于 使用WebSocket报如下错误, Uncaught InvalidStateError: Failed to execute 'send' on 'WebSocket': already in ...
- java使用websocket,并且获取HttpSession,源码分析
转载请在页首注明作者与出处 http://www.cnblogs.com/zhuxiaojie/p/6238826.html 一:本文使用范围 此文不仅仅局限于spring boot,普通的sprin ...
- HTML5学习总结-08 WebSocket 服务器推送
一 WebSocket 随着互联网的发展,传统的HTTP协议已经很难满足Web应用日益复杂的需求了.近年来,随着HTML5的诞生,WebSocket协议被提出,它实现了浏览器与服务器的全双工通信,扩展 ...
- Java后端WebSocket的Tomcat实现
转自:http://blog.chenzuhuang.com/archive/28.html 文章摘要随着互联网的发展,传统的HTTP协议已经很难满足Web应用日益复杂的需求了.近年来,随着HTML5 ...
- WebSocket简单介绍
Java后端WebSocket的Tomcat实现 一.WebSocket简单介绍 随着互联网的发展,传统的HTTP协议已经很难满足Web应用日益复杂的需求了.近年来,随着HTML5的诞生,WebSoc ...
随机推荐
- css用法大全
direction:控制文本方向 ltr:默认.文本方向从左到右. rtl:文本方向从右到左. inherit:规定应该从父元素继承 direction 属性的值. <select name=& ...
- Fedora安装vim失败解决方法
今天安装在fedora上安装vim的时候,出现如下错误 ================================================================= Downlo ...
- [maven] 实战笔记 - maven 安装配置
1.下载地址http://maven.apache.org/download.html 2.windows下安装maven(1)下载 apache-maven-3.0-bin.zip 解压到任意目录下 ...
- Jquery 插件PrintArea 打印指定的网页区域
Jquery 插件PrintArea 打印指定的网页区域 需要下载jquery 和printarea.js插件 PrintArea.Js插件,可以打印整个网页中某个指定的区域. $("打印区 ...
- 新建网站与新建Asp.Net Web 应用程序的区别
.net网站和应用程序区别,网站是动态执行的不用编译,他只依赖于自己的文档本身,甚至你用aspx里直接写jsp代码都可以,其实网站可以说只是在.net平台中打开的文档,相当于最初的记事本编码,他并不需 ...
- java高并发总结-常用于面试复习
定义: 独占锁是一种悲观保守的加锁策略,它避免了读/读冲突,如果某个只读线程获取锁,则其他读线程都只能等待,这种情况下就限制了不必要的并发性,因为读操作并不会影响数据的一致性. 共享锁则是一种乐观锁, ...
- linux下mysql的安装配置
http://blog.csdn.net/xiagege3/article/details/41852895 (实战用的此文,要求mysql源码必须是未编译的,需依赖cmake编译) http:/ ...
- 「小程序JAVA实战」小程序首页视频(49)
转自:https://idig8.com/2018/09/21/xiaochengxujavashizhanxiaochengxushouyeshipin48/ 视频显示的内容是视频的截图,用户的头像 ...
- Spring security 3.1 +JSF 2.0 . problem with annotating methods in ManagedBeans?
Hy .What i am trying to do is to integrate Spring security with a Jsf+spring IOC +hibernate applicat ...
- post get 区别
GET 请求能被缓存,POST不能: POST 相对安全,GET的请求都包含在URL里 POST 可以通过request body 来传输较多的数据 URL有长度限制,会影响到GET请求,这个长度是由 ...