Websocket出现的错误
前端使⽤sockjs,后台使⽤spring的websocket框架
结果在⼀个⽹络较慢的地⽅,发现tomcat报错信息:
Oct 28, 2015 10:10:43 AM org.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, u 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>" t at org.springframework.util.Assert.isTrue(Assert.java:65)
at org.springframework.http.server.ServletServerHttpAsyncRequestControl.<init>(ServletServerHttpAsyncRequestControl.java:59)
at org.springframework.http.AsyncRequestControl(ServletServerHttpRequest.java:202)
at org.springframework.web.ansport.session.AbstractHttpSockJsSession.initRequest(AbstractHttpSockJsSession.java:238)
at org.springframework.web.ansport.session.AbstractHttpSockJsSession.handleInitialRequest(AbstractHttpSockJsSession.java:203)
at org.springframework.web.ansport.session.StreamingSockJsSession.handleInitialRequest(StreamingSockJsSession.java:54)
at org.springframework.web.ansport.handler.AbstractHttpSendingTransportHandler.handleRequestInternal(AbstractHttpSendingTransportHandler.java:66)
at org.springframework.web.ansport.handler.AbstractHttpSendingTransportHandler.handleRequest(AbstractHttpSendingTransportHandler.java:58)
at org.springframework.web.ansport.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.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:303)
at org.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
at at.websocket.server.WsFilter.doFilter(WsFilter.java:52)
at org.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
at org.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.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:241)
at org.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:208)
at org.StandardWrapperValve.invoke(StandardWrapperValve.java:220)
at org.StandardContextValve.invoke(StandardContextValve.java:122)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:501)
at org.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.StandardEngineValve.invoke(StandardEngineValve.java:116)
at org.tor.CoyoteAdapter.service(CoyoteAdapter.java:408)
at http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1040)
at AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:607)
at at.util.JIoEndpoint$SocketProcessor.run(JIoEndpoint.java:316)
at urrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)
at urrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615)
at at.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'] && !kie_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'] && !kie_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 _adyState !== 'undefined'
&& _adyState !== 'complete'))) {
that._protocols.unshift(protocol);
that.protocol = 'waiting-for-load';
utils.attachEvent('load', function(){
that._try_next_protocol();websocket和socket
});
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._ || 0) * roundTrips) || 5000;
that._transport_tref = utils.delay(to, function() {
if (adyState === 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._);
that._transport = new SockJS[protocol](that, trans_url,
that._base_url);
return true;
}
}
前⾯也说过,他们⽹络环境较慢,所以才想,应该是连接超时导致的,有上⾯可知,to为超时时间,计算公式为 var to = ((that._ || 0) * roundTrips) || 5000;
关键在于that._,这个是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._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._) {
rtt = that._;
}
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._ = rtt;
that._ = untRTO(rtt);
that._options.info.null_origin = !_document.domain;
var probed = utils.probeProtocols();
that._protocols = utils.detectProtocols(probed, protocols_whitelist, info);
};
rto设置到了!that._ = untRTO(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) {
this.frameFormat = frameFormat;
this.asyncRequestControl = AsyncRequestControl(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.ServletRequest().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 l. Also you must use a Servlet 3.0+ container");
}
最长那⼀句断⾔报的错,说的是l的servlet和filter中要加⼊<async-supported>true</async-supported>
看下l中,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();
< = new AjaxObject('GET', url);
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 = {};
}
}
});
}
也就是说,超时时间控制不了的...什么⿁
this._transportsWhitelist = ansports;这个是设置⽩名单,优先使⽤这些⽅式。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论