Server versendete Ereignisse im Frühling

Vom Server gesendete Ereignisse im Frühjahr

1. Überblick

In diesem Tutorial erfahren Sie, wie Sie mit Spring auf Server-Sent-Events basierende APIs implementieren können.

Einfach ausgedrückt, Server-Sent-Events, kurz SSE, ist ein HTTP-Standard, mit dem eine Webanwendung einen unidirektionalen Ereignisstrom verarbeiten und Aktualisierungen erhalten kann, wenn der Server Daten ausgibt.

Die Version Spring 4.2 hat dies bereits unterstützt, beginnt jedoch mit Spring 5,we now have a more idiomatic and convenient way to handle it.

2. SSE mit Spring 5 Webflux

Um dies zu erreichen,we can make use of implementations such as the Flux class provided by the Reactor library, or potentially the ServerSentEvent entity, wodurch wir die Kontrolle über die Ereignismetadaten haben.

2.1. Stream-Ereignisse mitFlux

Flux ist eine reaktive Darstellung eines Ereignisstroms. Sie wird je nach angegebenem Anforderungs- oder Antwortmedientyp unterschiedlich behandelt.

Um einen SSE-Streaming-Endpunkt zu erstellen, müssen wir denW3C specifications folgen und seinen MIME-Typ alstext/event-stream festlegen:

