I'm trying to use Springs SseEmitter like in this post: Angular 2 spring boot server side events. 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. 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
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
@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. You are getting it because the servlet api that doesn't notify when a client connection is closed. That's why it try to write on the socket anyway, more info at 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):
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;
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With