java Spring DeferredResult 导致 IOException:已建立的连接被主机中的软件中止

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/32130249/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-11-02 19:42:13  来源:igfitidea点击:

Spring DeferredResult causes IOException: An established connection was aborted by the software in your host machine

javaspringspring-mvctomcat

提问by Kevin Workman

I'm trying to use Spring's DeferredResultto perform long polling. In this example, one user visits a page that uses long polling to wait for another user to click a link. A second user (you in another browser) then clicks that link, and the long polling returns to the first user, notifying her of the second user's click.

我正在尝试使用 SpringDeferredResult来执行长轮询。在这个例子中,一个用户访问一个使用长轮询等待另一个用户点击链接的页面。第二个用户(你在另一个浏览器中)然后点击该链接,长轮询返回给第一个用户,通知她第二个用户的点击。

The jsp looks like this:

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>

And the controller looks like this:

控制器看起来像这样:

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);
    }
}

And for completeness, here is my ErrorConfigclass:

为了完整起见,这是我的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";
    }
}

This seems to work okay. The first user is indeed notified whenever another user clicks the link.

这似乎工作正常。每当另一个用户单击链接时,第一个用户都会收到通知。

However, if that first user refreshes the page before the second user clicks the link, I also get a stack trace for every "old" DeferredResult:

但是,如果第一个用户在第二个用户单击链接之前刷新页面,我还会获得每个“旧”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.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
            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();
            }
0(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.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)

I can simply ignore these exceptions, but that feels wrong.

我可以简单地忽略这些异常,但这感觉不对。

So, my questions are:

所以,我的问题是:

  • What is causing the original ClientAbortException? Should I be doing something different?
  • What are the best practices for doing this kind of thing, preferably in a way that doesn't generate exceptions?
  • Is there a better way to keep track of DeferredResults for long polling?
  • Is there a way around the subsequent getOutputStream() has already been called for this responseexceptions, which I gather are caused by the error page of the exception handler?
  • 是什么导致了原来的 ClientAbortException?我应该做些不同的事情吗?
  • 做这种事情的最佳实践是什么,最好是以不产生异常的方式?
  • 有没有更好的方法来跟踪长轮询的 DeferredResults?
  • 有没有办法解决getOutputStream() has already been called for this response我收集的由异常处理程序的错误页面引起的后续异常?

I have a mavenized version of this project available on GitHub hereif you want to try it yourself.

我准备好这些项目的Maven化版本在GitHub在这里,如果你想自己尝试一下。

In the end I'm trying to add a notifications system to my Spring website, similar to StackOverflow's notification system. If there's a better way to do that with Spring and long polling, I'm all ears.

最后,我尝试向我的 Spring 网站添加一个通知系统,类似于 StackOverflow 的通知系统。如果使用 Spring 和长轮询有更好的方法来做到这一点,我会全力以赴。

Edit: I haven't received any answers (or even comments), so I've added a bounty. I'd definitely appreciate any feedback!

编辑:我没有收到任何答案(甚至评论),所以我添加了赏金。我绝对感谢任何反馈!

采纳答案by Alfonso Presa

The reason why it's failing is because when the first user closes the browser the Stream gets closed, and when you try to set the DeferredResult's result spring tries to send it to the client, causing the error.

它失败的原因是因为当第一个用户关闭浏览器时,Stream 被关闭,并且当您尝试设置 DeferredResult 的结果时,spring 尝试将其发送到客户端,从而导致错误。

You should try checking if the DeferredResult is in a usablestatus by calling it's isSetOrExpired()method before writting the result:

在写入结果之前,您应该尝试通过调用它的方法来检查 DeferredResult 是否处于可用状态isSetOrExpired()

<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.

If Spring still doesn't get the Deferred canceled when the client closes the connection, then there's not too much to do to prevent the exception from actually happening: Detecting client disconnect in tomcat servlet?.

