Événements envoyés par le serveur (SSE) dans JAX-RS

Événements envoyés par le serveur (SSE) dans JAX-RS

1. Vue d'ensemble

Les événements envoyés par le serveur (SSE) sont une spécification basée sur HTTP qui permet d'établir une connexion de longue durée et mono-canal entre le serveur et le client.

Le client lance la connexion SSE en utilisant le type de médiatext/event-stream dans l'en-têteAccept.

Plus tard, il est mis à jour automatiquement sans demander le serveur.

Nous pouvons vérifier plus de détails sur la spécification surthe official spec.

Dans ce didacticiel, nous présenterons la nouvelle implémentation JAX-RS 2.1 de SSE.

Par conséquent, nous allons voir comment nous pouvons publier des événements avec l'API du serveur JAX-RS. Nous allons également explorer comment nous pouvons les consommer soit par l'API client JAX-RS, soit simplement par un client HTTP comme l'outilcurl.

2. Comprendre les événements SSE

Un événement SSE est un bloc de texte composé des champs suivants:

  • Event:est le type de l'événement. Le serveur peut envoyer de nombreux messages de types différents et le client peut uniquement écouter un type particulier ou peut traiter différemment chaque type d'événement.

  • Data: le message envoyé par le serveur. Nous pouvons avoir plusieurs lignes de données pour le même événement

  • Id: l'id de l'événement, utilisé pour envoyer l'en-têteLast-Event-ID, après une nouvelle tentative de connexion. C'est utile car cela peut empêcher le serveur d'envoyer des événements déjà envoyés

  • Retry: le temps, en millisecondes, pour le client d'établir une nouvelle connexion lorsque le courant est perdu. Le dernier identifiant reçu sera automatiquement envoyé via le sheaderLast-Event-ID 

  • «:»: ceci est un commentaire et est ignoré par le client

En outre, deux événements consécutifs sont séparés par un double saut de ligne « ».

De plus, les données d'un même événement peuvent être écrites sur plusieurs lignes, comme dans l'exemple suivant:

event: stock
id: 1
: price change
retry: 4000
data: {"dateTime":"2018-07-14T18:06:00.285","id":1,
data: "name":"GOOG","price":75.7119}

event: stock
id: 2
: price change
retry: 4000
data: {"dateTime":"2018-07-14T18:06:00.285","id":2,"name":"IBM","price":83.4611}

DansJAX RS, un événement SSE est extrait par l'interfaceSseEvent _, or more precisely, by the two subinterfaces _OutboundSseEvent etInboundSseEvent.

While the OutboundSseEvent is used on the Server API and designs a sent event, the InboundSseEvent is used by the Client API and abstracts a received event.

3. Publication d'événements SSE

Maintenant que nous avons discuté de ce qu'est un événement SSE, voyons comment nous pouvons le créer et l'envoyer à un client HTTP.

3.1. Configuration du projet

Nous avons déjàa tutorial sur la configuration d'un projet Maven basé sur JAX RS. N'hésitez pas à y jeter un coup d'œil pour voir comment définir des dépendances et commencer à utiliser JAX RS.

3.2. Méthode de ressource SSE

Une méthode de ressource SSE est une méthode JAX RS qui:

  • Peut produire un type de médiatext/event-stream

  • A un paramètreSseEventSink injecté, où les événements sont envoyés

  • Peut également avoir un paramètreSse injecté qui est utilisé comme point d'entrée pour créer un générateur d'événements

@GET
@Path("prices")
@Produces("text/event-stream")
public void getStockPrices(@Context SseEventSink sseEventSink, @Context Sse sse) {
    //...
}

En conséquence, le client doit effectuer la première requête HTTP, avec l'en-tête HTTP suivant:

Accept: text/event-stream

3.3. L'instance SSE

Une instance SSE est un bean de contexte que JAX RS Runtime rendra disponible pour l’injection.

Nous pourrions l'utiliser comme une usine pour créer:

  • OutboundSseEvent.Builder – nous permet de créer des événements puis

  • SseBroadcaster – nous permet de diffuser des événements à plusieurs abonnés

Voyons comment cela fonctionne:

@Context
public void setSse(Sse sse) {
    this.sse = sse;
    this.eventBuilder = sse.newEventBuilder();
    this.sseBroadcaster = sse.newBroadcaster();
}

Maintenant, concentrons-nous sur le créateur d'événements. OutboundSseEvent.Builder est responsable de la création desOutboundSseEvent:

OutboundSseEvent sseEvent = this.eventBuilder
  .name("stock")
  .id(String.valueOf(lastEventId))
  .mediaType(MediaType.APPLICATION_JSON_TYPE)
  .data(Stock.class, stock)
  .reconnectDelay(4000)
  .comment("price change")
  .build();

Comme nous pouvons le voir,the builder has methods to set values for all event fields shown above. En outre, la méthodemediaType() est utilisée pour sérialiser l'objet Java de champ de données dans un format de texte approprié.

Par défaut, le type de média du champ de données esttext/plain. Par conséquent, il n’est pas nécessaire de le spécifier explicitement lorsqu’il s’agit du type de donnéesString.

Sinon, si nous voulons gérer un objet personnalisé, nous devons spécifier le type de média ou fournir unMessageBodyWriter.The JAX RS Runtime provides MessageBodyWriters for the most known media types personnalisé.

L'instance Sse possède également deux raccourcis de générateur pour créer un événement avec uniquement le champ de données, ou les champs de type et de données:

