Événements envoyés par le serveur au printemps

Événements envoyés par le serveur au printemps

1. Vue d'ensemble

Dans ce didacticiel, nous verrons comment nous pouvons mettre en œuvre des API basées sur les événements envoyés par le serveur avec Spring.

En termes simples, Server-Sent-Events, ou SSE en abrégé, est une norme HTTP qui permet à une application Web de gérer un flux d'événements unidirectionnel et de recevoir des mises à jour chaque fois que le serveur émet des données.

La version Spring 4.2 le supportait déjà, mais à partir de Spring 5,we now have a more idiomatic and convenient way to handle it.

2. SSE avec Spring 5 Webflux

Pour y parvenir,we can make use of implementations such as the Flux class provided by the Reactor library, or potentially the ServerSentEvent entity, qui nous donne le contrôle sur les métadonnées des événements.

2.1. Événements de flux utilisantFlux

Flux est une représentation réactive d'un flux d'événements - il est géré différemment en fonction du type de média de demande ou de réponse spécifié.

Pour créer un point de terminaison de streaming SSE, nous devrons suivre lesW3C specifications et désigner son type MIME commetext/event-stream:

@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());
}

La méthodeinterval crée unFlux qui émet des valeurslong de manière incrémentielle. Ensuite, nous mappons ces valeurs à notre sortie souhaitée.

Lançons notre application et essayons-la en parcourant ensuite le point de terminaison.

Nous verrons comment le navigateur réagit aux événements poussés seconde par seconde par le serveur. Pour plus d'informations surFlux et lesReactor Core, nous pouvons consulterthis post.

2.2. Utilisation de l'élémentServerSentEvent

Nous allons maintenant envelopper notre sortieString dans un objetServerSentSevent, et examiner les avantages de faire ceci:

@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());
}

Comme nous pouvons l'apprécier,there’re a couple of benefits of using the ServerSentEvent entity:

  1. nous pouvons gérer les métadonnées des événements, dont nous aurions besoin dans un scénario réel

  2. nous pouvons ignorer la déclaration de type de média «text/event-stream»

Dans ce cas, nous avons spécifié unid, unevent name et, surtout, ledata réel de l'événement.

De plus, nous aurions pu ajouter un attributcomments et une valeurretry, qui spécifieront l'heure de reconnexion à utiliser lors de la tentative d'envoi de l'événement.

2.3. Consommation des événements envoyés par le serveur avec un client Web

Maintenant, consommons notre flux d'événements avec unWebClient .:

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!!!"));
}

La méthodesubscribe nous permet d'indiquer comment nous procéderons lorsque nous recevons un événement avec succès, lorsqu'une erreur se produit et lorsque la diffusion est terminée.

Dans notre exemple, nous avons utilisé la méthoderetrieve, qui est un moyen simple et direct d'obtenir le corps de la réponse.

Cette méthode lance automatiquement unWebClientResponseException si nous recevons une réponse 4xx ou 5xx sauf si nous gérons les scénarios en ajoutant une instructiononStatus.

D'un autre côté, nous aurions pu également utiliser la méthodeexchange, qui donne accès auxClientResponse et n'émet pas de signal d'erreur en cas d'échec des réponses.

Nous devons garder à l'esprit que nous pouvons contourner le wrapperServerSentEvent si nous n'avons pas besoin des métadonnées d'événement.

3. SSE en streaming au printemps MVC

Comme nous l'avons dit, la spécification SSE était prise en charge depuis le printemps 4.2, lorsque la classeSseEmitter a été introduite.

En termes simples, nous allons définir unExecutorService, un thread où leSseEmitter fera son travail en poussant des données, et retournera l'instance de l'émetteur, en gardant la connexion ouverte de cette manière:

@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;
}

Assurez-vous toujours de choisir les bonsExecutorService pour votre scénario d'utilisation.

Nous pouvons en savoir plus sur SSE dans Spring MVC et jeter un œil à d'autres exemples en lisantthis interesting tutorial.

4. Comprendre les événements envoyés par le serveur

Maintenant que nous savons comment mettre en œuvre les points de terminaison SSE, essayons d'aller un peu plus loin en comprenant certains concepts sous-jacents.

Une SSE est une spécification adoptée par la plupart des navigateurs pour permettre la diffusion en continu d'événements à tout moment.

Les «événements» ne sont qu’un flux de données textuelles codées en UTF-8 qui suivent le format défini par la spécification.

Ce format consiste en une série d'éléments clé-valeur (id, retry, data et event, qui indique le nom), séparés par des sauts de ligne.

Les commentaires sont également pris en charge.

La spécification ne restreint en aucune façon le format de la charge utile des données; nous pouvons utiliser un simpleString ou une structure JSON ou XML plus complexe.

Un dernier point que nous devons prendre en compte est la différence entre l'utilisation du streaming SSE etWebSockets.

Alors queWebSockets offre une communication bidirectionnelle (bidirectionnelle) entre le serveur et le client, tandis que SSE utilise une communication unidirectionnelle.

De plus,WebSockets n'est pas un protocole HTTP et, contrairement à SSE, il n'offre pas de normes de gestion des erreurs.

5. Conclusion

Pour résumer, dans cet article, nous avons appris les principaux concepts du streaming SSE, qui est sans aucun doute une excellente ressource qui nous permettra de créer des systèmes de nouvelle génération.

Nous sommes maintenant dans une excellente position pour comprendre ce qui se passe sous le capot lorsque nous utilisons ce protocole.

De plus, nous avons complété la théorie avec quelques exemples simples, que l'on peut trouver dansour Github repository.