MySQL Java JavaScript PHP Python HTML-CSS C-sharp

Server-Sent Events

Server-Sent Events sau pe scurt SSE reprezintă o altă tehnologie de interacțiune între client și server, care permite serverului să trimită mesaje clientului. Este de remarcat faptul că, spre deosebire de WebSockets, comunicația prin Server-Sent Events este unidirecțională: mesajele sunt livrate într-o singură direcție - de la server la client (de exemplu, către browserul web al utilizatorului).

Acest lucru le face o opțiune excelentă când nu este necesar să se trimită date de la client la server. De exemplu, Server-Sent Events pot fi utilizate pentru gestionarea unor lucruri precum actualizarea statusului în rețelele sociale, fluxurile de știri sau trimiterea de date pentru stocare pe partea de client.

Pe o pagină web, în codul JavaScript pentru interacțiunea cu serverul se utilizează interfața EventSource. Obiectul EventSource reprezintă, în esență, un server care generează evenimente sau trimite mesaje. Pentru crearea unui obiect EventSource se folosește constructorul:

new EventSource(url, options)

Ca prim parametru obligatoriu în constructorul EventSource se transmite adresa URL a resursei pe server:

const evtSource = new EventSource("/events");

Opțional, se poate transmite un parametru neobligatoriu care configurează obiectul EventSource. Acest parametru reprezintă un obiect cu o singură proprietate withCredentials. Această proprietate indică dacă trebuie incluse antetele CORS pentru interacțiunea cross-domain. Implicit, este false.

Evenimente EventSource

Pentru gestionarea stării conexiunii în EventSource sunt definite o serie de evenimente:

  • open: generat la stabilirea conexiunii. Pentru setarea handler-ului de evenimente se poate folosi proprietatea onopen
  • error: generat la apariția unei erori în timpul stabilirii conexiunii. Pentru setarea handler-ului de evenimente se poate folosi proprietatea onerror
  • message: generat la primirea datelor de la server. Pentru setarea handler-ului de evenimente se poate folosi proprietatea onmessage

Handler-ii acestor evenimente primesc ca parametru un obiect standard Event. Exemplu de setare a handler-ilor de evenimente:

const evtSource = new EventSource("/events");

// folosind addEventListener
evtSource.addEventListener("open", () => {
 console.log("conexiune deschisă");
});
evtSource.addEventListener("error", () => {
 console.log("Eroare");
});

// folosind proprietăți
evtSource.onopen = () => {
 console.log("conexiune deschisă");
};
evtSource.onerror = () => {
 console.log("Eroare");
};

Recepția datelor

Când vin date de la server, la obiectul WebSocket se declanșează evenimentul message, pentru setarea handler-ului căruia se poate folosi proprietatea onmessage sau metoda addEventListener().

În handler-ul evenimentului message se transmite un obiect de tip MessageEvent. Acest obiect oferă o serie de proprietăți care permit extragerea datelor răspunsului serverului:

  • data: returnează datele primite
  • origin: stochează adresa expeditorului
  • lastEventId: stochează identificatorul unic al ultimului eveniment sub formă de string
  • source: returnează un obiect MessageEventSource, care poate fi un obiect WindowProxy, MessagePort sau ServiceWorker și care reprezintă expeditorul datelor primite
  • ports: returnează un array de obiecte MessagePort, care stochează porturile folosite pentru trimitere

Exemplu de recepție a datelor:

const evtSource = new EventSource("/events");
evtSource.onmessage = (event) => {
   console.log(event.data);    // afișăm datele trimise pe consolă
};

Închiderea conexiunii

Pentru închiderea conexiunii se folosește metoda close():

evtSource.close();

Exemplu de interacțiune între client și server cu Server-Sent Events

Să examinăm un exemplu mic de interacțiune între client și server folosind Server-Sent Events. Ca client va acționa codul JavaScript pe o pagină web. Iar ca server vom folosi Node.js.

Mai întâi, definim codul pe server. Pentru aceasta, creăm fișierul server.js cu următorul cod:

const http = require("http");
const fs = require("fs");

// date pentru trimitere la client
const messages = ["Salut", "Ce faci?", "Ce faci?", "Dormi?", "Pa"];
http.createServer(function(request, response){
     
   if(request.url == "/events"){   // dacă este o cerere SSE
       if (request.headers.accept && request.headers.accept === "text/event-stream") {
           sendEvent(response);
       }
       else{
           response.writeHead(400);
           response.end("Cerere greșită");
       }
   }
   else{   // în alte cazuri trimitem pagina index.html
       fs.readFile("index.html", (_, data) => response.end(data));
   }
}).listen(3000, ()=>console.log("Serverul a fost lansat pe adresa http://localhost:3000"));

