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?
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.
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