According to the Spring documentation, when returning a Flux
, Spring should emit a server-sent event for each element returned by the subscription.
Here’s an exemplaric REST controller:
package myapp.controller; import myapp.MyOutput; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import io.swagger.annotations.ApiResponse; import io.swagger.annotations.ApiResponses; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import reactor.core.publisher.Flux; @RestController @RequestMapping("/api/v1/test") @Api(tags = "Test API") public class TestController { @ApiOperation( value = "Test", response = MyOutput.class, produces = MediaType.TEXT_EVENT_STREAM_VALUE ) @RequestMapping( value = "", method = RequestMethod.PATCH, produces = MediaType.TEXT_EVENT_STREAM_VALUE ) @ApiResponses( value = { @ApiResponse(code = 200, message = "Service execution successful"), @ApiResponse(code = 400, message = "Bad input data"), @ApiResponse(code = 500, message = "An internal server error occurred."), @ApiResponse(code = 503, message = "The service is not available.") } ) public ResponseEntity<Flux<MyOutput>> test() { return ResponseEntity.ok().header("Connection", "Keep-Alive") .body(Flux.range(0, 1000) .delayElements(Duration.ofSeconds(1)) .map(MyOutput::new) ); } }
Example response, using wget:
data:{"recordCount":0} data:{"recordCount":148} data:{"recordCount":226} data:{"recordCount":266} data:{"recordCount":272} data:{"recordCount":286} data:{"recordCount":287} data:{"recordCount":293} data:{"recordCount":294}
When debugging the endpoint using Chrome or Postman, the clients seem to interpret the events as parts of a chunked response – not as a stream of events. I have confirmed that the response data is the same, and requires the expected amount of time. See screenshots from Chrome Network tab below:
The EventStream – tab is empty:
Compare this to the headers of a website like http://www.emojitracker.com/:
Where the EventStream tab displays the events correctly:
The important fact is, when consuming the Spring endpoint with a WebClient
, I can successfully receive each event at the expected time using .bodyToFlux
. So it seems like the events are missing some form of configuration that Chrome expects from a server-sent event stream – but which?
Advertisement
Answer
First of all in the browser when using EventSource will perform a GET request so using a PATCH is not really compatible in the browser.
If we takes your code and change the PATCH in GET and we create a simple page page:
<html lang="fr"> <head> <title>Test SSE</title> <script> const evtSource = new EventSource("/api/v1/test"); evtSource.onmessage = function(event) { const newElement = document.createElement("li"); const eventList = document.getElementById("results"); newElement.textContent = "message: " + event.data; eventList.appendChild(newElement); } </script> </head> <body> <ul id="results"> </ul> </body> </html>
Make your application serve this static file and open it in chrome. You will correctly see the events in the Event tab. However if you directly request /api/v1/test you will this the event in the page but not in the Event tab. I suppose that the event tab intercept EventSource object and won’t be used if no EventSource is created.