Printemps DeferredResult causes IOException: Une connexion établie a été abandonnée par le logiciel sur votre ordinateur hôte

Je suis en train d'utiliser le Printemps DeferredResult à effectuer le long du scrutin. Dans cet exemple, un utilisateur visite une page qui utilise le temps d'interrogation à attendre pour un autre utilisateur à cliquer sur un lien. Un deuxième utilisateur (vous dans un autre navigateur) puis clique sur le lien, et le temps d'interrogation renvoie à la première de l'utilisateur, en notifiant son de la deuxième clic de l'utilisateur.

La jsp ressemble à ceci:

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

Et le contrôleur ressemble à ceci:

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

Et pour être complet, voici mon ErrorConfig classe:

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

Cela semble fonctionner ok. Le premier utilisateur est en effet informé chaque fois qu'un autre utilisateur clique sur le lien.

Toutefois, si le premier utilisateur actualise la page avant que le deuxième utilisateur clique sur le lien, j'ai aussi une trace de la pile pour tous les "vieux" 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)

Je peux simplement ignorer ces exceptions, mais qui se sent mal.

Donc, mes questions sont les suivantes:

  • Ce qui est à l'origine de l'original ClientAbortException? Devrais-je faire quelque chose de différent?
  • Quelles sont les meilleures pratiques pour faire ce genre de chose, de préférence dans un sens qui ne génèrent pas des exceptions?
  • Est-il un meilleur moyen de garder une trace de DeferredResults pour le temps d'interrogation?
  • Est-il un moyen de contourner les ultérieur getOutputStream() has already been called for this response exceptions, que je recueille sont causés par la page d'erreur du gestionnaire d'exception?

J'ai un mavenized version de ce projet, disponible sur GitHub ici si vous voulez essayer vous-même.

À la fin, j'essaye d'ajouter un système de notifications pour mon site web Spring, semblable à StackOverflow du système de notification. Si il y a une meilleure façon de le faire qu'avec le Printemps et le temps d'interrogation, je suis tout ouïe.

Edit: je n'ai pas reçu de réponses (ou même de commentaires), j'ai donc ajouté une prime. Je serais certainement apprécions tous les commentaires!

  • Comme une alternative sur la façon de le faire au Printemps: je voudrais utiliser les WebSockets.
  • Merci Andrei. Je vois que StackOverflow utilise les WebSockets, donc je suppose qu'ils sont la peine d'enquêter. J'étais juste vraiment l'espoir de le faire fonctionner avec DeferredResult. Bon d'avoir un Plan B si.
  • Le ClientAbortException a été causé par le IOException, comme la trace de la pile clairement dit, et il ya de nombreuses questions existantes, d'ici à ce sujet.
  • J'ai vu ces questions, mais aucun d'entre eux semblent correspondre à mon scénario. Je serais heureux d'examiner toute question spécifique que vous pensez explique mon problème.
  • Je ne pouvais pas reproduire l'erreur. S'il vous plaît pourriez-vous expliquer plus en détail les actions de l'utilisateur User1 et User2? Je ne vois erreur de dépassement de Délai si User1 est en attente pour cliquez de User2 mais User2 ne rien faire.
  • Assurez-vous que vous avez la ErrorConfig correctement configuré. Sans elle, l'erreur est juste mangé en silence. Voici les étapes que je prendrais de rappeler l'erreur: l'Utilisateur 1 de visites de l'index. (Cela crée un DeferredResult qui est mémorisé.) L'utilisateur 1 rafraîchit. (Cela crée un autre DeferredResult qui est mémorisé.) L'utilisateur 1 actualise de nouveau. (Un Autre DeferredResult.) L'utilisateur 2 visites de l'index. L'utilisateur 2 clique sur le "Cliquez sur cette chose" lien. La dernière DeferredResult retourne correctement, mais les 2 premiers de générer des erreurs.
  • Qui de Version de Tomcat avec qui vous travaillez?
  • J'ai travaillé avec Tomcat 8.
  • J'ai essayé de reproduire le problème avec nettoie les installations de tomcat 8.0.1 et tomcat 8.0.26... Qui version exacte sont yo essayer contre? Aussi avez-vous apporté des modifications à la configuration de tomcat? Si oui, laquelle ou lesquelles des changements? pouvez-vous essayer avec un crean 8.0.26 installer?
  • Je vais vérifier tout ça quand je rentre à la maison plus tard aujourd'hui, mais assurez-vous d'avoir la ErrorConfig mis en place correctement. Sans elle, cette erreur est tout simplement ignoré.
  • Je peux confirmer la ErrorConfig est chargé (j'ai ajouté une trace dans son constructeur) et que rien n'est apparaissant après: l'ouverture de deux navigateurs à '/', rafraîchissant le premier navigateur, en cliquant sur "elle", dans le second.
  • "Une connexion établie a été abandonnée par le logiciel sur votre machine hôte". Je suis à penser qu'il pourrait être OS releated. Je suis sur windoze, où êtes-vous?
  • De même. Windows 7. Mais il arrive aussi sur un serveur linux.
  • C'est ce que je suis en utilisant... Alors j'ai aucune idée de pourquoi ça fonctionne très bien dans mon env... il est peut-être liés au navigateur. De toute façon, de penser à ce sujet, le client soudainement fermé la connexion avec le serveur... c'est une erreur, comme l'a démontré par le lien dans ma réponse et il n'ya aucun moyen que je peux penser à les éviter (autres que certains sale synchrone requête Ajax dans l'événement onbeforeunload). Si elle est cachée par défaut, sauf si vous ajoutez le ErrorConfig, alors je suppose que vous devez l'ignorer... de toute façon, je vous recommande fortement d'essayer Atmosphère de résoudre ce problème.
  • Votre code n'est pas thread-safe. Vous avez besoin d'un AtomicInteger au lieu de int, un concurrent à la file d'attente au lieu d'un tableau de liste, une tâche périodique pour traiter la file d'attente au lieu de frai fils vous-même. Vous devriez vérifier cet exemple de projet MVC de Printemps avec des exemples de DeferredResult.
  • Le fil de sécurité ont rien à voir avec le problème que j'ai décrit?
  • Pas vraiment, juste quelques améliorations.