// trimitem mesaj clientului
function sendEvent(response) {
   // formăm antetele
   response.writeHead(200, {    
       "Content-Type": "text/event-stream",    
       "Cache-Control": "no-cache",    
       "Connection": "keep-alive" 
   });
   const id = (new Date()).toLocaleTimeString();  // definim identificatorul ultimului eveniment
   // la fiecare 5 secunde trimitem un mesaj
   setInterval(() => { createServerSendEvent(response, id); }, 5000);
}
// trimitem date clientului
function createServerSendEvent(response, id) {
   // generăm un număr aleatoriu - index pentru array-ul messages
   const index = Math.floor(Math.random() * messages.length);
   const message = messages[index];
   response.write("id: " + id + "\n");  
   response.write("data: " + message + "\n\n");
}

Să trecem pe scurt prin cod. În primul rând, se conectează pachetele cu funcționalitatea pe care intenționăm să o utilizăm:

const http = require("http");   // pentru procesarea cererilor primite
const fs = require("fs");       // pentru citirea fișierului index.html de pe disc

Apoi, definim setul de date care vor fi trimise clientului - un set de șiruri cu mesaje importante pentru client:

const messages = ["Salut", "Ce faci?", "Ce faci?", "Dormi?", "Pa"];

Pentru crearea serverului se utilizează funcția http.createServer(). Acestei funcții i se transmite un handler, care este apelat de fiecare dată când serverul primește o cerere. Acest handler are doi parametri: request (conține datele cererii) și response (gestionează trimiterea răspunsului).

În handler, folosind proprietatea request.url, putem determina la ce resursă de pe server s-a făcut cererea. Astfel, în acest caz, dacă cererea a venit pe calea "/events", atunci vom interacționa cu clientul folosind Server-Sent Events:

if(request.url == "/events"){   // dacă cererea este SSE
   if (request.headers.accept && request.headers.accept === "text/event-stream") {
       sendEvent(response);
   }
   else{
       response.writeHead(400);
       response.end("Cerere greșită");
   }
}

Este important ca în cerere să fie setat antetul "Accept": acesta trebuie să aibă valoarea "text/event-stream". Dacă este așa, atunci pentru trimiterea datelor clientului executăm funcția sendEvent(), în care transmitem obiectul răspuns response. Dacă anteturile nu sunt setate, atunci trimitem un răspuns cu eroarea 400.

În funcția sendEvent formăm anteturile răspunsului, obținem ora curentă, care va servi ca identificator al evenimentului, și pornim un timer pentru trimiterea datelor clientului la fiecare 5 secunde.

function sendEvent(response) {
   // formăm anteturile
   response.writeHead(200, {    
       "Content-Type": "text/event-stream",    
       "Cache-Control": "no-cache",    
       "Connection": "keep-alive" 
   });
   const id = (new Date()).toLocaleTimeString();  // definim identificatorul ultimului eveniment
   // la fiecare 5 secunde trimitem un mesaj
   setInterval(() => { createServerSendEvent(response, id); }, 5000);
}

Trimiterea datelor se face în funcția createServerSendEvent:

function createServerSendEvent(response, id) {
   // generăm un număr aleatoriu - index pentru array-ul messages
   const index = Math.floor(Math.random() * messages.length);
   const message = messages[index];
   response.write("id: " + id + "\n");  
   response.write("data: " + message + "\n\n");
}

Aici, obținem un număr aleatoriu, care se află în intervalul de la 0 la messages.length și care va servi ca index, și pe baza acestui index selectăm un anumit mesaj. Apoi formăm răspunsul.

Setăm identificatorul evenimentului:

response.write("id: " + id + "\n");

Și stabilim propriu-zis datele:

response.write("data: " + message + "\n\n");

Dacă cererea a venit pe server pe o altă cale, atunci trimitem fișierul index.html, pe care îl vom defini în continuare.

else{
   fs.readFile("index.html", (_, data) => response.end(data));
}

Pentru citirea fișierelor se folosește funcția încorporată fs.readFile(). Primul parametru al funcției este adresa fișierului (în acest caz, se presupune că fișierul index.html se află în același folder cu fișierul serverului server.js). Al doilea parametru este funcția care este apelată după citirea fișierului și primește conținutul său prin al doilea parametru data. Apoi, conținutul citit poate fi trimis folosind funcția response.end(data).

La final, cu ajutorul funcției listen() lansăm serverul web pe portul 3000. Adică, serverul va fi accesibil la adresa http://localhost:3000/

Acum, în folderul serverului, definim un fișier index.html simplu cu următorul cod:

<!DOCTYPE html>
<html>
<head>
   <meta charset="utf-8" />
   <title>FDC.COM</title>
</head>
<body>
<ul id="list"></ul>
<script>
const source = new EventSource("/events");      
const list = document.getElementById("list")      
source.addEventListener("message", (e) => {
   const listItem = document.createElement("li");      
   listItem.textContent += e.data;
   list.appendChild(listItem);
});
</script>
</body>
</html>

Aici, la primirea datelor de la server, le adăugăm în lista de pe pagina web.

Acum, în consolă, ne deplasăm la folderul serverului folosind comanda cd și lansăm serverul folosind comanda node server.js.

După lansarea serverului, putem naviga în browser la adresa http://localhost:3000, unde ni se va afișa pagina, iar în codul JavaScript al acesteia se va realiza primirea datelor de la server și afișarea lor pe pagina web.

← Lecția anterioară Lecția următoare →