Maison > interface Web > js tutoriel > Communication Web en temps réel : interrogations longues/courtes, WebSockets et SSE expliqués + code Next.js

Communication Web en temps réel : interrogations longues/courtes, WebSockets et SSE expliqués + code Next.js

Linda Hamilton
Libérer: 2024-09-23 22:30:32
original
874 Les gens l'ont consulté

Real-Time Web Communication: Long/Short Polling, WebSockets, and SSE Explained + Next.js code

Histoire : la question inattendue de l'entretien

Il y a quelques mois, j'étais en plein entretien technique pour un poste front-end de niveau intermédiaire. Les choses se déroulaient bien jusqu'à ce que je reçoive une question qui m'a légèrement pris au dépourvu.

"Imaginez que vous ayez besoin d'une forme de communication constante pour vérifier quelque chose toutes les secondes jusqu'à ce que vous récupériez quelque chose dont vous avez besoin.

Par exemple, vous souhaitez continuer à vérifier si un paiement a réussi, comme dans une configuration de commerce électronique. Comment aborderiez-vous cela ? »

J'ai répondu prudemment : « Je pense que vous pourriez implémenter des WebSockets pour gérer cela. »

L'intervieweur a souri. "C'est une bonne solution, mais il existe d'autres options, sans doute meilleures, en fonction de la situation."

Et c'est à ce moment-là que nous avons plongé dans une conversation sur diverses approches de communication en temps réel, notamment Long Polling, Short Polling, WebSockets, et enfin, Server-Sent Events (SSE), qui est sans doute le meilleur choix pour un flux de données unidirectionnel, comme dans notre exemple de paiement.

Nous avons également discuté du choix de la bonne base de données pour gérer ces requêtes constantes mais légères sans épuiser les ressources du serveur. Dans ce contexte, Redis est apparu, connu pour sa simplicité et son efficacité dans la gestion de ce type de requêtes.

Cette conversation m'a marqué. J'ai réalisé que même si les WebSockets retiennent beaucoup d'attention, il existe un large éventail de techniques qui, une fois comprises, peuvent optimiser la façon dont nous gérons la communication en temps réel. Aujourd'hui, je souhaite décomposer ces quatre approches, quand utiliser chacune d'elles, ainsi que leurs avantages et inconvénients, d'une manière claire et engageante. À la fin, vous comprendrez parfaitement pourquoi les événements envoyés par le serveur (SSE) brillent souvent pour la communication unidirectionnelle en temps réel.

Avant de commencer, un grand merci à Marcos, l'ingénieur logiciel senior expérimenté qui a mené cette discussion et m'a inspiré à écrire cet article des mois plus tard, ce que j'ai beaucoup apprécié même si je n'ai pas obtenu le poste ! :)


Les quatre méthodes de communication en temps réel

Avant de passer à l’exemple SSE, décomposons les quatre méthodes dont nous avons discuté lors de cet entretien :

1. Sondage court

L'interrogation courte est probablement la méthode la plus simple. Cela implique de faire une requête au serveur à intervalles réguliers, en demandant : « Avez-vous de nouvelles données ? Le serveur répond avec l'état actuel, qu'il y ait ou non quelque chose de nouveau.

Avantages :

  • Facile à mettre en œuvre
  • Fonctionne avec les requêtes HTTP traditionnelles

Inconvénient :

  • C’est gourmand en ressources. Vous effectuez des demandes fréquentes, même lorsqu'aucune nouvelle donnée n'est disponible.
  • Peut augmenter la charge du serveur et le trafic réseau, ce qui devient inefficace pour les contrôles fréquents tels que les mises à jour du statut des paiements.

Idéal pour : Petites mises à jour de données à basse fréquence, comme le cours de la bourse, à mettre à jour toutes les minutes environ.

2. Long sondage

Les sondages longs vont encore plus loin avec les sondages courts. Le client demande à plusieurs reprises des informations au serveur, mais au lieu que le serveur réponde immédiatement, il conserve la connexion jusqu'à ce que de nouvelles données soient disponibles. Une fois les données renvoyées, le client ouvre immédiatement une nouvelle connexion et répète le processus.

