[JAVA] Erhalten Sie Flux-Ergebnisse von Spring Web Flux von JS mit der Fetch-API

Vor kurzem habe ich angefangen, verschiedene Dinge mit Spring Web Flux zu machen. Mit Spring WebFlux

	@GetMapping
	public Flux<Hoge> all() {
		return Flux.create(sink -> {
			// ...
		})
	}

Ich schreibe eine Beschreibung wie folgt in den Controller, aber wie erhalte ich diese im Browser? Ich habe ein wenig recherchiert und es versucht, also werde ich es schreiben.

Zunächst der Rückgabewert der von Flux zurückgegebenen Anforderung

Dieser Artikel wird sehr hilfreich sein. BLOG.IK.AM - First Spring WebFlux (Teil 1 - Try Spring WebFlux)

Es scheint, dass es im Format "Inhaltstyp: Text / Ereignis-Stream" ("Vom Server gesendetes Ereignis") oder im Format "Inhaltstyp: Anwendung / Stream + JSON" zurückgegeben wird.

Jeder scheint den Körper im folgenden Format zurückzugeben.

Wenn "Vom Server gesendetes Ereignis" gegoogelt wird, wird dies als Verwenden von vom Server gesendeten Ereignissen | MDN EventSource erläutert. Mir wurde schnell klar, dass ich es verwenden würde, aber was ist mit application / stream + json?

Zugriff mit application / stream + json über Fetch API

Mit der Fetch API können Sie anscheinend wie folgt darauf zugreifen.

  const url = "..."; //URL anzufordern
  const callback = json => { /* ...Logik zum Verarbeiten jeder Zeile JSON*/ };

  const decoder = new TextDecoder();

  const abortController = new AbortController();
  const { signal } = abortController;

  //Starten Sie den Abruf
  fetch(url, {
    signal,
    headers: {
      Accept: "application/stream+json"
    }
  }).then(response => {
    let buffer = "";
    /**
     *Verarbeiten Sie den aus dem Stream gelesenen Teilstring
     * @param {string}chunk String lesen
     * @returns {void}
     */
    function consumeChunk(chunk) {
      buffer += chunk;

      //Aufteilen nach Zeilenvorschubcode und Abrufen jeder JSON-Zeile
      // https://en.wikipedia.org/wiki/JSON_streaming#Line-delimited_JSON
      // http://jsonlines.org/
      const re = /(.*?)(\r\n|\r|\n)/g;
      let result;
      let lastIndex = 0;
      while ((result = re.exec(buffer))) {
        const data = result[1];
        if (data.trim()) {
          callback(JSON.parse(data)); //Übergeben Sie JSON an den Rückruf, um es zu verarbeiten.
        }
        ({ lastIndex } = re);
      }
      //Wenn der Zeilenvorschubcode nicht vorhanden ist, wird der Rest der Zeichenfolge mit dem nächsten Lesevorgang zur Verarbeitung kombiniert.
      buffer = buffer.slice(lastIndex);
    }
    //Erstellen Sie einen Stream-Reader
    const reader = response.body.getReader();
    //Prozesskörper lesen
    function readNext() {
      return reader.read().then(({ done, value }) => {
        if (!done) {
          // --Lesevorgang--
          consumeChunk(decoder.decode(value));
          return readNext();
        }

        // --Verarbeitung beenden--
        if (buffer.trim()) {
          //Wenn am Ende noch eine Zeichenfolge vorhanden ist, wird diese verarbeitet.
          //Wenn nach den letzten Zeilendaten kein Zeilenvorschubcode vorhanden ist, gelangen Sie hierher.
          //Es scheint nicht in Spring WebFlux zu passieren, aber http://jsonlines.org/Beim Betrachten hatte ich den Eindruck, dass der letzte Zeilenvorschubcode möglicherweise nicht verfügbar ist, daher werde ich ihn für alle Fälle implementieren.
          // `consumeChunk`Erkennt die Leitung nur, wenn Sie den Zeilenvorschubcode übergeben. Übergeben Sie daher den Zeilenvorschubcode.
          consumeChunk("\n");
        }

        return response;
      });
    }
    return readNext();
  });
}

Ich werde unten erklären, was ich tue.

AbortController

Dies hat nichts mit dem Abrufen von Flux zu tun, ist jedoch erforderlich, wenn Sie das Abrufen in der Mitte abbrechen möchten.

  const abortController = new AbortController();
  const { signal } = abortController;

  //Starten Sie den Abruf
  fetch(url, {
    signal,
    headers: {
      Accept: "application/stream+json"
    }
  })

Wenn Sie "Signal" an die Option "Abrufen" übergeben.

abortController.abort()

Dann können Sie fetch in der Mitte stoppen. Zu diesem Zeitpunkt scheint es noch besser zu sein, wenn die Java Controller-Seite den Prozess auch unterbricht, wenn er abgebrochen wird.

	@GetMapping
	public Flux<Hoge> all() {
		return Flux.create(sink -> {
			// ...
			if (sink.isCancelled()) {
				//Da es abgebrochen wurde, unterbrechen wir den Vorgang.
			}
			// ...
		})
	}

