WebSocket, WebSocket mindenhol…

2019. március 26.

Egyszer korábban a Facebook oldalon osztottam meg egy kis szösszenetet a websocketekkel kapcsolatban, és akkor el is határoztam, hogy az akkori visszajelzések alapján érdemes egy kicsivel hosszabb szösszenetet is összerakni, mert voltak kérdéses dolgok a hozzászólók körében.

(Ráadásul most dolgozok is egy újabb challange összerakásán… az első ilyen kifejezetten fejlesztőknek szóló kihívásom REST API-val működött, ez most majd websocketes kommunikáción fog alapulni…, de ez most egy mellékes információ volt)

Alapvetően a websocketet akkor érdemes használni, ha az alkalmazásban szükséged van valós idejű, kétirányú kommunikációra. Természetesen nem az igazi értelemben vett socket, de elég közel jár hozzá és elég jól is működik.

A szerver oldali részen lehet bármilyen szolgáltatás, íródhat Java, PHP, Node.js, stb. nyelveken és környeztekkel, igazából az az oldal lehet bármilyen. 🙂

Kliens oldalon is többféle megvalósításban találkozhatunk websocketekkel, a cikk során végig a ‘vanilla websocket’ fog központi szerephez jutni.

WebSocket API

A böngészőkben elérhető egy WebSocket API, annak is központi eleme a WebSocket objektum.

A példákban a célom az lesz, hogy egy sorbanállást szimuláljak… mondjuk állunk a sorban a postán.. 🙂

Új példány létrehozása

A WebSocket konstruktorának segítségével hozhatunk létre egy új példányt. A konstruktorban szükséges megadni, hogy hol érhető el a szerver oldali rész.

Ilyenkor ami a háttérben konkrétan történik, hogy a kliens a kézfogás (handshake) folyamat során egy ‘upgrade’ kérelmet küld (HTTP-ről WebSocketre), amire ha a szerver oldalon is minden rendben van, akkor egy HTTP/1.1 101 Switching Protocols választ küldi vissza, aminek hatására a kliens a kapcsolatot nyitva tarja.

Amíg a kapcsolódás történik, addig a WebSocket.CONNECTING állapotban van a websocketünk.

A példa szempontjából vegyük úgy, hogy ez olyan, mintha épp a posta ajtaján dörömbölnénk, hogy nyisson már ki, hogy beállhassunk a sorba. 🙂

const socket = new WebSocket('ws://online-post-office.com:8080/queue')

Kapcsolat kiépülésének eseménye

Amint felépült a kapcsolat, akkor átvált WebSocket.OPEN állapotba, és ezt az eseményt ‘el tudjuk kapni’ úgy, hogy a socketünk onopen property-jére rákötünk egy saját függvényt.

socket.onopen = function(event) {   
console.log("Post office is open now.")
}

Jupíí! Kinyitott a posta! Most már beállhatunk a sorba! No és ami a sorban fog történni, az valójában nem más, mint hogy üzeneteket fogunk küldeni és kapni, hogy mindig pontosan tudjuk, hogy épp hányan vannak előttünk és tudjuk azt is, hogy mikor szólítanak.

Üzenetküldés

Küldjük mi az első üzenetet, hogy szeretnénk beállni a sorba! Ehhez a WebSocket send metódusát fogjuk használni (ezen kívül egy másik van neki, amiről egy kicsit később). Kicsit módosítom az onopen függvényt, hisz csak akkor fogunk tudni majd üzenetet küldeni, ha már nyitva van a kapcsolat:

socket.onopen = function(event) {        
    console.log('Post office is open now.')
    socket.send('package pickup')
}

Sokmindent lehet küldeni, a leggyakoribb a szöveges kommunikáció.

Üzenetfogadás

Általában amit küldünk és fogadunk a szervertől az valamilyen strukturált üzenet, nem csak egy random szöveg. Legtöbbször az üzenetek JSON formátumban értkeznek és a válaszokat is abban szokták várni. Ehhez viszont az kell, hogy először tujdunk üzenetet fogadni, majd azt az üzenetet pedig JSON-ből át tudjuk alakítani.

Ahogy a kapcsolat megnyitására is volt egy property, így az üzenet fogadására is van, ez lesz az onmessage, amihez a mi saját függvényünket kötni tudjuk:

socket.onmessage = function(event) {
    console.debug("Post office has sent me something! :)", event.data)
}

Itt az event, ami tartalmazza a szerver által küldött üzenetet is, MessageEvent típusú, amiből a konkrét üzenetet a data property segítségével érhetjük el.

A példában én most JSON-ből átalakítom objektummá az üzenetet, és a szerver által küldött három típusú üzenettel kezdek most valamit, az első, hogy a postán kaptam egy sorszámot, a második, hogy figyelmeztet, hogy hányan várakoznak még előttem, a harmdik pedig, hogy engem szólítottak.

Természetesen ez egy elméleti példa, ezt annak megfelelően szükséges kialakítani, hogy a szerver oldal mit szolgáltat.