Avantages :

  • Plus efficace qu'une interrogation courte car le serveur ne répond que lorsque cela est nécessaire - c'est vraiment rapide.
  • Compatible avec les navigateurs et les protocoles HTTP/HTTPS.

Inconvénient :

  • Nécessite toujours de rouvrir les connexions à plusieurs reprises, ce qui entraîne une inefficacité au fil du temps - des ressources coûteuses.
  • Légèrement plus complexe qu'un sondage court.

Idéal pour : Situations dans lesquelles une communication en temps réel est nécessaire mais où les WebSockets/SSE peuvent être excessifs (par exemple, les applications de chat).

3. WebSockets

Les WebSockets sont une solution plus moderne qui assure une communication en duplex intégral entre le client et le serveur. Une fois la connexion ouverte, les deux parties peuvent envoyer des données librement sans rétablir les connexions - ce qui définit une communication bidirectionnelle.

Avantages :

  • Véritable communication en temps réel avec une latence minimale.
  • Idéal pour la communication bidirectionnelle (par exemple, jeux en temps réel, applications de chat).

Inconvénient :

  • Plus complexe à mettre en œuvre que le sondage ou le SSE.
  • Les WebSockets ne sont pas toujours idéaux pour une communication unidirectionnelle ou des mises à jour moins fréquentes, car ils peuvent consommer des ressources en maintenant des connexions ouvertes.
  • Peut nécessiter une configuration du pare-feu.

Idéal pour : Applications nécessitant une communication bidirectionnelle constante, comme les jeux multijoueurs, les outils collaboratifs, les applications de chat ou les notifications en temps réel.

4. Événements envoyés par le serveur (SSE)

Enfin, nous arrivons aux Server-Sent Events (SSE), le héros de notre exemple de paiement. SSE crée une connexion unidirectionnelle dans laquelle le serveur envoie des mises à jour au client. Contrairement aux WebSockets, c'est unidirectionnel : le client ne renvoie pas de données.

Avantages :

  • Idéal pour les flux de données unidirectionnels tels que les flux d'actualités, les titres boursiers ou les mises à jour de l'état des paiements.
  • Léger et plus simple à mettre en œuvre que les WebSockets.
  • Utilise la connexion HTTP existante, elle est donc bien prise en charge et compatible avec le pare-feu.

