Using Spring Boot version 2.2.0 RC1 we get error org.springframework.web.HttpMediaTypeNotAcceptableException: Could not find acceptable representation when trying to use endpoint that returns SseEmitter
Using Spring Boot version 2.1.9.RELEASE same endpoint works as expected.
Sample project to reproduce
The sample app returns an SSEEmitter object from the controller handler and does not emit anything and lets the browser timeout happen (close then resubscribe).
A quick analysis shows that with Spring Framework 5.1.x, the response headers with the proper content-type are set and the client gets a 200 OK. With Spring Framework 5.2.x, those headers are never set and the client gets a 406 instead.
I spent a bit of time looking at this too. The 406 on 2.2 comes from trying to turn the error controller's return value into text/event-stream which can't be done. No error is sent with Boot 2.1 as the response has been committed before it reaches DefaultHandlerExceptionResolver.handleAsyncRequestTimeoutException so the error controller never gets involved.
It looks like a side-effect of https://github.com/spring-projects/spring-framework/commit/53cadf15e776ae6ebd14172fa13fa0766a1c34f9#diff-699aef3cb52576c81f4e707e6263bdec to me. I suspect the problem only occurs because nothing is emitted within the async timeout period.
Indeed this is probably due to the deferred committing of the response. We might need to clear the content type to allow for an error response body.
@jukkasi note that after the fix I would expect a timeout response status, rather than the 200 from before. This was the intent with #21972.
On taking a closer look, EventSource sends "Accept: text/event-stream" which pretty much rules out the possibility to return a differently formatted response. That's what leads to the 406.
I'm pulling this back from 5.2.1 for further investigation. I need to understand if EventSource can handle non-200 responses, and what the expectations are for their formatting. Certainly the Accept header does not allow any room for that.
In the meantime is there any workarounds available to make SSE work?
What do you mean make SSE work? It should work. My understanding of the issue is that when there is an error (or a timeout) before the first event is published, and response committed, then instead of completing with 200 and empty response, it fails to render an error response with details because the browser has restricted the acceptable media types to text/event-stream only.
Yes but in this situation browser does not reconnect automatically in case of timeout, which I believe it should do, and therefore some hacks would be required (probably on browser side then). I might be wrong about expecting auto-reconnect though, but I think this is how it worked in previous Spring version.
Okay thanks, I'll experiment and report. As for a workaround, you could ensure at least one event is emitted immediately (e.g. just an empty comment). That should commit the response.
Thinking some more about this, if the connection does time out, then not reconnecting seems like the right outcome. You have the ability to set the timeout in SseEmitter (or more globally in WebMvcConfigurer`) to some value longer than the server default (usually 10-30 seconds), and to ensure the emitter does emit (even an empty comment) within that time period, or alternatively set it to -1 so it never times out.
You can also handle the timeout exception via an @ExceptionHandler to complete with a 200 if you want the browser to reconnect again, but why enforce a server side timeout if you want effectively to keep the connection going?
If I read specification https://www.w3.org/TR/2009/WD-eventsource-20090421/ "Processing model" and "Reset the connection" part I believe that automatic reconnect should happen. But this of course depends what timeout means and what HTTP code server would return in case of time out. Im not sure if responding HTTP 204 No content would be wrong in case of timeout?
I don't have any experience about SSE until now and this is my first use case so I'm trying to understand the correct way.
EDIT: Exploring this further I think you are correct. Some solutions suggest to use heartbeat events to keep connection alive. I think only question now is how this timeout should be handled by Spring as it currently does respond with 406 and errors HttpMediaTypeNotAcceptableException
The key again is this is a server timeout. The server basically doesn't have knowledge of what the application is doing and if it is okay to wait longer. You can change that timeout, both for the request or for the server, to anything including to never time out.
The server (application) is also the one emitting the events. So you're in control of both how long the timeout and how frequently events are published. So yes heartbeats are necessary, and this is mentioned in our docs.
Personally I find SSE too much lacking such fundamental needs like heartbeats. You can consider emitting events with a Reactor Flux which makes it easier to blend in periodic heartbeats or use SockJS messaging (also mentioned in the docs).
As for the 406 this is coming from Spring Boot generating an error response body, and it's not anything we can solve here in the Spring Framework. That said I think it's actually behaving correctly. The client Accept header says only SSE and won't accept anything else, so Boot fails to create an error response which it wants to send. However you can handle that exception yourself as I wrote already, but you should not not be in a (sever) timeout situation in the first place or else the application isn't built correctly for SSE streaming.