简体   繁体   English

Spring MVC SseEmitter - 连接中止异常

[英]Spring MVC SseEmitter - connection aborted Exception

I'm trying to use Springs SseEmitter like in this post: Angular 2 spring boot server side events . 我正试图在这篇文章中使用Springs SseEmitter: Angular 2 spring boot服务器端事件 The push events are working but everytime I'm closing or refreshing the tab I've got the exception below. 推送事件正在发挥作用,但每次我关闭或刷新选项卡时,我都有下面的例外情况。

The strange thing is that the exception is thrown within the send method of the emiter which is surrounded by a try catch block. 奇怪的是,异常被抛出在发射器的send方法中,该方法被try catch块包围。 The exception must be catched and logged and rethrown within the method. 必须在方法中捕获并记录和重新抛出异常。 But how can I prevent it. 但是我怎么能阻止它呢。 I don't want to suppress the error log. 我不想压制错误日志。

IOException: Eine bestehende Verbindung wurde softwaregesteuert durch den Hostcomputer abgebrochen

IOEexception an established connection was aborted by the software in your host machine

Thanks! 谢谢!

SseController.java SseController.java

import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

@RestController
public class SSEController {

    public static final List<SseEmitter> emitters = Collections.synchronizedList( new ArrayList<>());

    @RequestMapping(path = "/stream", method = RequestMethod.GET)
    public SseEmitter stream() throws IOException {

        SseEmitter emitter = new SseEmitter();

        emitters.add(emitter);
        emitter.onCompletion(() -> emitters.remove(emitter));

        return emitter;
    }
}

ServiceClass.java ServiceClass.java

@Scheduled
public void sendSseEventsToUI(Notification notification) { //your model class
        List<SseEmitter> sseEmitterListToRemove = new ArrayList<>();
        SSEController.emitters.forEach((SseEmitter emitter) -> {
            try {
                emitter.send(notification, MediaType.APPLICATION_JSON);
            } catch (Exception e) {
                emitter.complete();
                sseEmitterListToRemove.add(emitter);
            }
        });
        SSEController.emitters.removeAll(sseEmitterListToRemove);
    }

Exception: 例外:

