I have a card game. As backend I have a spring boot application with webflux and RESTful API as interface. As frontend I'm using next.js application. In between is the ngnix used as a proxy.
I wanted to push some game notifications towards frontend. So I choosed server side events, but I have troubles with it. As long as more then one gamer is online, every 30-60 seconds the clients starting receving Bad Gateway errors from ngnix. After 5-10 seconds the spook is over.
As endpoint I added following method, among other endpoints
@GetMapping(path = "/events/{gamer}", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> streamFlux(@PathVariable String gamer) {
log.info("subscribe for {}", gamer);
return service.subscribe(gamer);
}
Then as a service I have implemented following code
final Sinks.Many<CloudEvent> sink = Sinks.many().replay().limit(20, Duration.ofMinutes(10));
@EventListener
@SneakyThrows
public void eventListener(GameEvent gameEvent) {
log.info("occured an event {} {}", gameEvent, mapper.writeValueAsString(gameEvent));
var event = CloudEventBuilder.v1()
.withId(UUID.randomUUID().toString())
.withType(gameEvent.getClass().getSimpleName())
.withSource(URI.create("sol://some/uri"))
.withSubject(gameEvent.getGamer())
.withTime(OffsetDateTime.now(ZoneOffset.UTC))
.withData ("application/json", mapper.writeValueAsString(gameEvent).getBytes(StandardCharsets.UTF_8))
.build();
try {
sink.emitNext(event, Sinks.EmitFailureHandler.FAIL_FAST);
} catch (Exception ex) {
log.warn("Exception event emit", ex);
}
}
public Flux<String> subscribe(String gamer) {
return sink.asFlux()
.doOnNext((e) -> log.info("sent next event {}", e))
.filter(event -> "*".equals(event.getSubject()) || event.getSubject().equals(gamer))
.map(e -> new String(EventFormatProvider
.getInstance()
.resolveFormat(JsonFormat.CONTENT_TYPE)
.serialize(e))).log();
}
basicaly, I have a multicast replay sync. On some application events the event object is just wrapped to CloudEvent-object and pushed to the sync. On other end of the sync, connected clients receive only related events filtered by gamer id.
To the nginx configuration I added /api/events location. /api was already there
location /api/events {
#limit_req zone=mylimit;
proxy_pass http://localhost:9494;
proxy_buffering off;
proxy_cache off;
proxy_set_header Connection '';
proxy_http_version 1.1;
chunked_transfer_encoding off;
}
location /api {
#limit_req zone=mylimit;
proxy_pass http://localhost:9494;
}
On UI side I do create EventSource object and listen to the incoming events.
As mentioned above, if I test it alone, everything is working almost well. But as long as some other players starting to use the game, after 30-60 seconds every request to /api/ and /api/event/ returns Bad Gateway error. In backend log I do not see any errors. It seems that webflux stops to accept connection after a while.
If I use an old version of frontend, without subscription to the the /events/{game} endpoint, the game works well. No problems. It look like the subscribe service's method is wrong.