Search code examples
javaspringspring-mvcjersey-2.0server-sent-events

Cannot find Spring's SSEmitter Event "name" property in the resulting event-stream value


I'm using Server Sent Events (SSE) within the Spring Framework. I'm able to register to those streams with javascript code in the chrome browser and in the backend using jersey-media-sse

  <dependency>
        <groupId>org.glassfish.jersey.media</groupId>
        <artifactId>jersey-media-sse</artifactId>
        <version>2.21</version>
    </dependency>

The problem now is that Spring's SSEmitter logic is not setting the name attribute of the event as expected. Using the setter in the sending logic below seems intuitive - but the resulting value on the consuming/receiving client side yields name:null. The attribute still is available - but hard to find in one of three data fields of a big JSON String.

Please read the following to understand the issue in detail


Here is a snippet of the Endpoint which should yield a SSEvent stream

  @CrossOrigin(Resources.Origins.FRONTEND)
  @GetMapping(GET_LOG + "/stream")
  public SseEmitter subscribeToLogEventStream() {
    Optional<SseEmitter> sseEmitter = createAndSubscribe(EVENT_IDENTIFIER);
    return sseEmitter.orElse(null);
  }

Here is the logic for sending an event

 void sendEvent(SseEmitter sseEmitter, String toolId, String message) {
    SseEmitter.SseEventBuilder sseEventBuilder = SseEmitter.event()
            .data(message, MediaType.TEXT_EVENT_STREAM)
            .name(toolId);
    Set<ResponseBodyEmitter.DataWithMediaType> event = sseEventBuilder.build();

    try {
      sseEmitter.send(event);
      log.debug("Sending SSEvent");
    } catch (ClientAbortException e) {
      log.error("Stream connection is not longer established. Aborting.", e);
    } catch (IOException e) {
      log.error("Server sent event emitter failed", e);
    }

  }

Receiving this event in the backend the InboundEvent object yields these properties

enter image description here

Please note that the name field happens to be empty although it is set in the sending logic

 SseEmitter.SseEventBuilder sseEventBuilder = SseEmitter.event()
                .data(message, MediaType.TEXT_EVENT_STREAM)
                .name(toolId);

Actually the name field is defined within the 3rd data field of the following JSON String.

The weirdly formatted event stream data

    [
  {
    "data": "data:",
    "mediaType": {
      "type": "text",
      "subtype": "plain",
      "parameters": {
        "charset": "UTF-8"
      },
      "qualityValue": 1.0,
      "concrete": true,
      "wildcardType": false,
      "charSet": "UTF-8",
      "charset": "UTF-8",
      "wildcardSubtype": false
    }
  },
  {
    "data": "Wed Jan 24 18:17:13 CET 2018>ERROR: Finishing execution but still unhappy",
    "mediaType": {
      "type": "text",
      "subtype": "event-stream",
      "parameters": {},
      "qualityValue": 1.0,
      "concrete": true,
      "wildcardType": false,
      "charSet": null,
      "charset": null,
      "wildcardSubtype": false
    }
  },
  {
    "data": "\nevent:foo\n\n", // <<<< Name definition (suffixed with event)
    "mediaType": {
      "type": "text",
      "subtype": "plain",
      "parameters": {
        "charset": "UTF-8"
      },
      "qualityValue": 1.0,
      "concrete": true,
      "wildcardType": false,
      "charSet": "UTF-8",
      "charset": "UTF-8",
      "wildcardSubtype": false
    }
  }
]


Solution

  • I just found out that the .send() method does not expect an event created by the SseEmitter.SseEventBuilder - no it expects the builder itself. That was kinda not intuitive for me and therefore not realised for a long time.

     void sendEvent(SseEmitter sseEmitter, String toolId, String message) {
        SseEmitter.SseEventBuilder sseEventBuilder = SseEmitter.event()
                .data(message)
                .name(toolId);
    
        try {
          sseEmitter.send(sseEventBuilder );
          log.debug("Sending SSEvent");
        } catch (ClientAbortException e) {
          log.error("Stream connection is not longer established. Aborting.", e);
        } catch (IOException e) {
          log.error("Server sent event emitter failed", e);
        }
    
      }