[JAVA] Obtenez les résultats Flux de Spring Web Flux de JS avec Fetch API

Récemment, j'ai commencé à créer diverses choses en utilisant Spring Web Flux. Avec Spring WebFlux,

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

J'écris une description dans le contrôleur comme celle-ci, mais comment puis-je la recevoir dans le navigateur? J'ai fait un peu de recherche et l'ai essayé, donc je vais l'écrire.

Tout d'abord, la valeur de retour de la requête retournée par Flux

Cet article sera très utile. BLOG.IK.AM --First Spring WebFlux (Partie 1 - Essayez Spring WebFlux)

Il semble qu'il sera retourné au format Content-Type: text / event-stream ( Server-Sent Event) ou au format Content-Type: application / stream + json.

Chacun semble renvoyer le corps dans le format suivant.

Lorsque Evénement envoyé par le serveur 'est recherché sur Google, il est expliqué comme [Utilisation des événements envoyés par le serveur | MDN](https://developer.mozilla.org/ja/docs/Server-sent_events/Using_server-sent_events) [Source de l'événement] Je me suis vite rendu compte que j'allais l'utiliser, mais qu'en est-il de ʻapplication / stream + json?

Accès depuis Fetch API avec ʻapplication / stream + json`

En utilisant Fetch API, il semble que vous puissiez y accéder comme suit.

  const url = "..."; //URL à demander
  const callback = json => { /* ...Logique pour traiter chaque ligne JSON*/ };

  const decoder = new TextDecoder();

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

  //commencer à chercher
  fetch(url, {
    signal,
    headers: {
      Accept: "application/stream+json"
    }
  }).then(response => {
    let buffer = "";
    /**
     *Traiter la sous-chaîne lue dans le flux
     * @param {string}chunk Lire la chaîne
     * @returns {void}
     */
    function consumeChunk(chunk) {
      buffer += chunk;

      //Diviser par code de saut de ligne et récupérer chaque ligne JSON
      // 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)); //Passez JSON au rappel à traiter.
        }
        ({ lastIndex } = re);
      }
      //Si le code de saut de ligne n'existe pas, le reste de la chaîne sera combiné avec la prochaine lecture pour traitement.
      buffer = buffer.slice(lastIndex);
    }
    //Créer un lecteur de flux
    const reader = response.body.getReader();
    //Corps du processus de lecture
    function readNext() {
      return reader.read().then(({ done, value }) => {
        if (!done) {
          // --Processus de lecture--
          consumeChunk(decoder.decode(value));
          return readNext();
        }

        // --Terminer le traitement--
        if (buffer.trim()) {
          //S'il reste une chaîne de caractères à la fin, elle sera traitée.
          //S'il n'y a pas de code de saut de ligne après les dernières données de ligne, vous atteindrez ici.
          //Cela ne semble pas se produire dans Spring WebFlux, mais http://jsonlines.org/En regardant, j'ai eu l'impression que le dernier code de saut de ligne n'est peut-être pas disponible, donc je vais l'implémenter au cas où.
          // `consumeChunk`Ne reconnaît pas la ligne à moins que vous ne transmettiez le code de saut de ligne, alors transmettez le code de saut de ligne.
          consumeChunk("\n");
        }

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

J'expliquerai ce que je fais ci-dessous.

AbortController

Cela n'a rien à voir avec la récupération de Flux, mais c'est nécessaire si vous voulez annuler fetch au milieu.

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

  //commencer à chercher
  fetch(url, {
    signal,
    headers: {
      Accept: "application/stream+json"
    }
  })

Si vous passez signal à l'option fetch.

abortController.abort()

Ensuite, vous pouvez arrêter fetch au milieu. À ce stade, il semble encore mieux si le côté contrôleur Java interrompt également le processus s'il est annulé.

	@GetMapping
	public Flux<Hoge> all() {
		return Flux.create(sink -> {
			// ...
			if (sink.isCancelled()) {
				//Puisqu'il a été annulé, interrompons le processus.
			}
			// ...
		})
	}

Vous pouvez également utiliser ʻon Cancel`.