如果在客户端关闭连接时 Spring 仍然没有取消 Deferred,那么没有太多工作可以防止异常实际发生:Detecting client disconnect in tomcat servlet? .

Note that long-poling and comet in general is a hard thing to build from scratch, you should consider using something like Atmospherefor this. It will give you both websockets and comet for old browser compatibility.

请注意,一般而言,长极化和彗星很难从头开始构建,您应该考虑为此使用Atmosphere 之类的东西。它将为您提供 websockets 和 Comet 以兼容旧的浏览器。

About your other questions

关于您的其他问题

  • What are the best practices for doing this kind of thing, preferably in a way that doesn't generate exceptions?

    • You should consider using WebSockets for this. Spring 4.2 comes with some nice features for it, and also check Atmosphere
    • You can also give a chance to server-sent events
  • Is there a better way to keep track of DeferredResults for long polling?

    • I guess your way is ok if everyone is able to click the button and notify everyone listening. If you want some more targeted behaviour (like specifically notifying a user of his individual events) then you should keep a Map of users and pending long polling DeferredResults.
  • Is there a way around the subsequent getOutputStream() has already been called for this response exceptions, which I gather are caused by the error page of the exception handler?

    • That error happens for the same reason than the first one. As an error has occurred, the servlet container is trying to print the error JSP to the closed stream causing an error. The way to solve this is just making no error to happen ever when a request is closed before getting answered :-).
  • 做这种事情的最佳实践是什么,最好是以不产生异常的方式?

    • 为此,您应该考虑使用 WebSockets。Spring 4.2 为它提供了一些不错的功能,还可以检查Atmosphere
    • 您还可以为服务器发送的事件提供机会
  • 有没有更好的方法来跟踪长轮询的 DeferredResults?

    • 如果每个人都能够单击按钮并通知每个人在听,我想你的方式没问题。如果你想要一些更有针对性的行为(比如专门通知用户他的个人事件),那么你应该保留一个用户地图并等待长时间轮询 DeferredResults。
  • 有没有办法解决随后的 getOutputStream() 已经为此响应异常调用的方法,我认为这些异常是由异常处理程序的错误页面引起的?

    • 发生该错误的原因与第一个相同。发生错误时,servlet 容器试图将错误 JSP 打印到导致错误的关闭流。解决这个问题的方法就是在请求被回答之前关闭时不发生错误:-)。

回答by MS Ibrahim

What is causing the original ClientAbortException? Should I be doing something different?

是什么导致了原来的 ClientAbortException?我应该做些不同的事情吗?

When we are using DeferredResultor Callable:

当我们使用DeferredResult或 Callable 时:

ClientAbortException:Client made some other request/clicking some other link. which means current action suspended and it throws ClientAbortException on the current running thread.

ClientAbortException:客户端提出了其他一些请求/点击了其他一些链接。这意味着当前操作暂停,并在当前运行的线程上抛出 ClientAbortException。

An established connection was aborted by the software in your host machine

已建立的连接被主机中的软件中止

Most Probably,Windows Firewall can also do this kind of abort functionality check for it once.

很有可能,Windows 防火墙也可以为它做一次这种中止功能检查。

What are the best practices for doing this kind of thing, preferably in a way that doesn't generate exceptions?

做这种事情的最佳实践是什么,最好是以不产生异常的方式?

WebSockets

网络套接字

Is there a better way to keep track of DeferredResults for long polling?

有没有更好的方法来跟踪长轮询的 DeferredResults?

Define a collection for different User along with long polling.

为不同的用户定义一个集合以及长轮询。

Is there a way around the subsequent getOutputStream() has already been called for this response exceptions, which I gather are caused by the error page of the exception handler?

有没有办法解决随后的 getOutputStream() 已经为此响应异常调用的方法,我认为这些异常是由异常处理程序的错误页面引起的?

This may also be a reason for this Current Exception being thrown.

这也可能是引发此当前异常的原因。

回答by clinx chen

##代码##

Then the conneciton will not be close by browser.

然后浏览器不会关闭连接。