Spring MVC Streaming et traitement des demandes SSE

Spring MVC Streaming et traitement des demandes SSE

1. introduction

Ce tutoriel simple illustre l'utilisation de plusieurs objets asynchrones et en flux dans Spring MVC 5.x.x.

Plus précisément, nous examinerons trois classes clés:

  • ResponseBodyEmitter

  • SseEmitter

  • StreamingResponseBody

Nous verrons également comment interagir avec eux à l'aide d'un client JavaScript.

2. ResponseBodyEmitter

ResponseBodyEmitter gère les réponses asynchrones.

En outre, il représente un parent pour un certain nombre de sous-classes - dont nous allons examiner de plus près ci-dessous.

2.1. Du côté serveur

Il vaut mieux utiliser unResponseBodyEmitter avec son propre thread asynchrone dédié et enveloppé avec unResponseEntity (dans lequel nous pouvons injecter directement lesemitter):

@Controller
public class ResponseBodyEmitterController {

    private ExecutorService executor
      = Executors.newCachedThreadPool();

    @GetMapping("/rbe")
    public ResponseEntity handleRbe() {
        ResponseBodyEmitter emitter = new ResponseBodyEmitter();
        executor(() -> {
            try {
                emitter.send(
                  "/rbe" + " @ " + new Date(), MediaType.TEXT_PLAIN);
                emitter.complete();
            } catch (Exception ex) {
                emitter.completeWithError(ex);
            }
        });
        return new ResponseEntity(emitter, HttpStatus.OK);
    }
}

Ainsi, dans l'exemple ci-dessus,we can sidestep needing to use CompleteableFutures, promesses asynchrones plus compliquées, ou utilisation de l'annotation@Async.

Au lieu de cela, nous déclarons simplement notre entité asynchrone et l'enveloppons dans un nouveauThread fourni par leExecutorService.

2.2. Côté client

Pour une utilisation côté client, nous pouvons utiliser une méthode XHR simple et appeler nos points de terminaison API comme dans une opération AJAX habituelle:

var xhr = function(url) {
    return new Promise(function(resolve, reject) {
        var xmhr = new XMLHttpRequest();
        //...
        xmhr.open("GET", url, true);
        xmhr.send();
       //...
    });
};

xhr('http://localhost:8080/javamvcasync/rbe')
  .then(function(success){ //... });

3. SseEmitter

Le support deSseEmitter is actually a subclass of ResponseBodyEmitter and provides additional Server-Sent Event (SSE) est prêt à l'emploi.

3.1. Du côté serveur

Alors, jetons un coup d'œil à un exemple de contrôleur exploitant cette puissante entité:

@Controller
public class SseEmitterController {
    private ExecutorService nonBlockingService = Executors
      .newCachedThreadPool();

    @GetMapping("/sse")
    public SseEmitter handleSse() {
         SseEmitter emitter = new SseEmitter();
         nonBlockingService.execute(() -> {
             try {
                 emitter.send("/sse" + " @ " + new Date());
                 // we could send more events
                 emitter.complete();
             } catch (Exception ex) {
                 emitter.completeWithError(ex);
             }
         });
         return emitter;
    }
}

Tarif assez standard, mais nous remarquerons quelques différences entre celui-ci et notre contrôleur REST habituel:

  • Tout d'abord, nous retournons unSseEmitter

  • En outre, nous enveloppons les informations de réponse de base dans ses propresThread

  • Enfin, nous envoyons les informations de réponse en utilisant emitter.send()

3.2. Côté client

Notre client fonctionne un peu différemment cette fois car nous pouvons exploiter la bibliothèqueServer-Sent Event connectée en permanence:

var sse = new EventSource('http://localhost:8080/javamvcasync/sse');
sse.onmessage = function (evt) {
    var el = document.getElementById('sse');
    el.appendChild(document.createTextNode(evt.data));
    el.appendChild(document.createElement('br'));
};

4. StreamingResponseBody

Enfin,we can use StreamingResponseBody to write directly to an OutputStream before passing that written information back to the client using a ResponseEntity.

4.1. Du côté serveur

@Controller
public class StreamingResponseBodyController {

    @GetMapping("/srb")
    public ResponseEntity handleRbe() {
        StreamingResponseBody stream = out -> {
            String msg = "/srb" + " @ " + new Date();
            out.write(msg.getBytes());
        };
        return new ResponseEntity(stream, HttpStatus.OK);
    }
}

4.2. Côté client

Comme auparavant, nous utiliserons une méthode XHR standard pour accéder au contrôleur ci-dessus:

var xhr = function(url) {
    return new Promise(function(resolve, reject) {
        var xmhr = new XMLHttpRequest();
        //...
        xmhr.open("GET", url, true);
        xmhr.send();
        //...
    });
};

xhr('http://localhost:8080/javamvcasync/srb')
  .then(function(success){ //... });

Examinons ensuite quelques utilisations réussies de ces exemples.

5. Réunir tout cela

Après avoir compilé avec succès notre serveur et exécuté notre client ci-dessus (en accédant auxindex.jsp fournis), nous devrions voir ce qui suit dans notre navigateur:

image Et ce qui suit dans notre terminal:

 

image

Nous pouvons également appeler directement les ordinateurs d'extrémité et les voir diffuser en continu dans notre navigateur.

6. Conclusion

Alors queFuture etCompleteableFuture ont fait leurs preuves d'ajouts robustes à Java et Spring, nous avons maintenant plusieurs ressources à notre disposition pour gérer plus adéquatement les données asynchrones et en streaming pour les applications Web hautement simultanées.

Enfin, consultez les exemples de code completsover on GitHub.