> 2017-12-27 13:54:53.206  INFO 4248 --- [pool-4-thread-1]
> o.apache.coyote.http11.Http11Processor   : An error occurred in
> processing while on a non-container thread. The connection will be
> closed immediately
> 
> java.io.IOException: Eine bestehende Verbindung wurde
> softwaregesteuert durch den Hostcomputer abgebrochen  at
> sun.nio.ch.SocketDispatcher.write0(Native Method) ~[na:1.8.0_121]     at
> sun.nio.ch.SocketDispatcher.write(SocketDispatcher.java:51)
> ~[na:1.8.0_121]   at
> sun.nio.ch.IOUtil.writeFromNativeBuffer(IOUtil.java:93)
> ~[na:1.8.0_121]   at sun.nio.ch.IOUtil.write(IOUtil.java:65)
> ~[na:1.8.0_121]   at
> sun.nio.ch.SocketChannelImpl.write(SocketChannelImpl.java:471)
> ~[na:1.8.0_121]   at
> org.apache.tomcat.util.net.NioChannel.write(NioChannel.java:134)
> ~[tomcat-embed-core-8.5.23.jar:8.5.23]    at
> org.apache.tomcat.util.net.NioBlockingSelector.write(NioBlockingSelector.java:101)
> ~[tomcat-embed-core-8.5.23.jar:8.5.23]    at
> org.apache.tomcat.util.net.NioSelectorPool.write(NioSelectorPool.java:157)
> ~[tomcat-embed-core-8.5.23.jar:8.5.23]    at
> org.apache.tomcat.util.net.NioEndpoint$NioSocketWrapper.doWrite(NioEndpoint.java:1267)
> ~[tomcat-embed-core-8.5.23.jar:8.5.23]    at
> org.apache.tomcat.util.net.SocketWrapperBase.doWrite(SocketWrapperBase.java:670)
> ~[tomcat-embed-core-8.5.23.jar:8.5.23]    at
> org.apache.tomcat.util.net.SocketWrapperBase.flushBlocking(SocketWrapperBase.java:607)
> ~[tomcat-embed-core-8.5.23.jar:8.5.23]    at
> org.apache.tomcat.util.net.SocketWrapperBase.flush(SocketWrapperBase.java:597)
> ~[tomcat-embed-core-8.5.23.jar:8.5.23]    at
> org.apache.coyote.http11.Http11OutputBuffer.flushBuffer(Http11OutputBuffer.java:581)
> ~[tomcat-embed-core-8.5.23.jar:8.5.23]    at
> org.apache.coyote.http11.Http11OutputBuffer.flush(Http11OutputBuffer.java:272)
> ~[tomcat-embed-core-8.5.23.jar:8.5.23]    at
> org.apache.coyote.http11.Http11Processor.flush(Http11Processor.java:1560)
> ~[tomcat-embed-core-8.5.23.jar:8.5.23]    at
> org.apache.coyote.AbstractProcessor.action(AbstractProcessor.java:283)
> ~[tomcat-embed-core-8.5.23.jar:8.5.23]    at
> org.apache.coyote.Response.action(Response.java:173)
> [tomcat-embed-core-8.5.23.jar:8.5.23]     at
> org.apache.catalina.connector.OutputBuffer.doFlush(OutputBuffer.java:317)
> [tomcat-embed-core-8.5.23.jar:8.5.23]     at
> org.apache.catalina.connector.OutputBuffer.flush(OutputBuffer.java:284)
> [tomcat-embed-core-8.5.23.jar:8.5.23]     at
> org.apache.catalina.connector.CoyoteOutputStream.flush(CoyoteOutputStream.java:118)
> [tomcat-embed-core-8.5.23.jar:8.5.23]     at
> sun.nio.cs.StreamEncoder.implFlush(StreamEncoder.java:297)
> [na:1.8.0_121]    at
> sun.nio.cs.StreamEncoder.flush(StreamEncoder.java:141) [na:1.8.0_121]
>   at java.io.OutputStreamWriter.flush(OutputStreamWriter.java:229)
> [na:1.8.0_121]    at
> org.springframework.util.StreamUtils.copy(StreamUtils.java:119)
> [spring-core-4.3.13.RELEASE.jar:4.3.13.RELEASE]   at
> org.springframework.http.converter.StringHttpMessageConverter.writeInternal(StringHttpMessageConverter.java:106)
> [spring-web-4.3.13.RELEASE.jar:4.3.13.RELEASE]    at
> org.springframework.http.converter.StringHttpMessageConverter.writeInternal(StringHttpMessageConverter.java:41)
> [spring-web-4.3.13.RELEASE.jar:4.3.13.RELEASE]    at
> org.springframework.http.converter.AbstractHttpMessageConverter.write(AbstractHttpMessageConverter.java:227)
> [spring-web-4.3.13.RELEASE.jar:4.3.13.RELEASE]    at
> org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitterReturnValueHandler$HttpMessageConvertingHandler.sendInternal(ResponseBodyEmitterReturnValueHandler.java:207)
> [spring-webmvc-4.3.13.RELEASE.jar:4.3.13.RELEASE]     at
> org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitterReturnValueHandler$HttpMessageConvertingHandler.send(ResponseBodyEmitterReturnValueHandler.java:200)
> [spring-webmvc-4.3.13.RELEASE.jar:4.3.13.RELEASE]     at
> org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter.sendInternal(ResponseBodyEmitter.java:166)
> [spring-webmvc-4.3.13.RELEASE.jar:4.3.13.RELEASE]     at
> org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter.send(ResponseBodyEmitter.java:159)
> [spring-webmvc-4.3.13.RELEASE.jar:4.3.13.RELEASE]     at
> org.springframework.web.servlet.mvc.method.annotation.SseEmitter.send(SseEmitter.java:126)
> [spring-webmvc-4.3.13.RELEASE.jar:4.3.13.RELEASE]     at
> org.springframework.web.servlet.mvc.method.annotation.SseEmitter.send(SseEmitter.java:107)
> [spring-webmvc-4.3.13.RELEASE.jar:4.3.13.RELEASE]     at
> com.example.demo.PushService.sendSseEventsToUI(PushService.java:22)
> [classes/:na]     at sun.reflect.NativeMethodAccessorImpl.invoke0(Native
> Method) ~[na:1.8.0_121]   at
> sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
> ~[na:1.8.0_121]   at
> sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
> ~[na:1.8.0_121]   at java.lang.reflect.Method.invoke(Method.java:498)
> ~[na:1.8.0_121]   at
> org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledMethodRunnable.java:65)
> [spring-context-4.3.13.RELEASE.jar:4.3.13.RELEASE]    at
> org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:54)
> [spring-context-4.3.13.RELEASE.jar:4.3.13.RELEASE]    at
> java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
> [na:1.8.0_121]    at
> java.util.concurrent.FutureTask.runAndReset(FutureTask.java:308)
> [na:1.8.0_121]    at
> java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:180)
> [na:1.8.0_121]    at
> java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:294)
> [na:1.8.0_121]    at
> java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
> [na:1.8.0_121]    at
> java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
> [na:1.8.0_121]    at java.lang.Thread.run(Thread.java:745)
> [na:1.8.0_121]

You are seeing a log from org.apache.coyote.http11.Http11Processor. 您正在从org.apache.coyote.http11.Http11Processor中看到一个日志。 You are getting it because the servlet api that doesn't notify when a client connection is closed. 您正在获取它,因为servlet api在客户端连接关闭时不通知。 That's why it try to write on the socket anyway, more info at jira.spring.io/browse/SPR-13292 这就是为什么它试图在套接字上写,更多信息在jira.spring.io/browse/SPR-13292

If you don't want to see this log change the log level for this package by adding this property (in your application.properties): 如果您不希望看到此日志,请通过添加此属性(在application.properties中)更改此程序包的日志级别:

logging.level.org.apache.coyote.http11=ERROR

Or you can edit your logger configuration file. 或者您可以编辑记录器配置文件。

Furthermore don't use static final collection for keeping your emitter do something like: 此外,不要使用静态最终集合来保持您的发射器执行以下操作:

In your service 在您的服务中

private final Collection<SseEmitter> emitters = Collections.synchronizedCollection(new HashSet<SseEmitter>());

public void register(SseEmitter emitter) {
    emitter.onTimeout(() -> timeout(emitter));
    emitter.onCompletion(() -> complete(emitter));

    emitters.add(emitter);
}

private void complete(SseEmitter emitter) {
    System.out.println("emitter completed");
    emitters.remove(emitter);
}

private void timeout(SseEmitter emitter) {
    System.out.println("emitter timeout");
    emitters.remove(emitter);
}

@Scheduled(fixedDelay = 3000)
public void sendSseEventsToUI() { //your model class
    for(SseEmitter emitter : emitters) {
        try {
            emitter.send(UUID.randomUUID().toString(), MediaType.APPLICATION_JSON);
        } catch (Throwable e) {
            emitter.complete();
        }
    };
}

In your controller: 在你的控制器中:

@Autowired
public PushController(PushService service) {
    this.service = service;
}

@RequestMapping(path = "/", method = RequestMethod.GET)
public SseEmitter stream() {
    final SseEmitter emitter = new SseEmitter(0L);
    service.register(emitter);
    return emitter;
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM