Keresés tartalomra
Kategóriák
Címkék
- Java
- Spring
- Python
- IoC
- Android
- DI
- Dagger
- Thymeleaf
- Markdown
- JDK11
- AOP
- Aspect
- Captcha
- I18n
- JavaSpark
- Microframework
- Testing
- JUnit
- Security
- JWT
- REST
- Database
- JPA
- Gépház
- WebFlux
- ReactiveProgramming
- Microservices
- Continuous Integration
- CircleCI
- Deployment Pipeline
- Docker
- Mocking
- LogProcessing
- PlantUML
- UML
- Modellezés
- OAuth2
- Node.js
- DevOps
- Websocket
Websocket szerver Express alkalmazásban
Kezdjük azzal, hogy mire jó nekünk egy websocket kapcsolat? A websocketek kétirányú, aszinkron kapcsolatot tudnak fenntartani a csatlakozott felek között. Chat szolgáltatások, játékok, valósidejű információs portálrendszerek, igazából elég sok felhasználási lehetősége van (vagy ahol nagyobb flexibilitásra van szükség, az alacsonyabb szintű TCP socketeket használják). A websocketek legnagyobb előnye, hogy egyrészt nagyon könnyen integrálhatóak "klasszikus" HTTP(S) webszerverekkel, másrészt az üzenetek "összeragasztása" nem a mi feladatunk (byte-streamekről lévén szó, TCP socket esetén még nekünk kell foglalkozni a byte-stream értelmezhető egységekké való visszaállításáról).
A websocket életciklusa egy standard HTTP kérésként indul, az egyetlen szembetűnő különbség azon a ponton csupán a megszokottól eltérő protokoll, ahogyan a kliens megszólítja a szervert: ws
(titkosítatlan, a http
megfelelője), illetve wss
(titkosított, mint a https
). A portok is lehetnek azonosak, tehát a 80 és a 443 továbbra is tökéletes választás. Az igazi különbség a kliens és a szerver között váltott első két üzenetben mutatkozik meg. A kliens egy GET
kérést küld a HTTP szerver socket endpointjára, benne egy Upgrade: websocket
és Connection: upgrade
header-párral. Ez a két header jelzi a szerver számára, hogy a kliens a továbbiakban websocketen keresztül szeretne kommunikálni. Ha a szerver is képes erre, ugyanezzel a két headerrel válaszol, HTTP 101 Switching Protocols
státusz kíséretében.
Ettől a ponttól kezdve a két fél egy aktív, kétirányú, folyamatosan nyitott kommunikációs vonalat használ, mely teljesen esemény-vezérelt. Mindkét fél látja, ha a másik üzenetet küldött (message
esemény), ha bezárta a kapcsolatot (close
esemény - szándékosan, vagy hálózati hiba miatt), illetve ha bármilyen hiba lépett fel a socket működésében (error
esemény). Az open
esemény speciális, akkor következik be, ha a websocket kapcsolat létrejött a két fél között -- ezt fel tudjuk használni például a kliens authentikálására. A websocketen átküldött üzenetek formátuma, kódolása, tartalma az implementáló felek közötti contracton múlik, teljesen tetszőleges, persze a legtöbb esetben ajánlott valamilyen strukturált formátumot, például JSON-t használni.
Az Express kibővítése websocket szerverré
Egy kisebb trükköt kell eljátszanunk az Express-szel, hogy az képes legyen működni websocket szerverként is. Pontosítanék, nem az Express fog websocket szerverként működni, hanem ő és a websocket szerver implementáció fognak osztozni egy közös HTTP szerveren. Az Express a háttérben egy standard, a Node.js runtimeban jelenlévő HTTP szervert épít, erre "köti rá" az endpointokat, amiket regisztrálunk benne, tehát a tényleges HTTP forgalmat nem az Express, hanem ez az alacsony szintű HTTP szerver kezeli. A jó hír számunkra, hogy pont erre lesz szükségünk a websocket szerver esetében is, a még jobb hír pedig az, hogy az Express a .listen()
hívás visszatéréseként rendelkezésünkre is bocsátja az említett HTTP szervert (megjegyzés: kézzel is össze lehet rakni a HTTP szervert és azt odaadni az Express-nek, de így egyszerűbb).
A websocket szerver létrehozására a ws
npm libraryt fogjuk használni (cikk végén linkelem). A létrejött HTTP szerver példányt kell majd átadnunk a WebSocketServer
konfigurációjának (ha ezt nem tesszük meg, az is megpróbál készíteni egy saját HTTP szerver példányt, amit most szeretnénk elkerülni). A websocket szerveren regisztrálnunk kell egy connection
esemény figyelőt, ezen keresztül tud reagálni a szerver alkalmazás a kliensek kapcsolódási kéréseire. A ws
library, amennyiben egy kliens sikeresen kapcsolódott, egy Socket
példányt hoz létre, innentől ezen a példányon keresztül tudunk kommunikálni a klienssel (kimenő és bejövő üzenetek tekintetében egyaránt).
A szerver létrehozása az alábbi módon történik:
// Az Express-re bízzuk a HTTP szerver létrehozását ...
const server = this.express
// ...
.listen(this.serverConfig.port, this.serverConfig.host, () => {
// ...
});
// ... majd azt használjuk a WebSocketServer inicializálására
this.webSocketServer = new WebSocketServer({
server: server,
clientTracking: true,
path: "/agent"
});
Az alábbi kódrészlet pedig a kliensek kapcsolódási kéréseit figyeli:
// a callbackben levú socket példány innentől mindig erre a konkrét kommunikációs vonalra vonatkozik
this.webSocketServer.on("connection", (socket, request) => {
// sikeres kapcsolódás esetén authorizáljuk azt
if (!this.agentAuthorizer.authorize(socket, request)) {
return;
}
// csatoljuk az esemény-figyelőket
this.socketHandler.attachListener(socket);
});
A fenti néhány sorral elértük azt, hogy az Express és a WebSocketServer egy közös HTTP szerveren osztoznak majd, egyetlen port lesz nyitva az alkalmazáson, ahol a standard HTTP és a wwebsocket kommunikáció is lebonyolítható. A fenti konfiguráció konkrétan a /agent
endpointot foglalja le websocket kommunikációs célokra, a kliensnek erre a konkrét endpointra kell kapcsolódnia. Az összes többi forgalom standard HTTP (REST) kommunikáció marad, melyet az Express kezel.
Fontos megjegyezni, hogy amennyiben proxy mögött van a szerverünk (például Apache HTTPD VirtualHost proxykat), a proxy szerveren is be kell kapcsolni a websocket támogatást, illetve wss
kapcsolat esetén a megfelelő TLS certificate konfigurálása is szükséges.
Kommunikáció a socketen át
Amennyiben a szerver akar üzenni a kliensnek, a kapcsolódás óta már létezik a socket objektum, amin keresztül ezt megteheti. Az üzenet elküldése a socket objektum .send()
metódusával történik, mely az elküldendő üzenetet várja bármilyen könnyen szeralizálható formátumban (string, byte-array, Buffer, stb.) illetve opcionálisan egy callback függvényt, amit sikeres elküldés esetén hív meg.
// a JSON-string tökéletes választás az elküldéshez
const jsonResponse = JSON.stringify(message);
// a socket változó egy WebSocket objektum a ws libraryből
socket.send(jsonResponse, callback);
Kliens oldalon ugyanez a helyzet, csak a socket objektumot neki kell létrehoznia (ez egyúttal meg is nyitja a kapcsolatot a szerverrel).
const socket = new WebSocket("http://localhost:9999/agent", {
// headerként küldhetünk például authentikációhoz szükséges információt
headers: {}
});
Az üzenetek fogadása mindkét oldalon az on-message esemény figyelő implementálásával történik:
// a data paraméter egy byte-stream, WebSocket.RawData típusba csomagolva
// a .toString() metódus meghívásával visszakapjuk az eredeti üzenetet
socket.on("message", data => {
const message = JSON.parse(data.toString());
// az üzenet itt már feldolgozható formátumban van
});
A konkrét use case a Domino esetében
Az új Domino egy nagyobb lélegzetvételű architekturális átalakításon esett át, melynek egyik célja az volt, hogy a Domino képes legyen több szervert is központosítottan kezelni. Tehát egyetlen koordinátor fut, az API, és több kisebb "agent" csatlakozik a koordinátorhoz. A regisztrált alkalmazások konfigurációja határozza meg, melyik alkalmazást melyik szerverre kell telepíteni. Ehhez alapesetben arra volna szükség, hogy minden agent rendelkezzen egy publikus endpointtal, melyen keresztül az megszólítható a deployment elindításához -- ez egyrészt teljesen értelmetlen, másrészt nem is igazán biztonságos. Ehelyett, az agentek csatlakoznak a koordinátorhoz, a standard HTTP kommunikáció viszont ahhoz kevés, hogy az agentet is meg tudja ezután szólítani a koordinátor. Erre a megoldás az, hogy a koordinátor websocket szerverként is tud üzemelni, így az agentek kétirányú kapcsolatot tudnak azzal nyitni, lehetővé téve, hogy a csatlakozott agenteket a koordinátor bármikor megszólítsa.
WebSocket szerver implementáció az új Domino-ban
WebSocket kliens implementáció az új Domino-ban
Komment írásához jelentkezz be
Bejelentkezés
Még senki nem szólt hozzá ehhez a bejegyzéshez.