OutboundSseEvent sseEvent = sse.newEvent("cool Event");
OutboundSseEvent sseEvent = sse.newEvent("typed event", "data Event");

3.4. Envoi d'un événement simple

Maintenant que nous savons comment créer des événements et que nous comprenons le fonctionnement d’une ressource SSE. Envoyons un événement simple.

L'interfaceSseEventSink fait abstraction d'une seule connexion HTTP. JAX-RS Runtime peut le rendre disponible uniquement par injection dans la méthode de ressource SSE.

Envoyer un événement est alors aussi simple que d'appelerSseEventSink.send(). 

Dans l'exemple suivant, nous allons envoyer un lot de mises à jour de stock et éventuellement fermer le flux d'événements:

@GET
@Path("prices")
@Produces("text/event-stream")
public void getStockPrices(@Context SseEventSink sseEventSink /*..*/) {
    int lastEventId = //..;
    while (running) {
        Stock stock = stockService.getNextTransaction(lastEventId);
        if (stock != null) {
            OutboundSseEvent sseEvent = this.eventBuilder
              .name("stock")
              .id(String.valueOf(lastEventId))
              .mediaType(MediaType.APPLICATION_JSON_TYPE)
              .data(Stock.class, stock)
              .reconnectDelay(3000)
              .comment("price change")
              .build();
            sseEventSink.send(sseEvent);
            lastEventId++;
        }
     //..
    }
    sseEventSink.close();
}

Après l'envoi de tous les événements, le serveur ferme la connexion soit en invoquant explicitement la méthodeclose(), soit, de préférence, en utilisant lestry-with-resource, commeSseEventSink étend l'interfaceAutoClosable:

try (SseEventSink sink = sseEventSink) {
    OutboundSseEvent sseEvent = //..
    sink.send(sseEvent);
}

Dans notre exemple d'application, nous pouvons voir ceci en cours d'exécution si nous visitons:

http://localhost:9080/sse-jaxrs-server/sse.html

3.5. Événements de diffusion

La diffusion est le processus par lequel les événements sont envoyés à plusieurs clients simultanément. Ceci est accompli par l'APISseBroadcaster, et cela se fait en trois étapes simples:

Tout d'abord, nous créons l'objetSseBroadcaster à partir d'un contexte Sse injecté comme indiqué précédemment:

SseBroadcaster sseBroadcaster = sse.newBroadcaster();

Ensuite, les clients doivent s'inscrire pour pouvoir recevoir les événements Sse. Cela se fait généralement dans une méthode de ressource SSE où une instance de contexteSseEventSink est injectée:

@GET
@Path("subscribe")
@Produces(MediaType.SERVER_SENT_EVENTS)
public void listen(@Context SseEventSink sseEventSink) {
    this.sseBroadcaster.register(sseEventSink);
}

Et enfin,we can trigger the event publishing by invoking the broadcast() method:

@GET
@Path("publish")
public void broadcast() {
    OutboundSseEvent sseEvent = //...;
    this.sseBroadcaster.broadcast(sseEvent);
}

Cela enverra le même événement à chaqueSseEventSink. enregistré

Pour présenter la diffusion, nous pouvons accéder à cette URL:

http://localhost:9080/sse-jaxrs-server/sse-broadcast.html

Et nous pouvons ensuite déclencher la diffusion en appelant la méthode de ressource broadcast ():

curl -X GET http://localhost:9080/sse-jaxrs-server/sse/stock/publish

4. Consommer des événements SSE

Pour consommer un événement SSE envoyé par le serveur, nous pouvons utiliser n'importe quel client HTTP, mais pour ce didacticiel, nous utiliserons l'API client JAX RS.

4.1. API client JAX RS pour SSE

Pour commencer à utiliser l'API client pour SSE, nous devons fournir des dépendances pour la mise en œuvre du client JAX RS.

Ici, nous allons utiliser l'implémentation du client Apache CXF:


    org.apache.cxf
    cxf-rt-rs-client
    ${cxf-version}


    org.apache.cxf
    cxf-rt-rs-sse
    ${cxf-version}

LeSseEventSource est le cœur de cette API, et il est construit à partir desWebTarget.

Nous commençons par écouter les événements entrants qui sont extraits par l'interfaceInboundSseEvent:

Client client = ClientBuilder.newClient();
WebTarget target = client.target(url);
try (SseEventSource source = SseEventSource.target(target).build()) {
    source.register((inboundSseEvent) -> System.out.println(inboundSseEvent));
    source.open();
}

Une fois la connexion établie, le consommateur d'événements enregistré sera appelé pour chaque * InboundSseEvent *.

On peut alors utiliser la méthodereadData() pour lire les données originalesString:

String data = inboundSseEvent.readData();

Ou nous pouvons utiliser la version surchargée pour obtenir l'objet Java désérialisé en utilisant le type de support approprié:

Stock stock = inboundSseEvent.readData(Stock.class, MediaType.Application_Json);

Ici, nous venons de fournir un simple consommateur d’événements qui imprime l’événement entrant dans la console.

5. Conclusion

Dans ce didacticiel, nous avons expliqué comment utiliser les événements envoyés par le serveur dans JAX RS 2.1. Nous avons fourni un exemple qui montre comment envoyer des événements à un seul client et comment diffuser des événements vers plusieurs clients.

Enfin, nous avons utilisé ces événements à l’aide de l’API client JAX-RS.

Comme d'habitude, le code de ce tutoriel peut être trouvéover on Github.