Spring MVC Streaming und SSE-Anforderungsverarbeitung

Spring MVC Streaming und SSE Request Processing

1. Einführung

In diesem einfachen Lernprogramm wird die Verwendung mehrerer asynchroner und Streaming-Objekte in Spring MVC 5.x.x veranschaulicht.

Im Einzelnen werden drei Schlüsselklassen besprochen:

  • ResponseBodyEmitter

  • SseEmitter

  • StreamingResponseBody

Außerdem werden wir diskutieren, wie Sie mit ihnen über einen JavaScript-Client interagieren können.

2. ResponseBodyEmitter

ResponseBodyEmitter verarbeitet asynchrone Antworten.

Außerdem stellt es ein übergeordnetes Element für eine Reihe von Unterklassen dar. Eine davon wird im Folgenden näher erläutert.

2.1. Serverseite

Es ist besser, einResponseBodyEmitter zusammen mit einem eigenen dedizierten asynchronen Thread zu verwenden und mit einemResponseEntity zu umschließen (in das wir dieemitter direkt einfügen können):

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

Im obigen Beispiel alsowe can sidestep needing to use CompleteableFutures, kompliziertere asynchrone Versprechen oder Verwendung der Annotation@Async.

Stattdessen deklarieren wir einfach unsere asynchrone Entität und verpacken sie in ein neuesThread, das vonExecutorService. bereitgestellt wird

2.2. Client-Seite

Für die clientseitige Verwendung können wir eine einfache XHR-Methode verwenden und unsere API-Endpunkte wie bei einer normalen AJAX-Operation aufrufen:

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

SseEmitter is actually a subclass of ResponseBodyEmitter and provides additional Server-Sent Event (SSE) sofort einsatzbereite Unterstützung.

3.1. Serverseite

Werfen wir also einen kurzen Blick auf einen Beispiel-Controller, der diese leistungsstarke Entität nutzt:

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

Ziemlich normaler Tarif, aber wir werden einige Unterschiede zwischen diesem und unserem üblichen REST-Controller feststellen:

  • Zuerst geben wir aSseEmitter zurück

  • Außerdem verpacken wir die Kernantwortinformationen in eigeneThread

  • Schließlich senden wir Antwortinformationen mit emitter.send()

3.2. Client-Seite

Unser Client arbeitet diesmal etwas anders, da wir die kontinuierlich verbundeneServer-Sent Event-Bibliothek nutzen können:

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

Schließlichwe can use StreamingResponseBody to write directly to an OutputStream before passing that written information back to the client using a ResponseEntity.

4.1. Serverseite

@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. Client-Seite

Wie zuvor verwenden wir eine reguläre XHR-Methode, um auf den oben genannten Controller zuzugreifen:

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){ //... });

Schauen wir uns als nächstes einige erfolgreiche Verwendungen dieser Beispiele an.

5. Alles zusammenbringen

Nachdem wir unseren Server erfolgreich kompiliert und unseren Client oben ausgeführt haben (Zugriff auf die angegebenenindex.jsp), sollte in unserem Browser Folgendes angezeigt werden:

image Und Folgendes in unserem Terminal:

 

image

Wir können die Endpunkte auch direkt anrufen und sie als Streaming-Antworten in unserem Browser anzeigen.

6. Fazit

Während sichFuture undCompleteableFuture als robuste Ergänzungen zu Java und Spring erwiesen haben, stehen uns jetzt mehrere Ressourcen zur Verfügung, um asynchrone Daten und Streaming-Daten für hochkonkurrierende Webanwendungen angemessener zu verarbeiten.

Überprüfen Sie abschließend die vollständigen Codebeispieleover on GitHub.