Inconvénient :

  • Ne convient pas à la communication bidirectionnelle.
  • Certains navigateurs (en particulier les anciennes versions d'IE) ne prennent pas entièrement en charge SSE.

Idéal pour : Mises à jour en temps réel où le client n'a besoin que de recevoir des données, telles que les scores en direct, les notifications et notre exemple de statut de paiement.


SSE en action : statut des paiements en temps réel avec Next.js

Entrons dans le vif du sujet. J'ai créé une application Next.js simple pour simuler un processus de paiement en temps réel à l'aide de Server-Sent Events (SSE). Il montre exactement comment vous pouvez configurer une communication unidirectionnelle pour vérifier l'état d'un paiement et informer l'utilisateur lorsque le paiement réussit ou échoue.

C'est un peu un casse-tête de le configurer pour Next car il fonctionne un peu différemment du plain js donc vous pourrez me remercier plus tard !

Voici la configuration :

Frontend : composant de contrôle des transactions

Dans le composant suivant, nous avons une interface utilisateur simple affichant des boutons pour simuler différents types de transactions qui proviendraient d'une véritable API de passerelle (Pix, Stripe et un paiement par carte de crédit défaillant). Ces boutons déclenchent des mises à jour de l'état des paiements en temps réel via SSE.

C’est ici que la magie SSE opère. Lorsqu'un paiement est simulé, le client ouvre une connexion SSE pour écouter les mises à jour du serveur. Il gère différents statuts comme en attente, en transit, payé et en échec.

"use client";

import { useState } from "react";
import { PAYMENT_STATUSES } from "../utils/payment-statuses";

const paymentButtons = [
  {
    id: "pix",
    label: "Simulate payment with Pix",
    bg: "bg-green-200",
    success: true,
  },
  {
    id: "stripe",
    label: "Simulate payment with Stripe",
    bg: "bg-blue-200",
    success: true,
  },
  {
    id: "credit",
    label: "Simulate failing payment",
    bg: "bg-red-200",
    success: false,
  },
];

type transaction = {
  type: string;
  amount: number;
  success: boolean;
};

const DOMAIN_URL = process.env.NEXT_PUBLIC_DOMAIN_URL;

export function TransactionControl() {
  const [status, setStatus] = useState<string>("");
  const [isProcessing, setIsProcessing] = useState<boolean>(false);

  async function handleTransaction({ type, amount, success }: transaction) {
    setIsProcessing(true);
    setStatus("Payment is in progress...");

    const eventSource = new EventSource(
      `${DOMAIN_URL}/payment?type=${type}&amount=${amount}&success=${success}`
    );

    eventSource.onmessage = (e) => {
      const data = JSON.parse(e.data);
      const { status } = data;

      console.log(data);

      switch (status) {
        case PAYMENT_STATUSES.PENDING:
          setStatus("Payment is in progress...");
          break;
        case PAYMENT_STATUSES.IN_TRANSIT:
          setStatus("Payment is in transit...");
          break;
        case PAYMENT_STATUSES.PAID:
          setIsProcessing(false);
          setStatus("Payment completed!");
          eventSource.close();
          break;
        case PAYMENT_STATUSES.CANCELED:
          setIsProcessing(false);
          setStatus("Payment failed!");
          eventSource.close();
          break;
        default:
          setStatus("");
          setIsProcessing(false);
          eventSource.close();
          break;
      }
    };
  }

  return (
    <div>
      <div className="flex flex-col gap-3">
        {paymentButtons.map(({ id, label, bg, success }) => (
          <button
            key={id}
            className={`${bg} text-background rounded-full font-medium py-2 px-4
            disabled:brightness-50 disabled:opacity-50`}
            onClick={() =>
              handleTransaction({ type: id, amount: 101, success })
            }
            disabled={isProcessing}
          >
            {label}
          </button>
        ))}
      </div>

      {status && <div className="mt-4 text-lg font-medium">{status}</div>}
    </div>
  );
}

Copier après la connexion

Backend : implémentation SSE dans Next.js

Côté serveur, nous simulons un processus de paiement en envoyant des mises à jour périodiques du statut via SSE. Au fur et à mesure que la transaction progresse, le client recevra des informations indiquant si le paiement est toujours en attente, a été effectué ou a échoué.

import { NextRequest, NextResponse } from "next/server";

import { PAYMENT_STATUSES } from "../utils/payment-statuses";

export const runtime = "edge";
export const dynamic = "force-dynamic";

// eslint-disable-next-line @typescript-eslint/no-unused-vars
export async function GET(req: NextRequest, res: NextResponse) {
  const { searchParams } = new URL(req.url as string);
  const type = searchParams.get("type") || null;
  const amount = parseFloat(searchParams.get("amount") || "0");
  const success = searchParams.get("success") === "true";

  if (!type || amount < 0) {
    return new Response(JSON.stringify({ error: "invalid transaction" }), {
      status: 400,
      headers: {
        "Content-Type": "application/json",
      },
    });
  }
  const responseStream = new TransformStream();
  const writer = responseStream.writable.getWriter();
  const encoder = new TextEncoder();
  let closed = false;

  function sendStatus(status: string) {
    writer.write(
      encoder.encode(`data: ${JSON.stringify({ status, type, amount })}\n\n`)
    );
  }

  // Payment gateway simulation
  async function processTransaction() {
    sendStatus(PAYMENT_STATUSES.PENDING);

    function simulateSuccess() {
      setTimeout(() => {
        if (!closed) {
          sendStatus(PAYMENT_STATUSES.IN_TRANSIT);
        }
      }, 3000);

      setTimeout(() => {
        if (!closed) {
          sendStatus(PAYMENT_STATUSES.PAID);

          // Close the stream and mark closed to prevent further writes
          writer.close();
          closed = true;
        }
      }, 6000);
    }

    function simulateFailure() {
      setTimeout(() => {
        if (!closed) {
          sendStatus(PAYMENT_STATUSES.CANCELED);

          // Close the stream and mark closed to prevent further writes
          writer.close();
          closed = true;
        }
      }, 3000);
    }

    if (success === false) {
      simulateFailure();
      return;
    }

    simulateSuccess();
  }

  await processTransaction();

  // Return the SSE response
  return new Response(responseStream.readable, {
    headers: {
      "Access-Control-Allow-Origin": "*",
      Connection: "keep-alive",
      "X-Accel-Buffering": "no",
      "Content-Type": "text/event-stream; charset=utf-8",
      "Cache-Control": "no-cache, no-transform",
      "Content-Encoding": "none",
    },
  });
}
Copier après la connexion

Assurez-vous également d'ajouter le fichier .env.local avec ce contenu :

NEXT_PUBLIC_DOMAIN_URL='http://localhost:3000'
Copier après la connexion

Pourquoi SSE plutôt que WebSockets dans ce cas ?

Maintenant que nous avons vu comment l'implémenter, vous vous demandez peut-être : pourquoi utiliser SSE plutôt que WebSockets pour cela ? Voici pourquoi :

  • Communication unidirectionnelle : Dans notre scénario, le client n'a besoin que de recevoir des mises à jour sur l'état du paiement. Le client n'a pas besoin de renvoyer constamment des données au serveur, la simplicité de SSE s'adapte donc parfaitement.
  • Léger : Étant donné que SSE utilise une seule connexion HTTP pour diffuser les mises à jour, il est plus économe en ressources que les WebSockets, qui maintiennent une communication en duplex intégral.
  • Adapté aux pare-feu : SSE est plus facile à utiliser dans différents environnements réseau car il fonctionne via HTTP, qui est généralement ouvert dans les pare-feu, alors que les connexions WebSocket peuvent parfois rencontrer des problèmes.
  • Prise en charge des navigateurs : Bien qu'il ne soit pas aussi largement pris en charge que les WebSockets, SSE est pris en charge par les navigateurs modernes, ce qui le rend fiable pour la plupart des cas d'utilisation où des données unidirectionnelles sont nécessaires.

Conclusion : connaissez vos outils

Cette question d'entretien s'est transformée en une expérience d'apprentissage incroyable, m'ouvrant les yeux sur les différences subtiles entre les sondages longs, les sondages courts, les WebSockets et SSE. Chaque méthode a son temps et son lieu, et comprendre quand utiliser laquelle est crucial pour optimiser la communication en temps réel.

SSE n'est peut-être pas aussi glamour que WebSockets, mais lorsqu'il s'agit de communication efficace et unidirectionnelle, c'est l'outil parfait pour le travail, tout comme dans notre exemple de paiement de commerce électronique. La prochaine fois que vous créerez quelque chose qui nécessite des mises à jour en temps réel, ne vous contentez pas d'utiliser les WebSockets par défaut : pensez à SSE pour sa simplicité et son efficacité.

J'espère que cette plongée approfondie dans les techniques de communication en temps réel vous permettra de rester alerte pour votre prochain projet ou cette question délicate d'entretien !


Mettons-nous les mains dans le cambouis

Next.js + Dépôt d'exemples TypeScript : https://github.com/brinobruno/sse-next
Exemple de déploiement Next.js + TypeScript : https://sse-next-one.vercel.app/

Références

voici quelques sources et références faisant autorité que vous pourriez explorer pour des informations plus approfondies :

Documentation WebSockets et SSE :

MDN Web Docs : l'API WebSockets
MDN Web Docs : Utilisation des événements envoyés par le serveur

Itinéraires API suivants

Next.js : Routes API


Connectons-nous

Je partagerai mes réseaux sociaux pertinents au cas où vous souhaiteriez vous connecter :
Github
LinkedIn
Portefeuille

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

source:dev.to
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Derniers articles par auteur
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal