Hoe u WebSockets in Node.js kunt gebruiken om real-time apps te maken

Deze tutorial demonstreert hoe u WebSockets in Node.js gebruikt voor tweerichtingscommunicatie tussen een browser en een server. De techniek is essentieel voor snelle, real-time toepassingen zoals dashboards, chat-apps en multiplayer games.

Table of Contents

Het Web is gebaseerd op aanvraag-antwoord HTTP berichten. Uw browser maakt een URL-aanvraag en een server reageert met gegevens. Dat kan leiden tot verdere browseraanvragen en serverreacties voor afbeeldingen, CSS, JavaScript, enz., maar de server kan geen gegevens willekeurig naar een browser sturen.

Long polling Ajax-technieken kunnen webapps doen lijken te updaten in realtime, maar het proces is te beperkend voor echte realtime applicaties. Het afvragen elke seconde zou op bepaalde momenten inefficiënt en op andere momenten te traag zijn.

Na een eerste verbinding van een browser, server-sent events zijn een standaard (gestroomde) HTTP-respons die berichten van de server op elk moment kan sturen. Echter, het kanaal is enkellopend en de browser kan geen berichten terugsturen. Voor echt snelle tweerichtingscommunicatie heb je WebSockets nodig.

WebSockets Overzicht

De term WebSocket verwijst naar een TCP communicatieprotocol over ws:// of het veilige en gecodeerde wss://. Het is anders dan HTTP, hoewel het kan draaien op poort 80 of 443 om ervoor te zorgen dat het werkt op plaatsen die niet-webverkeer blokkeren. De meeste browsers uitgebracht sinds 2012 ondersteunen het WebSocket protocol.

In een typische realtime webapplicatie moet je minstens één webserver hebben om webinhoud te dienen (HTML, CSS, JavaScript, afbeeldingen, enz.) en één WebSocket server om tweerichtingscommunicatie te verwerken.

De browser maakt nog steeds een initiële WebSocket-aanvraag aan een server, die een communicatiekanaal opent. Zowel de browser als de server kunnen vervolgens een bericht op dat kanaal sturen, wat een gebeurtenis op het andere apparaat veroorzaakt.

Communicatie met andere verbonden browsers

Na de initiële aanvraag kan de browser berichten verzenden en ontvangen van/naar de WebSocket-server. De WebSocket-server kan berichten verzenden en ontvangen van/naar een van zijn verbonden clientbrowsers.

P2P-communicatie is niet mogelijk. BrowserA kan BrowserB niet direct berichten, zelfs niet als ze op hetzelfde apparaat draaien en verbonden zijn met dezelfde WebSocket-server! BrowserA kan alleen een bericht naar de server sturen en hopen dat het op de nodige wijze naar andere browsers wordt doorgestuurd.

WebSocket Server Support

Node.js heeft nog geen native WebSocket-ondersteuning, hoewel er geruchten zijn dat het snel aankomt! Voor dit artikel gebruik ik de derde-partij ws module, maar er zijn tientallen anderen.

Native WebSocket-ondersteuning is beschikbaar in de Deno en Bun JavaScript-runtimes.

WebSocket-bibliotheken zijn beschikbaar voor runtimes zoals PHP, Python, en Ruby. Derden SaaS-opties zoals Pusher en PubNub bieden ook gehoste WebSocket-services.

WebSocket Demonstratie Snelstart

Chat-apps zijn het Hello, World! van WebSocket-demonstraties, dus mijn excuses voor:

  1. Onorigineel zijn. Dat gezegd hebbende, chat-apps zijn een geweldige manier om de concepten uit te leggen.

  2. Het niet kunnen bieden van een volledig gehoste online oplossing. Ik wil liever geen controle en moderering hoeven uit te voeren op een stroom van anonieme berichten!

Kloon of download de node-wschat repository van GitHub:

git clone https://github.com/craigbuckler/node-wschat

Installeer de Node.js afhankelijkheden:

cd node-wschat
npm install

Start de chat-applicatie:

npm start

Open http://localhost:3000/ in verschillende browsers of tabbladen (je kunt ook je chatnaam op de query string definiëren — zoals http://localhost:3000/?Craig). Typ iets in één venster en druk op SEND of druk op Enter en je zult zien dat het verschijnt in alle verbonden browsers.

Overzicht van Node.js-code

Het index.js startbestand van de Node.js-toepassing start twee servers:

  1. Een Express-app die draait op http://localhost:3000/ met een EJS-sjabloon om een enkele pagina te dienen met client-side HTML, CSS en JavaScript. De browser JavaScript gebruikt de WebSocket API om de eerste verbinding te maken en vervolgens berichten te sturen en ontvangen.

  2. A WebSocket server running at ws://localhost:3001/, which listens for incoming client connections, handles messages, and monitors disconnections. The full code:

    // WebSocket server
    import WebSocket, { WebSocketServer } from 'ws';
    
    const ws = new WebSocketServer({ port: cfg.wsPort });
    
    // client connection
    ws.on('connection', (socket, req) => {
    
      console.log(`connection from ${ req.socket.remoteAddress }`);
    
      // received message
      socket.on('message', (msg, binary) => {
    
        // broadcast to all clients
        ws.clients.forEach(client => {
          client.readyState === WebSocket.OPEN && client.send(msg, { binary });
        });
    
      });
    
      // closed
      socket.on('close', () => {
        console.log(`disconnection from ${ req.socket.remoteAddress }`);
      });
    
    });

De Node.js ws bibliotheek:

  • Genereert een "connection" gebeurtenis wanneer een browser verbinding wil maken. De event handler ontvangt een socket object dat gebruikt wordt om te communiceren met dat individuele apparaat. Dit moet behouden worden gedurende de hele levensduur van de verbinding.

  • Genereert een socket "message" gebeurtenis wanneer een browser een bericht stuurt. De event handler zendt het bericht terug naar elke verbonden browser (inclusief degene die het stuurde).

  • Genereert een socket "close" gebeurtenis wanneer de browser verbinding verbreekt — meestal wanneer de tab wordt gesloten of opnieuw geladen.

Client-side JavaScript Code Overzicht

Het toepassingsbestand static/main.js voert een wsInit() functie uit en geeft het adres van de WebSocket-server door (domein van de pagina plus een poortwaarde gedefinieerd in het HTML-paginavoorbeeld):

wsInit(`ws://${ location.hostname }:${ window.cfg.wsPort }`);

// WebSocket-communicatie afhandelen
function wsInit(wsServer) {

  const ws = new WebSocket(wsServer);

  // verbinding maken met server
  ws.addEventListener('open', () => {
    sendMessage('entered the chat room');
  });

Het open gebeurtenisevenement treedt op wanneer de browser verbinding maakt met de WebSocket-server. De eventhandler functie stuurt een binnen gekomen in de chatkamer bericht door middel van sendMessage() aan te roepen:

// bericht versturen
function sendMessage(setMsg) {

  let
    name = dom.name.value.trim(),
    msg =  setMsg || dom.message.value.trim();

  name && msg && ws.send( JSON.stringify({ name, msg }) );

}

De sendMessage() functie haalt de gebruikersnaam en het bericht op uit het HTML-formulier, hoewel het bericht kan worden overschreven door een doorgegeven setMsg argument. De waarden worden omgezet in een JSON-object en naar de WebSocket-server verstuurd met behulp van de ws.send() methode.

De WebSocket-server ontvangt het inkomende bericht, wat de "message" handler (zie boven) activeert en het terug uitzendt naar alle browsers. Dit activeert een "message" gebeurtenis op elk cliënt:

// bericht ontvangen
ws.addEventListener('message', e => {

  try {

    const
      chat = JSON.parse(e.data),
      name = document.createElement('div'),
      msg  = document.createElement('div');

    name.className = 'name';
    name.textContent = (chat.name || 'unknown');
    dom.chat.appendChild(name);

    msg.className = 'msg';
    msg.textContent = (chat.msg || 'said nothing');
    dom.chat.appendChild(msg).scrollIntoView({ behavior: 'smooth' });

  }
  catch(err) {
    console.log('invalid JSON', err);
  }

});

De eventhandler ontvangt de verzonden JSON-gegevens op de .data eigenschap van het gebeurtenisobject. De functie parseert het naar een JavaScript-object en updatet het chatvenster.

Ten slotte worden nieuwe berichten verstuurd met behulp van de sendMessage() functie telkens wanneer de "submit" eventhandler van het formulier wordt geactiveerd:

// formulier verzenden
dom.form.addEventListener('submit', e => {
  e.preventDefault();
  sendMessage();
  dom.message.value = '';
  dom.message.focus();
}, false);

Foutafhandeling

Een "error" gebeurtenis treedt op wanneer WebSocket-communicatie mislukt. Dit kan op de server worden afgehandeld:

// WebSocket-fout afhandelen op server
socket.on('error', e => {
  console.log('WebSocket error:', e);
});

en/of de client:

// WebSocket-fout afhandelen op client
ws.addEventListener('error', e => {
  console.log('WebSocket error:', e);
})

Alleen de client kan de verbinding herstellen door de new WebSocket() constructor opnieuw uit te voeren.

Sluiten van verbindingen

Beide apparaten kunnen de WebSocket op elk moment sluiten met behulp van de .close() methode van de verbinding. U kunt eventueel een code integer en reden string (max 123 bytes) argumenten opgeven, die naar het andere apparaat worden overgebracht voordat het wordt afgesloten.

Geavanceerde WebSockets

Het beheren van WebSockets is gemakkelijk in Node.js: één apparaat stuurt een bericht met behulp van een .send() methode, die een "message" gebeurtenis op het andere apparaat triggert. Hoe elk apparaat die berichten creëert en erop reageert, kan echter moeilijker zijn. De volgende secties beschrijven problemen die u misschien moet overwegen.

WebSocket-beveiliging

Het WebSocket-protocol regelt geen autorisatie of authenticatie. U kunt niet garanderen dat een inkomende communicatieverzoek afkomstig is van een browser of een gebruiker die is ingelogd op uw webtoepassing – vooral wanneer de web- en WebSocket-servers op verschillende apparaten kunnen zijn. De initiële verbinding ontvangt een HTTP-header met cookies en de server Origin, maar het is mogelijk deze waarden te spoofen.

De volgende techniek zorgt ervoor dat u WebSocket-communicatie beperkt tot geautoriseerde gebruikers:

  1. Voordat de eerste WebSocket-aanvraag wordt gedaan, contacteert de browser de HTTP-webserver (misschien met behulp van Ajax).

  2. De server controleert de gebruikersreferenties en retourneert een nieujevergunningsticket. Het ticket verwijst meestal naar een databaserecord met daarin de gebruikers-ID, IP-adres, aanvraagtijd, sessietijd, en eventueel andere vereiste gegevens.

  3. De browser geeft het ticket door aan de WebSocket-server in de initiële handshakes.

  4. De WebSocket-server controleert het ticket en kijkt naar factoren zoals het IP-adres, vervaltijd, enzovoort, voordat de verbinding wordt toegestaan. Het voert de WebSocket .close() methode uit wanneer een ticket ongeldig is.

  5. De WebSocket-server moet misschien af en toe de databaserecord opnieuw controleren om ervoor te zorgen dat de gebruikerssessie geldig blijft.

Belangrijk is, valideer altijd inkomende gegevens:

  • Net als HTTP is de WebSocket-server vatbaar voor SQL-injectie en andere aanvallen.

  • De client mag geen rawe waarden in de DOM injecteren of JavaScript-code uitvoeren.

Apart versus meerdere WebSocket-serverinstanties

Overweeg een online multiplayer game. Het spel heeft veel universums die afzonderlijke instanties van het spel spelen: universeA, universeB en universeC. Een speler verbindt zich met een enkel universum:

  • universeA: gejoint door player1, player2 en player3
  • universeB: gejoint door player99

Je zou het volgende kunnen implementeren:

  1. A separate WebSocket server for each universe.

    A player action in universeA would never be seen by those in universeB. However, launching and managing separate server instances could be difficult. Would you stop universeC because it has no players, or continue to manage that resource?

  2. Gebruik een enkele WebSocket-server voor alle game universums.

    Dit gebruikt minder resources en is makkelijker te beheren, maar de WebSocket-server moet bijhouden welk universum elke speler bezoekt. Wanneer player1 een actie uitvoert, moet deze worden uitgezonden naar player2 en player3 maar niet naar player99.

Meerdere WebSocket servers

Het voorbeeld chat applicatie kan honderden gelijktijdige gebruikers aan, maar zal crashen zodra de populariteit en geheugengebruik boven kritieke drempels stijgt. U zult uiteindelijk horizontaal moeten schalen door verder servers toe te voegen.

Elke WebSocket server kan alleen zijn eigen verbonden clients beheren. Een bericht dat vanuit een browser naar serverX wordt verzonden, kan niet worden uitgezonden naar degenen die verbonden zijn met serverY. Het kan nodig worden om achterkant uitgever-abonnee (pub-sub) berichtensystemen te implementeren. Bijvoorbeeld:

  1. WebSocket serverX wil een bericht naar alle clients sturen. Het publiceert het bericht op het pub-sub systeem.

  2. Alle WebSocket servers die zijn geabonneerd op het pub-sub systeem ontvangen een nieuw bericht evenement (inclusief serverX). Elke kan het bericht afhandelen en het uitzenden naar hun verbonden clients.

Efficiëntie van WebSocket berichten

WebSocket-communicatie is snel, maar de server moet alle verbonden clients beheren. Je moet de mechanica en efficiëntie van berichten overwegen, vooral bij het bouwen van multiplayeractiongames:

  • Hoe synchroniseer je een speler​​’s acties over alle clientapparaten?

  • Als speler1 zich op een andere locatie bevindt dan speler2, is het nodig om speler2 informatie te sturen over acties die ze niet kunnen zien?

  • Hoe ga je om met netwerklatentie — of communicatievertraging? Zou iemand met een snelle machine en verbinding een oneerlijk voordeel hebben?

Snelle games moeten compromissen sluiten. Denk eraan alsof je het spel op je lokale apparaat speelt, maar sommige objecten worden beïnvloed door de activiteiten van anderen. In plaats van de exacte positie van elk object altijd te sturen, sturen games vaak eenvoudigere, minder frequente berichten. Bijvoorbeeld:

  • objectX is verschenen op puntX
  • objectY heeft een nieuwe richting en snelheid
  • objectZ is vernietigd

Elke clientgame vult de leemtes op. Als objectZ ontploft, maakt het niet uit of de explosie er op elk apparaat anders uitziet.

Conclusie

Node.js maakt het gemakkelijk om WebSockets te behandelen. Het maakt real-time applicaties niet noodzakelijk gemakkelijker te ontwerpen of te coderen, maar de technologie zal je niet tegenhouden!

De belangrijkste nadelen:

  • WebSockets vereisen hun eigen afzonderlijke serverinstantie. Ajax Fetch() verzoeken en server-sent events kunnen worden behandeld door de webserver die je al gebruikt.

  • WebSocket servers vereisen hun eigen beveiliging en autorisatiecontroles.

  • Verloren WebSocket verbindingen moeten handmatig worden hersteld.

Maar laat dat je niet afschrikken!

Veelgestelde Vragen (FAQs) over Real-Time Apps met WebSockets en Server-Sent Events

Hoe verschillen WebSockets van HTTP qua prestaties en functionaliteit?

WebSockets bieden een full-duplex communicatiekanaal via één TCP-verbinding, wat betekent dat gegevens tegelijkertijd kunnen worden verzonden en ontvangen. Dit is een belangrijke verbetering ten opzichte van HTTP, waarbij elke aanvraag een nieuwe verbinding vereist. WebSockets stellen ook voor realtime gegevensoverdracht, waardoor ze ideaal zijn voor toepassingen die directe updates vereisen, zoals chat-apps of live sportupdates. Aan de andere kant is HTTP stateless en is elke aanvraag-antwoordpaar onafhankelijk, wat meer geschikt kan zijn voor toepassingen waar realtime updates niet noodzakelijk zijn.

Kunt u het levenscyclus van een WebSocket-verbinding uitleggen?

Het levenscyclus van een WebSocket-verbinding begint met een handslag, die een HTTP-verbinding omzet in een WebSocket-verbinding. Zodra de verbinding is gemaakt, kan gegevens heen en weer worden verzonden tussen de client en de server totdat een van beide partijen besluit om de verbinding te sluiten. De verbinding kan worden gesloten door de ene partij een sluitframe te sturen, gevolgd door de andere partij die het sluitframe bevestigt.

Hoe kan ik WebSockets implementeren in een Android-toepassing?

Het implementeren van WebSockets in een Android-toepassing vereist het maken van een WebSocket-client die verbinding kan maken met een WebSocket-server. Dit kan worden gedaan met behulp van bibliotheken zoals OkHttp of Scarlet. Zodra de client is ingesteld, kunt u een verbinding met de server openen, berichten verzenden en ontvangen, en verschillende gebeurtenissen afhandelen zoals het openen van de verbinding, het ontvangen van berichten en het sluiten van de verbinding.

Wat zijn Server-Sent Events en hoe vergelijken ze met WebSockets?

Server-Sent Events (SSE) zijn een standaard die ervoor zorgt dat een server updates naar een client kan sturen via HTTP. In tegenstelling tot WebSockets zijn SSE unidirectioneel, wat betekent dat ze alleen toestaan dat gegevens van de server naar de client worden verzonden. Dit maakt ze minder geschikt voor toepassingen die tweerichtingscommunicatie vereisen, maar ze kunnen een eenvoudiger en efficiënter oplossing zijn voor toepassingen die alleen updates van de server nodig hebben.

Wat zijn enkele veelvoorkomende gebruiksvoorbeelden van WebSockets en Server-Sent Events?

WebSockets worden vaak gebruikt in toepassingen die realtime, tweerichtingscommunicatie vereisen, zoals chat-apps, multiplayer games en samenwerkingsgereedschappen. Server-Sent Events worden daarentegen vaak gebruikt in toepassingen die realtime updates van de server nodig hebben, zoals live nieuws updates, aandelenkoers updates of voortgangsrapporten voor langdurige taken.

Hoe kan ik WebSocket-verbindingen afhandelen in een Spring Boot-toepassing?

Spring Boot biedt ondersteuning voor WebSocket-communicatie via het Spring WebSocket-module. U kunt de @EnableWebSocket-annotatie gebruiken om ondersteuning voor WebSocket in te schakelen en vervolgens een WebSocketHandler definiëren om het verbindingsleven en berichtbehandeling te beheren. U kunt ook de SimpMessagingTemplate gebruiken om berichten naar verbonden clients te sturen.

Wat zijn de beveiligingsoverwegingen bij het gebruik van WebSockets?

Net als elke andere webtechnologie kunnen WebSockets kwetsbaar zijn voor verschillende beveiligingsbedreigingen, zoals Cross-Site WebSocket Hijacking (CSWSH) en Denial of Service (DoS) aanvallen. Om deze risico’s te beperken, moet u altijd beveiligde WebSocket-verbindingen (wss://) gebruiken en alle inkomende gegevens valideren en saniteren. U kunt ook overwegen gebruik te maken van authenticatie- en autorisatiemechanismen om toegang tot uw WebSocket-server te beheren.

Kan ik WebSockets gebruiken met een REST API?

Ja, u kunt WebSockets in combinatie met een REST API gebruiken. Hoewel REST APIs uitstekend zijn voor stateless request-response communicatie, kunnen WebSockets worden gebruikt voor real-time, tweerichtingscommunicatie. Dit kan bijzonder handig zijn in toepassingen die direct updates vereisen, zoals chat-apps of live sport updates.

Hoe kan ik een WebSocket-server testen?

Er zijn verschillende hulpmiddelen beschikbaar voor het testen van WebSocket-servers, zoals de Echo Test van WebSocket.org, of Postman. Deze tools stellen u in staat om een WebSocket-verbinding te openen met een server, berichten te sturen en reacties te ontvangen. U kunt ook geautomatiseerde tests schrijven voor uw WebSocket-server met behulp van bibliotheken zoals Jest of Mocha.

Wat zijn de beperkingen van WebSockets en Server-Sent Events?

Hoewel WebSockets en Server-Sent Events krachtige mogelijkheden bieden voor real-time communicatie, hebben ze ook hun beperkingen. Bijvoorbeeld, niet alle browsers en netwerken ondersteunen deze technologieën, en ze kunnen een aanzienlijke hoeveelheid middelen verbruiken als ze niet goed worden beheerd. Bovendien kunnen ze complexer zijn om te implementeren en te beheren in vergelijking met traditionele HTTP-communicatie.

Source:
https://www.sitepoint.com/real-time-apps-websockets-server-sent-events/