Recevoir une réponse dans Stream

Reportez-vous à ReadableStream.getReader () | MDN.

En utilisant ReadableStream, il semble que la réponse puisse être reçue par Stream comme suit.

  fetch(...).then(response => {
    // ...
    //Créer un lecteur de flux
    const reader = response.body.getReader();
    function readNext() {
      return reader.read().then(({ done, value }) => {
        if (!done) {
          /*Valeur de poignée*/
          // ...
          return readNext();
        }

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

décoder la valeur

La valeur obtenue de ReadableStream est [Uint8Array](https://developer.mozilla.org/en/ Il semble venir dans docs / Web / JavaScript / Reference / Global_Objects / Uint8Array), alors décodez-le en utilisant TextDecoder.

const decoder = new TextDecoder();

// ...

const sValue = decoder.decode(value) //Décodez Uint8Array et convertissez-le en chaîne

Gérer chunk

Jusqu'à présent, vous avez atteint le point où vous pouvez obtenir le contenu de Stream sous forme de chaîne de caractères. Ici, nous allons analyser le JSON.

    let buffer = "";
    /**
     *Traiter la sous-chaîne lue dans le flux
     * @param {string}chunk Lire la chaîne
     * @returns {void}
     */
    function consumeChunk(chunk) {
      buffer += chunk;

      //Diviser par code de saut de ligne et récupérer chaque ligne JSON
      // 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)); //Passez JSON au rappel à traiter.
        }
        ({ lastIndex } = re);
      }
      //Si le code de saut de ligne n'existe pas, le reste de la chaîne sera combiné avec la prochaine lecture pour traitement.
      buffer = buffer.slice(lastIndex);
    }

Je fais quelque chose, mais je l'expliquerai plus tard.

Diviser JSON par code de saut de ligne

ʻApplication / stream + json` renvoie la réponse suivante comme décrit ci-dessus.

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

Cette réponse est un JSON séparé par un code de saut de ligne. Ainsi, chaque ligne JSON est JSON.parse séparée par un code de saut de ligne.

Combiner des morceaux

La chaîne obtenue avec read () de ReadableStream n'est pas un JSON complet. Ce n'est qu'une partie de la réponse globale.

Par conséquent, si le bloc ne contient pas de code de saut de ligne, il est combiné avec le bloc suivant pour traitement.


C'est tout pour l'explication.

Impressions

La méthode utilisant EventSource peut ne pas être en mesure de transmettre les informations d'en-tête et peut nécessiter une certaine ingéniosité ou des modifications en fonction de l'application à incorporer. (Par exemple, lors du traitement côté serveur en supposant que le jeton d'authentification est placé dans l'en-tête.)

Cependant, cette méthode d'utilisation de Fetch API est presque la même que l'accès HTTP normal, j'ai donc eu l'impression qu'elle pouvait être utilisée facilement (si le navigateur supportait Fetch API).

Recommended Posts

Obtenez les résultats Flux de Spring Web Flux de JS avec Fetch API
Obtenez la partie Body de HttpResponse avec Spring Filter
◆ Obtenez l'API créée par Spring Boot à partir de React
Créer un serveur API Web avec Spring Boot
[Spring Boot] Obtenez des informations utilisateur avec l'API Rest (débutant)
Tester l'API Web avec junit
Démarrez avec Spring Boot
Lier l'API avec Spring + Vue.js
Implémentez un serveur API Web REST simple avec Spring Boot + MySQL
Exemple d'utilisation de l'API Bulk de Salesforce à partir d'un client Java avec PK-chunking
[Débutant] Essayez d'écrire l'API REST pour l'application Todo avec Spring Boot
Spring avec Kotorin - 4 Conception d'API REST
Obtenez des résultats de validation avec Spring Boot
Filtrer le résultat de BindingResult [Spring]
[Java] Obtenir et gérer Json à partir d'une URL avec une API standard (javax.script)
[Java] Récupère MimeType à partir du contenu du fichier avec Apathce Tika [Kotlin]