java - Spring MVC SseEmitter - 连接异常中止

标签 java spring spring-mvc spring-boot

我正在尝试像这篇文章中那样使用 Springs SseEmitter: Angular 2 spring boot server side events .推送事件有效,但每次我关闭或刷新选项卡时,我都会遇到以下异常。

奇怪的是,异常是在 emiter 的 send 方法中抛出的,它被一个 try catch block 包围。异常必须在方法中被捕获和记录并重新抛出。但是我怎么能阻止它呢。我不想抑制错误日志。

IOException: Eine bestehende Verbindung wurde softwaregesteuert durch den Hostcomputer abgebrochen

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

谢谢!

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

服务类.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]

最佳答案

您正在查看来自 org.apache.coyote.http11.Http11Processor 的日志。你得到它是因为 servlet api 在客户端连接关闭时不通知。这就是为什么它无论如何都会尝试在套接字上写入的原因,更多信息请访问 jira.spring.io/browse/SPR-13292

如果您不想看到此日志,请通过添加此属性(在您的 application.properties 中)更改此包的日志级别:

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

或者您可以编辑记录器配置文件。

此外,不要使用静态最终集合来让你的发射器做类似的事情:

为您服务

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

在你的 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;
}

关于java - Spring MVC SseEmitter - 连接异常中止,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48003853/

相关文章:

java - mac 中的 processWindowEvent

java - 创建没有 XML 文件的relativelayout

java - 设置 Spring Archetype 时重复出现的问题

java - @Scheduled 在服务器启动时被调用

java - 使用 flex + servlet + jasper 在新浏览器选项卡中显示 PDF 时出现问题

java - 如何使用java代码获取用户提交到git-hub的代码行?

java - 来自 Spring MVC 中 LocalDateTime(java 8) 的 Json 字符串

java - 根据Spring的bean定义bean

java - DB返回值是多个

java - Spring 4.1 静态内容配置