Sie können auch "onCancel" verwenden.

Erhalte eine Antwort in Stream

Siehe ReadableStream.getReader () | MDN.

Bei Verwendung von ReadableStream kann die Antwort von Stream wie folgt empfangen werden.

  fetch(...).then(response => {
    // ...
    //Erstellen Sie einen Stream-Reader
    const reader = response.body.getReader();
    function readNext() {
      return reader.read().then(({ done, value }) => {
        if (!done) {
          /*Wert handhaben*/
          // ...
          return readNext();
        }

        return; /*Ende*/
      });
    }
    return readNext();
  })

dekodiere value

Der von ReadableStream erhaltene Wert ist Uint8Array. Es scheint in docs / Web / JavaScript / Reference / Global_Objects / Uint8Array zu kommen. Verwenden Sie daher TextDecoder, um es zu dekodieren.

const decoder = new TextDecoder();

// ...

const sValue = decoder.decode(value) //Dekodiere Uint8Array und konvertiere es in einen String

Behandeln Sie "Chunk"

Bis zu diesem Punkt haben Sie den Punkt erreicht, an dem Sie den Inhalt von Stream als Zeichenfolge abrufen können. Hier werden wir den JSON tatsächlich analysieren.

    let buffer = "";
    /**
     *Verarbeiten Sie den aus dem Stream gelesenen Teilstring
     * @param {string}chunk String lesen
     * @returns {void}
     */
    function consumeChunk(chunk) {
      buffer += chunk;

      //Aufteilen nach Zeilenvorschubcode und Abrufen jeder JSON-Zeile
      // https://en.wikipedia.org/wiki/JSON_streaming#Line-delimited_JSON
      // http://jsonlines.org/
      const re = /(.*?)(\r\n|\r|\n)/g;
      let result;
      let lastIndex = 0;
      while ((result = re.exec(buffer))) {
        const data = result[1];
        if (data.trim()) {
          callback(JSON.parse(data)); //Übergeben Sie JSON an den Rückruf, um es zu verarbeiten.
        }
        ({ lastIndex } = re);
      }
      //Wenn der Zeilenvorschubcode nicht vorhanden ist, wird der Rest der Zeichenfolge mit dem nächsten Lesevorgang zur Verarbeitung kombiniert.
      buffer = buffer.slice(lastIndex);
    }

Ich mache etwas, aber ich werde es später erklären.

Teilen Sie JSON nach Zeilenvorschubcode

application / stream + json gibt die folgende Antwort wie oben beschrieben zurück.

{"key":"value" ... }
{"key":"value" ... }
{"key":"value" ... }

Diese Antwort ist ein JSON, der durch einen Zeilenvorschubcode getrennt ist. Daher ist jede Zeile JSON "JSON.parse", getrennt durch einen Zeilenvorschubcode.

Kombinieren Sie Stücke

Die mit "read ()" von ReadableStream erhaltene Zeichenfolge ist kein vollständiger JSON. Es ist nur ein Teil der gesamten Antwort.

Wenn der Block keinen Zeilenvorschubcode enthält, wird er daher zur Verarbeitung mit dem nächsten Block kombiniert.


Das ist alles für die Erklärung.

Impressionen

Die Methode, die EventSource verwendet, kann möglicherweise keine Header-Informationen übergeben und erfordert je nach einzubettender Anwendung möglicherweise Einfallsreichtum oder Änderungen. (Zum Beispiel bei der Verarbeitung auf der Serverseite unter der Annahme, dass das Authentifizierungstoken im Header platziert ist.)

Diese Methode zur Verwendung von Fetch API entspricht jedoch fast dem normalen HTTP-Zugriff, sodass ich den Eindruck hatte, dass sie problemlos verwendet werden kann (wenn der Browser Fetch API unterstützt).

Recommended Posts

Erhalten Sie Flux-Ergebnisse von Spring Web Flux von JS mit der Fetch-API
Holen Sie sich den Body-Teil von HttpResponse mit Spring Filter
◆ Rufen Sie die von Spring Boot erstellte API von React ab
Erstellen Sie einen Web-API-Server mit Spring Boot
[Spring Boot] Benutzerinformationen mit Rest API abrufen (Anfänger)
Testen Sie die Web-API mit junit
Beginnen Sie mit Spring Boot
API mit Spring + Vue.js verknüpfen
Implementieren Sie einen einfachen Web-REST-API-Server mit Spring Boot + MySQL
Beispiel für die Verwendung der Bulk-API von Salesforce vom Java-Client mit PK-Chunking
[Anfänger] Versuchen Sie, die REST-API für die Todo-App mit Spring Boot zu schreiben
Feder mit Kotorin --4 REST API Design
Erhalten Sie Validierungsergebnisse mit Spring Boot
Filtern Sie das Ergebnis von BindingResult [Spring]
[Java] Json von der URL mit der Standard-API (javax.script) abrufen und verarbeiten
[Java] Holen Sie sich MimeType aus dem Inhalt der Datei mit Apathce Tika [Kotlin]