socket.onmessage = function(event) {
    console.debug("Post office has sent me something! :)", event.data)
    let message = JSON.parse(message)
    switch (message.id) {
        case 'queueNumber': 
            console.log('Your queue number is: ' + message.number)
            break
        case 'update':
            console.log('There are only ' + message.number + ' people in front of you')
            break
        case 'yourTurn':
            console.log('It is my turn!')
            break
    }
}

Kapcsolat lezárása

Ha szeretnénk bezárni a WebSocket kapcsolatot, használhatjuk a másik metódust, hívjuk meg close-t. Alapvetően paraméterek nélkül is meghívhatjuk, de van neki két opcionálisan átadható paramétere. Az egyik egy kód, amit egy fix készletből is választhatunk, és ha ez nincs megadva, akkor alapértelmezetten az 1005-ös lesz kiválasztva, azaz hogy nem volt státuszkód átadva. Át lehet adni még egy ’ember által olvasható’ szöveget is, amelyben ki lehet fejteni a lezárás okát, de lehetőleg ne túl bőbeszédűen, mert maximum 123 bájtnyi UTF-8 karakterkódolású szöveg engedélyezett. 😛

socket.close(1001, 'I am going away, there are too many people here... ')

A lezárás közben státuszváltás is történik… amíg a zárás folyamatban van, addig a WebSocket.CLOSING állapot van érvényben, miután pedig lezárult, már WebSocket.CLOSED állapot lesz.

Persze hiba esetén van, hogy magától bezáródik a kapcsolat, erre is van egy property, az onclose, hogy erre az eseményre is köthessünk dolgokat.

socket.onclose = function(event) {
  console.log('They threw me out of the post office... :(')
}

Hibakezelés

Természetes, hogy bármikor keletkezhet bármilyen hiba. Ha pedig ennek az eseményét is szeretnénk elkapni, és valamit kezdeni a hibaüzenettel, akkor az onerror property használható.

socket.onerror = function(event) { 
    console.error("Something is not right here... :", event)
} 

Állapot lekérdezése

Mindenhol elmondtam, hogy mikor történik állapotváltozás, de azt elfeljtettem említeni, hogy egyáltalán honnan kérdezhetjük le az állapot konkrét értékét. Nos, nagyon egyszerű, van a readyState property. 🙂

És mindössze a már korábban említett 4 különböző értéket vehet fel, azaz
CONNECTING, OPEN, CLOSING és CLOSED.

let state = socket.readyState

Támogatottság

Természetes, hogy felmerül, hogy egyáltalán melyik böngészőkben támogatott a dolog… Hát a válaszért webes ügyekben általában a caniuse.com oldalhoz fordulok, most is segítségül hívtam (kattintsatok a képre, megnyílik nagyban):

böngésző támogatottság

Utolsó kérdések

Biztonságos csatorna

Ugye alapvetően a kommunikáció egyetlen TCP socketen keresztül folyik, és a példában unsecure ws protokollt használtam, de lehetőség van secure wss protokoll használatára is, amennyibe a szerver is támogatja.

Autentikáció

Ugye azért jó, hogy HTTP-n keresztül indul a csatlakozás, mert az autentikáció mehet még ott Authorization header segítségével Basic vagy Bearer tokenes autentikációhoz.

De persze sűrűn előfordul az is, hogy inkább azt a megoldást választják, hogy a kézfogás utáni első üzenetváltásban egy megfelelően formázott üzenetben küldik fel a titkos dolgokat… 🙂

Újracsatlakozás?

Sajnos jelenleg nem támogatott, hogy automatikusan újracsatlakozzon, ha valamiért megszakad a kapcsolat, de meg lehet oldani úgy például, hogy az onclose property-re kötött funkcióban meghívunk egy másik függvényt, ami újracsatlakozik… 🙂

Összefoglaló gondolatok

Szerintem nagyon hasznos dolog, ráadásul nem is túl bonyolult. 🙂

Persze lehet mindenféle kiterjesztést is használni, ott van például a Socket.io is – talán az összes közül a legnépszerűbb -, ami erre a nagyon kis egyszerű API-ra rádob egy kicsit a lapáttal és plusz funkciókat ad hozzá. Például fallback mechanizmusként elsődlegesen preferálja a natív WebSocket API-t, de ha valamiért nem elérhető, mert mondjuk egy régi böngészőről van szó, stb, akkor is megoldja, hogy működjön a dolog… 🙂

2 hozzászólás

  • Szia kedves fejlesztő leányzó 🙂 Jókis cikk a websocet ről.Én a saját projektemben az eventsource objektumot használom,ami szintén élő kapcsolatot képes kialakítani.Szívesen olvasnék tőled egy összehasonlító cikket,melyben bemutatnád ezt a lehetőséget is.

    • Köszönöm! 🙂 Örülök, hogy tetszett! Köszi a tippet, megfontolandó! 🙂 A Server Sent Events / Eventsource ugye alapvetően inkább arra használandó, hogy a kliens felé küldünk adatokat, tehát inkább egyirányú, de ettől függetlenül mindenképpen érdemes lenne erről is írni egyet! 🙂

Vélemény, hozzászólás?

Az email címet nem tesszük közzé. A kötelező mezőket * karakterrel jelöltük