@GetMapping(path = "/stream-flux", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux streamFlux() {
    return Flux.interval(Duration.ofSeconds(1))
      .map(sequence -> "Flux - " + LocalTime.now().toString());
}

Die Methodeinterval erstellt einenFlux, der inkrementelllong-Werte ausgibt. Dann ordnen wir diese Werte unserer gewünschten Ausgabe zu.

Starten Sie unsere Anwendung und probieren Sie sie aus, indem Sie dann den Endpunkt durchsuchen.

Wir werden sehen, wie der Browser auf Ereignisse reagiert, die vom Server Sekunde für Sekunde übertragen werden. Weitere Informationen zuFlux undReactor Core finden Sie unterthis post.

2.2. Verwendung desServerSentEvent-Elements

Wir werden jetzt unsere AusgabeString in einServerSentSevent-Objekt einwickeln und die Vorteile dieser Vorgehensweise untersuchen:

@GetMapping("/stream-sse")
public Flux> streamEvents() {
    return Flux.interval(Duration.ofSeconds(1))
      .map(sequence -> ServerSentEvent. builder()
        .id(String.valueOf(sequence))
          .event("periodic-event")
          .data("SSE - " + LocalTime.now().toString())
          .build());
}

Wie wir schätzen können, sindthere’re a couple of benefits of using the ServerSentEvent entity:

  1. Wir können die Ereignismetadaten verarbeiten, die wir in einem realen Fall benötigen würden

  2. Wir können die Medientypdeklaration "text/event-stream" ignorieren

In diesem Fall haben wirid,event name und vor allem die tatsächlichendata des Ereignisses angegeben.

Außerdem hätten wir eincomments-Attribut und einenretry-Wert hinzufügen können, die die Wiederverbindungszeit angeben, die beim Senden des Ereignisses verwendet werden soll.

2.3. Vom Server gesendete Ereignisse mit einem WebClient verarbeiten

Verbrauchen wir nun unseren Ereignisstrom mitWebClient.:

public void consumeServerSentEvent() {
    WebClient client = WebClient.create("http://localhost:8080/sse-server");
    ParameterizedTypeReference> type
     = new ParameterizedTypeReference>() {};

    Flux> eventStream = client.get()
      .uri("/stream-sse")
      .retrieve()
      .bodyToFlux(type);

    eventStream.subscribe(
      content -> logger.info("Time: {} - event: name[{}], id [{}], content[{}] ",
        LocalTime.now(), content.event(), content.id(), content.data()),
      error -> logger.error("Error receiving SSE: {}", error),
      () -> logger.info("Completed!!!"));
}

Mit der Methodesubscribekönnen wir angeben, wie wir vorgehen, wenn wir ein Ereignis erfolgreich empfangen, wenn ein Fehler auftritt und wenn das Streaming abgeschlossen ist.

In unserem Beispiel haben wir dieretrieve-Methode verwendet, mit der der Antworttext auf einfache und unkomplizierte Weise ermittelt werden kann.

Diese Methode löst automatischWebClientResponseException aus, wenn wir eine 4xx- oder 5xx-Antwort erhalten, es sei denn, wir behandeln die Szenarien, in denen eineonStatus-Anweisung hinzugefügt wird.

Andererseits hätten wir auch dieexchange-Methode verwenden können, die den Zugriff auf dieClientResponse ermöglicht und auch bei fehlgeschlagenen Antworten kein Fehlersignal gibt.

Wir müssen berücksichtigen, dass wir denServerSentEvent-Wrapper umgehen können, wenn wir die Ereignismetadaten nicht benötigen.

3. SSE-Streaming im Frühjahr MVC

Wie bereits erwähnt, wurde die SSE-Spezifikation seit Frühjahr 4.2 unterstützt, als die KlasseSseEmittereingeführt wurde.

In einfachen Worten, wir definieren einExecutorService, einen Thread, in demSseEmitter seine Arbeit zum Pushen von Daten erledigt, und geben die Emitter-Instanz zurück, wobei die Verbindung auf folgende Weise offen bleibt:

@GetMapping("/stream-sse-mvc")
public SseEmitter streamSseMvc() {
    SseEmitter emitter = new SseEmitter();
    ExecutorService sseMvcExecutor = Executors.newSingleThreadExecutor();
    sseMvcExecutor.execute(() -> {
        try {
            for (int i = 0; true; i++) {
                SseEventBuilder event = SseEmitter.event()
                  .data("SSE MVC - " + LocalTime.now().toString())
                  .id(String.valueOf(i))
                  .name("sse event - mvc");
                emitter.send(event);
                Thread.sleep(1000);
            }
        } catch (Exception ex) {
            emitter.completeWithError(ex);
        }
    });
    return emitter;
}

Stellen Sie immer sicher, dass Sie die richtigenExecutorServicefür Ihr Anwendungsfall-Szenario auswählen.

Wir können mehr über SSE in Spring MVC erfahren und uns andere Beispiele ansehen, indem wirthis interesting tutorial lesen.

4. Grundlegendes zu vom Server gesendeten Ereignissen

Nachdem wir nun wissen, wie SSE-Endpunkte implementiert werden, versuchen wir, ein bisschen tiefer zu gehen, indem wir einige zugrunde liegende Konzepte verstehen.

Eine SSE ist eine Spezifikation, die von den meisten Browsern verwendet wird, um das Streaming von Ereignissen jederzeit in eine Richtung zu ermöglichen.

Die "Ereignisse" sind nur ein Stream von UTF-8-codierten Textdaten, die dem in der Spezifikation definierten Format entsprechen.

Dieses Format besteht aus einer Reihe von Schlüsselwertelementen (id, retry, data und event, die den Namen angeben), die durch Zeilenumbrüche getrennt sind.

Kommentare werden ebenfalls unterstützt.

Die Spezifikation schränkt das Datennutzlastformat in keiner Weise ein. Wir können ein einfachesString oder eine komplexere JSON- oder XML-Struktur verwenden.

Ein letzter Punkt, den wir berücksichtigen müssen, ist der Unterschied zwischen der Verwendung von SSE-Streaming undWebSockets.

WährendWebSocketseine Vollduplex-Kommunikation (bidirektional) zwischen dem Server und dem Client bieten, verwendet SSE eine unidirektionale Kommunikation.

Außerdem istWebSockets kein HTTP-Protokoll und bietet im Gegensatz zu SSE keine Fehlerbehandlungsstandards.

5. Fazit

Zusammenfassend haben wir in diesem Artikel die Hauptkonzepte des SSE-Streamings kennengelernt. Dies ist zweifellos eine großartige Ressource, mit der wir Systeme der nächsten Generation erstellen können.

Wir sind jetzt in einer ausgezeichneten Position, um zu verstehen, was unter der Haube passiert, wenn wir dieses Protokoll verwenden.

Darüber hinaus haben wir die Theorie mit einigen einfachen Beispielen ergänzt, die inour Github repository zu finden sind.