Websocket v Jakarta EE

Websocket je technológia, ktorá umožňuje vytvoriť pevné spojenie medzi serverom a klientom (browserom) v rámci webovej aplikácie. Javascript priamo podporuje vytváranie websocketových spojení a po nadviazaní spojenia môže Javascript z browsera kedykoľvek odoslať správu na server alebo server (naša javovská webová aplikácia) môže kedykoľvek poslať správu pripojenému browseru, ktorý má vytvorené aktívne websocketové spojenie). Na strane javascriptu sa spojenie vytvára takto:
    wsocket = new WebSocket("ws://ADRESA_SERVRA:8080/ADRESA_APLIKACIE/MENO_WEBSOCKET_ENDPOINTU");
    wsocket.onmessage = onMessage;

    function onMessage(evt)
    {
        var data = evt.data.split(";");
	// spracuj data
    }

    ...
    wsocket.send("some message...");


    ...
    wsocket.close(); // ak potrebujeme zatvorit spojenie
a na strane webovej aplikácie je potrebné vytvoriť triedu s anotáciou @ServerEndpoint("/MENO_WEBSOCKET_ENDPOINTU"), ktorá môže definovať metódy vyvolané pri jednotlivých udalostiach:
import jakarta.websocket.*;
import jakarta.websocket.server.ServerEndpoint;

@ServerEndpoint("/whatever")
public class SomeOurWebsocketServerEndpointClass
{
    // it is possible to inject references to CDI beans (session or application)
    @Inject
    SomeAppOrSessionBean bean;

    @OnOpen
    public void open(Session session, EndpointConfig conf)
    {
        // websocket session allows us to communicate with the other side
    }

    @OnClose
    public void close(Session session, CloseReason reason)
    {
        // if the connection has been closed, we may want to remove it from our datastructures, free resources, etc.
    }

    @OnMessage
    public void processGreeting(String message, Session session) {
        System.out.println("Greeting received:" + message);
        // send some response back to javascript
        sessesion.getBasicRemote().sendText("some message to send back");
    }

    @OnError
    public void onError(Session session, Throwable throwable) {
        // any error handling here
    } 
V nasledujúcom jednoduchom ilustratívnom príklade vytvoríme malú webovú aplikáciu - hru pre viacerých používateľov, ktorí hádajú tajné číslo vygenerované na serveri. Ten, ktorému sa to podarí skôr, získa bod. Hociktorý používateľ môže hru kedykoľvek reštartovať, potom sa háda od začiatku a ten, čo uhádne získa ďalší bod. Vytvoríme dve verzie: prvá bude založená len na statických stránkach a requestoch, používateľ sa niečo dozvie vždy až po odoslaní svojho pokusu o uhádnutie čísla. Druhá verzia bude využívať websocket: v prípade, že niekto číslo uhádne, všetkým hráčom sa o tom hneď zobrazí správa. Naviac, všetkým hráčom sa zobrazuje aktuálny počet hráčov pripojených na server.

Prvá, statická, verzia aplikácie je vybudovaná čiste pomocou JSF. Obsahuje dve webstránky:

index.xhtml

kde používateľ zadá svoje meno čo ho v zápätí presunie na hlavnú stránku guess.xhtml

guess.xhtml

obsahuje formulár v ktorom používateľ háda náhodné číslo 1-10000, ktoré bolo vopred vygenerované na serveri, zobrazuje sa mu správa o výsledku každého jeho pokusu a celkový počet dosiahnutých bodov.

Na serveri fungujú dva objekty:

guess.Guess

obsahuje vygenerované číslo, má platnosť pre celú aplikáciu (ApplicationScope) a flag isOpen, ktorý udáva, či hra stále beží (je otvorená), alebo skončila a treba ju reštartovať. Nové číslo sa vygeneruje buď pri deploynutí aplikácie, alebo pri stlačení Restart na stránke guess.xhtml, ktoré je s metódou restart() priamo previazané pomocou JSF / Expression Language.

guess.UserGuess

objekt s platnosťou session jedného hráča (každý hráč má svoj objekt UserGuess). Obsahuje jeho meno, aktuálny pokus, celkové dosiahnuté skóre (počet čísel, ktoré už v rámci tejto session uhádol), a momentálne vypísanú správu - reakciu na jeho pokus. Odpoveď na jeho pokus sa mu generuje v metóde getMessage(), pričom sa v prípade uhádnutia čísla v aplikačnom objekte Guess overuje, či už niekto iný číslo neuhádol skôr.

Hru si vyskúšajte a otvorte naraz z rôznych prehliadačov (na samostatnú session vždy môžete použiť súkromné/inkognito okno prehliadača.

Verzia pre Jakarta EE 9: websockety.zip

Verzia pre Java EE 8: GuessNumber-ee8.zip

Druhá, interaktívna a dynamická verzia aplikácie využíva websocket, ktorý sa vytvára nanovo pri každom novom requeste zaslanom z klientského webového prehliadača. Cez websocket sa do browsera posielajú dva druhy správ: počet aktuálne hrajúcich hráčov (ten sa získava podľa toho, koľko websocketových sessions je práve otvorených, tieto správy majú formát "1;pocet_hracov") a správa o uhádnutí čísla nejakým iným hráčom a o reštartovaní hry (tieto správy majú formát "2;sprava"). Aby browser mohol správy kedykoľvek prijať a spracovať, potrebuje kúsok Javascriptového kódu, ktorý pri otvorení stránky guess.xhtml vytvorí websocket-ové spojenie na server a pridá handler, ktorý bude spracovávať všetky správy doručené cez websocket. Spracovanie správ je v našom prípade triviálne - len sa vyrenderuje nový počet hráčov, alebo správa zo servera do príslušného outputText komponentu.

Na strane servera nám pribúda tretia trieda

guess.GameOverNotifier

ktorá reprezentuje serverovský koniec websocketu - vyveseného pomocou anotácie @ServerEndpoint na adrese ws://localhost:8080/ADRESA_APLIKACIE/guessnotify. Websocket protokol je rozšírením HTTP protokolu a má teda výhodu, že komunikácia prebieha po rovnakých portoch ako bežná komunikácia s HTTP serverom a nepodlieha obmedzeniam firewallov, ako sme mohli vidieť v prípade bežných socketov, ktoré nám komunikovali na iných portoch na začiatku semestra.

Keďže objekt tejto triedy je nový pri každom novom requeste (načítaní stránky), musíme objektu Userguess vždy oznámiť referenciu na seba, aby v prípade potreby mohla všetkým hráčom odoslať správu o reštarte alebo ukončení hry. (alternatívne a asi korektnejšie riešenie by do passivation-capable session beanov neukladalo referencie na iné objekty, ktoré spravuje aplikačný server a vždy využije korektne injectnutý aplication-scoped bean na vzájomnú interakciu, ale pre naše potreby funguje aj toto riešenie). CDI-čkovská anotácia @Inject nám dovoľuje do requestového websocketového endpointu vložiť vždy platnú referenciu na applikačný objekt triedy Guess a session-ovský objekt triedy Userguess. Pri vytváraní projektu v IntelliJ už nebude stačiť zaškrtnúť iba CDI a JSF špecifikácie, ale aj Websocket, ale stále vystačíme s implementáciami Mojarra JSF a Weld SE pre CDI.

Verzia pre Jakarta EE 9: websocknaozaj.zip

Verzia pre Java EE 8: WebSocketGuessNumber-ee8.zip



Teraz už môžete pristúpiť k riešeniu domácej úlohy - naprogramujte sieťovú hru padacie piškvorky (connect 4) pre dvoch hráčov (pozri LIST).