我正在尝试使用Spring的DeferredResult
来执行长轮询。在此示例中,一个用户访问使用长轮询的页面,以等待另一个用户单击链接。然后第二个用户(您在另一个浏览器中)单击该链接,长轮询返回给第一个用户,通知她第二个用户的点击。
jsp看起来像这样:
<!DOCTYPE html>
<html lang="en">
<head>
<title>Spring Example</title>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.2/jquery.min.js"></script>
<script>
function pollContent() {
$.ajax({url: "waitForClick", success: function(result){
console.log("Polled result: " + result);
$("#polledContent").html(result);
pollContent();
}});
}
$(pollContent);
</script>
</head>
<body>
<p><a href="clickTheThing">Click this thing.</a></p>
<p id="polledContent">Waiting for somebody to click the thing...</p>
</body>
</html>
控制器看起来像这样:
package com.example.controller;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import org.springframework.stereotype.Component;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.context.request.async.DeferredResult;
import com.example.controller.interfaces.ExampleControllerInterface;
@Component
public class ExampleController implements ExampleControllerInterface{
private int clickCount = 0;
private List<DeferredResult<String>> waiting = new ArrayList<>();
@Override
public String viewHomePage(HttpServletRequest request, ModelMap model, HttpSession session){
return "index";
}
@RequestMapping(value = "/clickTheThing", method = RequestMethod.GET)
public String clickTheThing(HttpServletRequest request, ModelMap model, HttpSession session){
new Thread(){
public void run(){
clickCount++;
System.out.println("Somebody clicked the thing! Click count: " + clickCount);
Iterator<DeferredResult<String>> iterator = waiting.iterator();
while(iterator.hasNext()){
DeferredResult<String> result = iterator.next();
System.out.println("Setting result.");
result.setResult("Somebody clicked the thing! Click count: " + clickCount);
iterator.remove();
}
}
}.start();
return "clicked";
}
@ResponseBody
@RequestMapping(value = "/waitForClick", method = RequestMethod.GET)
public DeferredResult<String> waitForClick(HttpServletRequest request, ModelMap model, HttpSession session){
final DeferredResult<String> result = new DeferredResult<>();
waiting.add(result);
return result;
}
@ResponseBody
@RequestMapping(value = "/getClickCount", method = RequestMethod.GET)
public String getClickCount(HttpServletRequest request, ModelMap model, HttpSession session){
return String.valueOf(clickCount);
}
}
为了完整起见,这是我的ErrorConfig
课程:
package com.example.config;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
@ControllerAdvice
public class ErrorConfig{
@ExceptionHandler(Exception.class)
public String handleException (HttpServletRequest request, HttpServletResponse response, HttpSession session, Exception e) {
e.printStackTrace();
return "index";
}
}
这似乎工作正常。当其他用户点击链接时,确实会通知第一个用户。
但是,如果第一个用户在第二个用户点击链接之前刷新页面,我还会获得每个“旧”DeferredResult的堆栈跟踪:
org.apache.catalina.connector.ClientAbortException: java.io.IOException: An established connection was aborted by the software in your host machine
at org.apache.catalina.connector.OutputBuffer.realWriteBytes(OutputBuffer.java:396)
at org.apache.tomcat.util.buf.ByteChunk.flushBuffer(ByteChunk.java:426)
at org.apache.catalina.connector.OutputBuffer.doFlush(OutputBuffer.java:342)
at org.apache.catalina.connector.OutputBuffer.flush(OutputBuffer.java:316)
at org.apache.catalina.connector.CoyoteOutputStream.flush(CoyoteOutputStream.java:110)
at sun.nio.cs.StreamEncoder.implFlush(StreamEncoder.java:297)
at sun.nio.cs.StreamEncoder.flush(StreamEncoder.java:141)
at java.io.OutputStreamWriter.flush(OutputStreamWriter.java:229)
at org.springframework.util.StreamUtils.copy(StreamUtils.java:106)
at org.springframework.http.converter.StringHttpMessageConverter.writeInternal(StringHttpMessageConverter.java:106)
at org.springframework.http.converter.StringHttpMessageConverter.writeInternal(StringHttpMessageConverter.java:40)
at org.springframework.http.converter.AbstractHttpMessageConverter.write(AbstractHttpMessageConverter.java:208)
at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:143)
at org.springframework.web.servlet.mvc.method.annotation.AbstractMessageConverterMethodProcessor.writeWithMessageConverters(AbstractMessageConverterMethodProcessor.java:89)
at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.handleReturnValue(RequestResponseBodyMethodProcessor.java:193)
at org.springframework.web.method.support.HandlerMethodReturnValueHandlerComposite.handleReturnValue(HandlerMethodReturnValueHandlerComposite.java:71)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:122)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandleMethod(RequestMappingHandlerAdapter.java:749)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:689)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:83)
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.doGet(FrameworkServlet.java:852)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:618)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:837)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:725)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:301)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:721)
at org.apache.catalina.core.ApplicationDispatcher.doDispatch(ApplicationDispatcher.java:639)
at org.apache.catalina.core.ApplicationDispatcher.dispatch(ApplicationDispatcher.java:605)
at org.apache.catalina.core.AsyncContextImpl$1.run(AsyncContextImpl.java:208)
at org.apache.catalina.core.AsyncContextImpl.doInternalDispatch(AsyncContextImpl.java:363)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:214)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:503)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:136)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:78)
at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:610)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88)
at org.apache.catalina.connector.CoyoteAdapter.asyncDispatch(CoyoteAdapter.java:405)
at org.apache.coyote.http11.AbstractHttp11Processor.asyncDispatch(AbstractHttp11Processor.java:1636)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:646)
at org.apache.coyote.http11.Http11NioProtocol$Http11ConnectionHandler.process(Http11NioProtocol.java:222)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1566)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1523)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:745)
Caused by: java.io.IOException: An established connection was aborted by the software in your host machine
at sun.nio.ch.SocketDispatcher.write0(Native Method)
at sun.nio.ch.SocketDispatcher.write(SocketDispatcher.java:51)
at sun.nio.ch.IOUtil.writeFromNativeBuffer(IOUtil.java:93)
at sun.nio.ch.IOUtil.write(IOUtil.java:65)
at sun.nio.ch.SocketChannelImpl.write(SocketChannelImpl.java:470)
at org.apache.tomcat.util.net.NioChannel.write(NioChannel.java:122)
at org.apache.tomcat.util.net.NioBlockingSelector.write(NioBlockingSelector.java:101)
at org.apache.tomcat.util.net.NioSelectorPool.write(NioSelectorPool.java:173)
at org.apache.coyote.http11.InternalNioOutputBuffer.writeToSocket(InternalNioOutputBuffer.java:139)
at org.apache.coyote.http11.InternalNioOutputBuffer.addToBB(InternalNioOutputBuffer.java:197)
at org.apache.coyote.http11.InternalNioOutputBuffer.access$000(InternalNioOutputBuffer.java:41)
at org.apache.coyote.http11.InternalNioOutputBuffer$SocketOutputBuffer.doWrite(InternalNioOutputBuffer.java:320)
at org.apache.coyote.http11.filters.IdentityOutputFilter.doWrite(IdentityOutputFilter.java:84)
at org.apache.coyote.http11.AbstractOutputBuffer.doWrite(AbstractOutputBuffer.java:257)
at org.apache.coyote.Response.doWrite(Response.java:523)
at org.apache.catalina.connector.OutputBuffer.realWriteBytes(OutputBuffer.java:391)
... 50 more
Aug 20, 2015 7:19:24 PM org.apache.catalina.core.ApplicationDispatcher invoke
SEVERE: Servlet.service() for servlet jsp threw exception
java.lang.IllegalStateException: getOutputStream() has already been called for this response
at org.apache.catalina.connector.Response.getWriter(Response.java:535)
at org.apache.catalina.connector.ResponseFacade.getWriter(ResponseFacade.java:212)
at javax.servlet.ServletResponseWrapper.getWriter(ServletResponseWrapper.java:103)
at org.apache.jasper.runtime.JspWriterImpl.initOut(JspWriterImpl.java:115)
at org.apache.jasper.runtime.JspWriterImpl.flushBuffer(JspWriterImpl.java:108)
at org.apache.jasper.runtime.PageContextImpl.release(PageContextImpl.java:173)
at org.apache.jasper.runtime.JspFactoryImpl.internalReleasePageContext(JspFactoryImpl.java:120)
at org.apache.jasper.runtime.JspFactoryImpl.releasePageContext(JspFactoryImpl.java:75)
at org.apache.jsp.WEB_002dINF.jsp.index_jsp._jspService(index_jsp.java:93)
at org.apache.jasper.runtime.HttpJspBase.service(HttpJspBase.java:70)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:725)
at org.apache.jasper.servlet.JspServletWrapper.service(JspServletWrapper.java:432)
at org.apache.jasper.servlet.JspServlet.serviceJspFile(JspServlet.java:403)
at org.apache.jasper.servlet.JspServlet.service(JspServlet.java:347)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:725)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:301)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:721)
at org.apache.catalina.core.ApplicationDispatcher.doInclude(ApplicationDispatcher.java:584)
at org.apache.catalina.core.ApplicationDispatcher.include(ApplicationDispatcher.java:523)
at org.springframework.web.servlet.view.InternalResourceView.renderMergedOutputModel(InternalResourceView.java:201)
at org.springframework.web.servlet.view.AbstractView.render(AbstractView.java:267)
at org.springframework.web.servlet.DispatcherServlet.render(DispatcherServlet.java:1221)
at org.springframework.web.servlet.DispatcherServlet.processDispatchResult(DispatcherServlet.java:1005)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:952)
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.doGet(FrameworkServlet.java:852)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:618)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:837)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:725)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:301)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206)
at org.apache.catalina.core.ApplicationDispatcher.invoke(ApplicationDispatcher.java:721)
at org.apache.catalina.core.ApplicationDispatcher.doDispatch(ApplicationDispatcher.java:639)
at org.apache.catalina.core.ApplicationDispatcher.dispatch(ApplicationDispatcher.java:605)
at org.apache.catalina.core.AsyncContextImpl$1.run(AsyncContextImpl.java:208)
at org.apache.catalina.core.AsyncContextImpl.doInternalDispatch(AsyncContextImpl.java:363)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:214)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:503)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:136)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:78)
at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:610)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88)
at org.apache.catalina.connector.CoyoteAdapter.asyncDispatch(CoyoteAdapter.java:405)
at org.apache.coyote.http11.AbstractHttp11Processor.asyncDispatch(AbstractHttp11Processor.java:1636)
at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:646)
at org.apache.coyote.http11.Http11NioProtocol$Http11ConnectionHandler.process(Http11NioProtocol.java:222)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1566)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1523)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:745)
我可以简单地忽略这些例外,但这感觉不对。
所以,我的问题是:
getOutputStream() has already been called for this response
异常的方法,我收集的是由异常处理程序的错误页面引起的?如果您想亲自尝试,我可以在GitHub here上找到该项目的一个mavenized版本。
最后我试图在我的Spring网站上添加一个通知系统,类似于StackOverflow的通知系统。如果有一个更好的方法来做到这一点与春季和长期民意调查,我都是耳朵。
编辑:我没有收到任何答案(甚至评论),所以我添加了赏金。我非常感谢任何反馈!
答案 0 :(得分:5)
它失败的原因是因为当第一个用户关闭浏览器时Stream关闭,当你尝试设置DeferredResult的结果时,spring会尝试将它发送到客户端,从而导致错误。
在写入结果之前,您应该通过调用它的isSetOrExpired()
方法来检查DeferredResult是否处于可用状态:
while(iterator.hasNext()){
DeferredResult<String> result = iterator.next();
System.out.println("Setting result.");
if(!result.isSetOrExpired()){
result.setResult("Somebody clicked the thing! Click count: " + clickCount);
}
iterator.remove();
}
如果在客户端关闭连接时Spring仍然没有取消延迟,那么防止异常实际发生并没有太多事情要做:Detecting client disconnect in tomcat servlet?。
请注意,长整流和彗星一般来说很难从头开始构建,你应该考虑使用Atmosphere这样的东西。它将为您提供旧的浏览器兼容性的websockets和comet。
做这种事情的最佳做法是什么,最好是以不产生例外的方式?
是否有更好的方法来跟踪长轮询的DeferredResults?
有没有办法围绕后续的getOutputStream()调用此响应异常,我收集的是异常处理程序的错误页面引起的?
答案 1 :(得分:1)
导致原始ClientAbortException的原因是什么?我应该这样做吗? 不同的东西?
当我们使用DeferredResult或Callable时:
ClientAbortException:客户端发出了一些其他请求/单击其他链接。 这意味着当前操作被挂起,它会在当前运行的线程上抛出ClientAbortException。
已建立的连接已被主机中的软件中止 机
最重要的是,Windows防火墙也可以执行此类中止功能检查一次。
最好是做这种事情的最佳做法是什么 以不产生异常的方式?
的WebSockets
是否有更好的方法可以长时间跟踪DeferredResults 轮询?
为不同的用户定义一个集合以及长轮询。
有没有办法解决后续的getOutputStream()问题 被称为此响应异常,我收集的是由此引起的 异常处理程序的错误页面?
这也可能是引发此当前异常的原因。
答案 2 :(得分:-1)
<a href="clickTheThing">Click this thing.</a> . If you use a tag href this web page will reload. should use preventDefault to disable reload page.
然后浏览器不会关闭连接。