Šiame straipsnyje išnagrinėsime, kaip naudoti serverio siunčiamus įvykius, kad klientas gautų automatinius naujinimus iš serverio HTTP ryšiu. Taip pat panagrinėsime, kodėl tai naudinga, ir parodysime praktinių demonstracijų, kaip naudoti serverio siunčiamus įvykius su Node.js.
Kodėl serverio siunčiami įvykiai yra naudingi
Žiniatinklis yra pagrįstas užklausos ir atsako HTTP pranešimais. Jūsų naršyklė pateikia URL užklausą, o serveris atsako pateikdamas duomenis. Dėl to gali atsirasti papildomų naršyklės užklausų ir serverio atsakymų dėl vaizdų, CSS, JavaScript ir kt. Serveris negali pradėti pranešimus į naršyklę, tai kaip ji gali nurodyti, kad duomenys pasikeitė? Laimei, prie serverio siunčiamų įvykių galite pridėti tokių funkcijų, kaip tiesioginiai naujienų biuleteniai, orų ataskaitos ir akcijų kainos.
Įdiegti tiesioginius duomenų atnaujinimus naudojant standartines žiniatinklio technologijas visada buvo įmanoma:
- 1990-ųjų žiniatinklis naudojo viso puslapio arba kadro / „iframe“ atnaujinimą.
- 2000-ųjų žiniatinklis pristatė „Ajax“, kuri galėjo naudoti ilgą apklausą, kad prašytų duomenų ir atnaujintų atitinkamus DOM elementus nauja informacija.
Nė viena parinktis nėra ideali, nes naršyklė turi suaktyvinti atnaujinimą. Jei per dažnai pateikia užklausas, jokie duomenys nepasikeis, todėl tiek naršyklė, tiek serveris atliks nereikalingą darbą. Jei jis pateikia užklausas per lėtai, jis gali praleisti svarbų atnaujinimą, o jūsų stebima akcijų kaina jau nukrito!
Serverio siunčiami įvykiai (SSE) leidžia serveriui bet kuriuo metu perkelti duomenis į naršyklę:
- Naršyklė vis tiek pateikia pradinę užklausą užmegzti ryšį.
- Serveris grąžina įvykių srauto atsaką ir palaiko ryšį.
- Serveris gali naudoti šį ryšį tekstiniams pranešimams siųsti bet kuriuo metu.
- Gaunami duomenys sukelia „JavaScript“ įvykį naršyklėje. Įvykių tvarkyklės funkcija gali išanalizuoti duomenis ir atnaujinti DOM.
Iš esmės SSE yra nesibaigiantis duomenų srautas. Pagalvokite apie tai kaip be galo didelio failo atsisiuntimą mažais gabalėliais, kuriuos galite perimti ir perskaityti.
SSE pirmą kartą buvo įdiegta 2006 m. ir visos pagrindinės naršyklės palaiko standartą. Galbūt tai mažiau žinoma nei „WebSockets“, tačiau serverio siunčiami įvykiai yra paprastesni, naudoja standartinį HTTP, palaiko vienpusį ryšį ir siūlo automatinį pakartotinį prisijungimą. Šioje mokymo programoje pateikiamas pavyzdys Node.js kodas be trečiųjų šalių modulių, tačiau SSE galima naudoti kitomis serverio kalbomis įskaitant PHP.
Serverio išsiųstų įvykių greita pradžia
Toliau pateiktoje demonstracijoje įdiegtas Node.js žiniatinklio serveris, kuris atsitiktiniu intervalu bent kartą per tris sekundes išveda atsitiktinį skaičių nuo 1 iki 1 000.
Čia galite rasti mūsų Node.js SSE demonstraciją.
Kode naudojamas standartinis Node.js http
ir url
moduliai, skirti sukurti žiniatinklio serverį ir analizuoti URL:
import http from "node:http";
import url from "node:url";
Serveris išnagrinėja gaunamą URL užklausą ir reaguoja, kai susiduria su a /random
kelias:
const port = 8000;
http.createServer(async (req, res) => {
const uri = url.parse(req.url).pathname;
switch (uri) {
case "/random":
sseStart(res);
sseRandom(res);
break;
}
}).listen(port);
console.log(`server running: http://localhost:${port}\n\n`);
Iš pradžių jis atsako su SSE HTTP įvykių srauto antrašte:
function sseStart(res) {
res.writeHead(200, {
Content-Type: "text/event-stream",
Cache-Control: "no-cache",
Connection: "keep-alive"
});
}
Tada kita funkcija siunčia atsitiktinį skaičių ir pasikviečia save, kai praeina atsitiktinis intervalas:
function sseRandom(res) {
res.write("data: " + (Math.floor(Math.random() * 1000) + 1) + "\n\n");
setTimeout(() => sseRandom(res), Math.random() * 3000);
}
Jei paleidžiate kodą vietoje, galite išbandyti atsakymą naudodami cURL savo terminale:
$> curl -H Accept:text/event-stream http://localhost:8000/random
data: 481
data: 127
data: 975
Paspauskite Ctrl | Cmd ir C prašymą nutraukti.
Naršyklės kliento pusės JavaScript prisijungia prie /random
URI naudojant an EventSource objektų konstruktorius:
const source = new EventSource("/random");
Įeinančių duomenų paleidikliai a message
įvykių tvarkytoja, kur seka eilutė data:
yra įvykio objekte .data
nuosavybė:
source.addEventListener('message', e => {
console.log('RECEIVED', e.data);
});
Svarbios pastabos
- Kaip Paimti ()naršyklė pateikia standartinę HTTP užklausą, todėl gali tekti tvarkyti CSP, CORS ir pasirinktinai praleiskite sekundę
{ withCredentials: true }
argumentas prieEventSource
konstruktorius siųsti slapukus. - Serveris turi išlaikyti individualų
res
atsakymo objektus, kad kiekvienas prisijungęs vartotojas galėtų siųsti jiems duomenis. Tai pasiekiama aukščiau pateiktame kode, perduodant uždarymo reikšmę kitam skambučiui. - Pranešimo duomenys gali būti tik eilutė (galbūt JSON), išsiųsta tokiu formatu
data: <message>\n\n
. Baigiantis vežimo grąžinimas yra būtinas. - Serveris gali bet kada nutraukti SSE atsakymą su
res.end()
bet… - Atsijungus, naršyklė automatiškai bando prisijungti iš naujo; nereikia rašyti savo prisijungimo kodo.
Išplėstiniai serverio siunčiami įvykiai
SSE nereikia daugiau kodo, nei nurodyta aukščiau, tačiau tolesniuose skyriuose aptariamos kitos parinktys.
Vienas prieš daug SSE kanalų
Serveris gali pateikti bet kokį SSE kanalo URL skaičių. Pavyzdžiui:
/latest/news
/latest/weather
/latest/stockprice
Tai gali būti praktiška, jei viename puslapyje rodoma viena tema, bet mažiau, jei viename puslapyje rodomos naujienos, orai ir akcijų kainos. Tokiu atveju serveris turi palaikyti tris ryšius kiekvienam vartotojui, o tai gali sukelti atminties problemų didėjant srautui.
Alternatyva yra pateikti vieną galutinio taško URL, pvz /latest
, kuris siunčia bet kokio tipo duomenis vienu ryšio kanalu. Naršyklė URL užklausos eilutėje gali nurodyti dominančias temas, pavyzdžiui, /latest?type=news,weather,stockprice
— todėl serveris gali apriboti SSE atsakymus iki konkrečių pranešimų.
Skirtingų duomenų siuntimas vienu kanalu
Pranešimai iš serverio gali būti susieti event:
praėjo virš linijos data:
nustatyti konkrečias informacijos rūšis:
event: news
data: SSE is great!
event: weather
data: { "temperature": "20C", "wind": "10Kph", "rain": "25%" }
event: stock
data: { "symbol": "AC", "company": "Acme Corp", "price": 123.45, "increase": -1.1 }
Šie bus ne suaktyvinti kliento pusę "message"
įvykių vedėjas. Kiekvienam tipui turite pridėti tvarkykles event
. Pavyzdžiui:
source.addEventListener('news', e => {
document.getElementById('headline')
.textContent = e.data;
});
source.addEventListener('weather', e => {
const w = JSON.parse(e.data);
document.getElementById('weather')
.textContent = `${ w.temperature } with ${ w.wind } wind`;
});
source.addEventListener('stock', e => {
const s = JSON.parse(e.data);
document.getElementById(`stock-${ s.symbol }`)
.textContent = `${ s.share }: ${ s.price } (${ s.increase }%)`;
});
Duomenų identifikatorių naudojimas
Pasirinktinai serveris taip pat gali siųsti el id:
po to a data:
eilutė:
event: news
data: SSE is great!
id: 42
Jei ryšys nutrūksta, naršyklė siunčia paskutinį id
atgal į serverį Last-Event-ID
HTTP antraštė, kad serveris galėtų pakartotinai išsiųsti visus praleistus pranešimus.
Naujausias ID taip pat pasiekiamas kliento pusėje įvykio objekte .lastEventId
nuosavybė:
source.addEventListener('news', e => {
console.log(`last ID: ${ e.lastEventId }`);
document.getElementById('headline')
.textContent = e.data;
});
Nurodoma pakartotinio bandymo delsa
Nors pakartotinis prisijungimas yra automatinis, jūsų serveris gali žinoti, kad tam tikrą laikotarpį naujų duomenų nesitikima, todėl nereikia išlaikyti aktyvaus ryšio kanalo. Serveris gali siųsti a retry:
atsakymas su milisekundžių reikšme arba atskirai, arba kaip galutinio pranešimo dalis. Pavyzdžiui:
retry: 60000
data: Please don't reconnect for another minute!
Gavusi naršyklė nutrauks SSE ryšį ir, pasibaigus delsos laikotarpiui, bandys prisijungti iš naujo.
Kiti įvykių tvarkytojai
Taip pat kaip "message"
ir pavadintus įvykius, taip pat galite sukurti "open"
ir "error"
tvarkyklės jūsų kliento pusės „JavaScript“.
An "open"
įvykis suaktyvinamas, kai užmezgamas serverio ryšys. Jis gali būti naudojamas papildomam konfigūracijos kodui paleisti arba DOM elementams inicijuoti:
const source = new EventSource('/sse1');
source.addEventListener('open', e => {
console.log('SSE connection established.');
});
An "error"
įvykis suveikia, kai serverio ryšys nutrūksta arba nutrūksta. Galite ištirti įvykio objektą .eventPhase
turtas patikrinti, kas atsitiko:
source.addEventListener('error', e => {
if (e.eventPhase === EventSource.CLOSED) {
console.log('SSE connection closed');
}
else {
console.log('error', e);
}
});
Atminkite, kad nereikia iš naujo prisijungti: tai vyksta automatiškai.
Nutraukiamas SSE ryšys
Naršyklė gali nutraukti SSE ryšį naudodama objektą EventSource .close()
metodas. Pavyzdžiui:
const source = new EventSource('/sse1');
setTimeout(() => source.close(), 3_600_000);
Serveris gali nutraukti ryšį:
- šaudymas
res.end()
arba siunčiant aretry:
delsimastada - HTTP būsenos grąžinimas 204, kai ta pati naršyklė bando prisijungti iš naujo.
Tik naršyklė gali atkurti ryšį sukurdama naują EventSource
objektas.
Išvada
Serverio pusės įvykiai suteikia galimybę įdiegti tiesioginius puslapio atnaujinimus, kurie galbūt yra paprastesni, praktiškesni ir lengvesni nei Fetch()
„Ajax“ apklausa. Sudėtingumas yra serverio gale. Tu privalai:
- išlaikyti visus aktyvius vartotojo ryšius atmintyje ir
- suaktyvinti duomenų perdavimą, kai kas nors pasikeičia.
Tačiau tai visiškai kontroliuojate jūs, o mastelio keitimas neturėtų būti sudėtingesnis nei bet kuri kita žiniatinklio programa.
Vienintelis trūkumas yra tas, kad SSE neleidžia siųsti pranešimų iš naršyklės į serverį (išskyrus pradinę prisijungimo užklausą). Galite naudoti „Ajax“, bet tai per lėta programoms, pvz., veiksmo žaidimams. Norint užtikrinti tinkamą dvipusį ryšį, jums reikia „WebSockets“. Patikrinkite Kaip naudoti WebSockets Node.js kuriant programas realiuoju laiku norėdami sužinoti daugiau!