TSM - Websockets – http pe steroizi

Philip Peterhansl - IT Consultant Automotive

Http a apărut în 1999, dar cererea în continuă creștere a aplicațiilor web moderne pentru servere push și un protocol de comunicare mai eficient a dus la definirea protocolului Websocket în 2011.

Întrebările puse la Conferința Java 2014 în Cluj au fost simptomatice pentru noile tehnologii:

Acest articol promovează Websockets și descrie în linii generale cum să le folosești și motivele pentru care ar trebui să le folosești.

Securitatea Websocket este un subiect important, dar nu poate fi acoperit în acest articol. Vedeți Linkuri utile pentru mai multe informații.

Cum se folosesc Websockets?

Implementările Websocket partajează o definiție de interfață comună între server, client și chiar între limbajele de programare. Următoarele exemple de cod vor implementa un exemplu elementar de dialog prin conectarea unui client Java și unul Javascript la un server Java.

Utilizați următoarea funcție expert în proiectele voastre pentru server și client Java pentru a include API JSR-356 pentru Java Websockets:

<dependency>
    <groupId>javax.websocket</groupId>
    <artifactId>javax.websocket-api</artifactId>
    <version>1.1</version>
</dependency>

Server Java: Adnotat ServerEndpoint

Mai întâi creăm serverul utilizând ServerEndpoint adnotat, definit în JSR-356. Acolo mai există și o specificație pentru crearea programatică a punctului final (endpoint), dar nu ne vom ocupa de aceasta în acest articol.

Mai întâi codul, apoi explicațiile mai jos:

@ServerEndpoint(value = "/service")
public class ChatServerEndpoint {

    private static final Set connections =
            new CopyOnWriteArraySet<>();

    private Session session;

    @OnOpen
    public void start(Session session) {
        this.session = session;
        connections.add(this);
    }

}

Proprietatea valoare a ServerEndpoint definește calea pe care acest endpoint o receptează. URL-ul pentru conectarea la acest endpoint este: ws://://.

Adnotația @OnOpen marchează metoda start care va fi apelată atunci când se conectează un client. Ar trebui să faceți toată inițializarea care este necesară. În exemplul dialogului, sesiunea clientului conectat este memorată în setul de conexiuni, pentru utilizare ulterioară în interiorul metodei de difuzare.

Ce se întâmplă dacă un client se deconectează? Definiți o metodă, adnotați-o cu adnotația @OnClose și aceasta va fi apelată ori de câte ori un client se deconectează, indiferent de cauză:

@OnClose
public void end() {
    connections.remove(this);
}

În acest exemplu simplu de dialog, clientul deconectat este înlăturat din setul de conexiuni.

Nucleul oricărei aplicații antrenate de date, care implementează Websockets, este trimiterea și primirea de date. Soluția cea mai elementară pentru primirea de mesaje într-un JSR Endpoint este adnotarea unei metode cu @OnMessage și următoarea semnătură:

@OnMessage
public void incoming(String message) {
    broadcast(message);
}

Există mai multe moduri sofisticate de a primi (și a trimite) mesaje utilizând codoare și decodoare cu mesaje text și binare, dar nu ne vom ocupa de ele în acest articol. Căutați în API JSR-356 mai multe informații.

Trimiterea de mesaje se face prin sesiunea clientului care primește. Metoda broadcast trece prin fiecare client conectat din setul de conexiuni și trimite mesajul utilizând BasicRemote:

private static void broadcast(String msg) {
    for (ChatAnnotation client : connections) {
        try {
            synchronized (client) {
                client.session.getBasicRemote().sendText(msg);
            }
        } catch (IOException e) {
            connections.remove(client);
            try {
                client.session.close();
            } catch (IOException e1) {
                // Ignore
            }
        }
    }
}

BasicRemote obținut prin metoda getBasicRemote() din sesiunea clientului trimite mesajul în modul blocare. Există o implementare non-blocare, numită AsyncRemote. Aceasta este obținută prin folosirea metodei getAsyncRemote() a sesiunii clientului.

Acum avem un server care poate primi mesaje de la orice client și le poate trimite mai departe către toți clienții conectați. Haideți să ne uităm la implementarea clientului Java.

Client Java

Pentru crearea unui client Websocket în Java, un ClientEndpoint trebuie să fie definit într-un mod similar cu ServerEndpoint:

@ClientEndpoint
public class RemoteWebsocketClient {

    private Session session;

    @OnOpen
    public void onOpen(Session session) {
        this.session = session;
    }

    @OnMessage
    public void onMessage(String message) {
        System.out.println(message);
        //TODO: output message in a chat window
    }

    @OnClose
    public void onClose(Session session, CloseReason reason) {

    }

}

În acest exemplu simplu de dialog, noi trebuie doar să implementăm metoda adnotată @OnMessage. Metoda numai indică mesajul primit pe consolă.

Veți avea nevoie de o implementare JSR-356 pe partea client, deoarece nu există nicio implementare cuprinsă în Java JRE. Implementarea standard este Project Tyrus și este utilizată în acest exemplu.

Adăugați următoarele funcții la POM al proiectului client al vostru pentru a utiliza Tyrus în modul autonom:

<dependency>
    <groupId>org.glassfish.tyrus</groupId>
    <artifactId>tyrus-server</artifactId>
    <version>1.0</version>
</dependency>

<dependency>
    <groupId>org.glassfish.tyrus</groupId>
    <artifactId>tyrus-client</artifactId>
    <version>1.0</version>
</dependency>

<dependency>
    <groupId>org.glassfish.tyrus</groupId>
    <artifactId>tyrus-container-grizzly</artifactId>
    <version>1.0</version>
</dependency>

Să conectăm clientul la server. Este foarte ușor, prin apelarea metodei connectToServer din ClientManager. Vezi sursa completă pentru ChatClient:

public class ChatClient {

    private ClientManager manager;

    public void connect(URI uri) {
        this.manager = ClientManager.createClient();
        try {
            manager.connectToServer(ChatClientEndpoint.class, uri);
        } catch (DeploymentException | IOException e) {
            System.exit(-1);
        }
    }

    public void run() {
        String msg = System.in.readLine();
        while (!"".equals(msg)) {
            manager.getSession().getBasicRemote().send(msg);
            msg = System.readLine();
        }
    }

    public void main(String[] args) {
        ChatClient cc = new ChatClient();
        cc.connect(URI.create("ws://localhost:8080/chat/service"));
        cc.run();
    }
}

Clientul de dialog se conectează la server prin apelarea connectToServer, furnizând o clasă de implementare ClientEndpoint și un URL la care să te conectezi. Bucla principală așteaptă ca input-ul de la utilizator să fie trimis clientului. Operarea mesajelor este realizată de către ChatClientEndpoint.

Javascript

Codul pentru un client Websocket Javascript este aproape identic cu Java ClientEndpoint. Conexiunea este realizată într-un mod specific browser-ului, căci Mozilla Firefox are o implementare individuală numită MozWebsocket în loc de Websocket standard:

var connect = function(url) {
    if ('WebSocket' in window) {
        socket = new WebSocket(url);
    } else if ('MozWebSocket' in window) {
        socket = new MozWebSocket(url);
    } 
};

Operarea mesajelor are loc în interiorul metodei socket.onmessage, care ar trebui să fie suprareglată. Pentru afișarea mesajelor dialog, vom crea un nou paragraf și îl vom atașa unei părți div din pagina html afișată.

socket.onmessage = function(msg) {
    var log = document.getElementById('log');
    var p = document.createElement('p');

    p.style.wordWrap = 'break-word';
    p.innerHTML = msg.data;
    log.appendChild(p);
}

Trimiterea se realizează prin utilizarea metodei de trimitere din socket:

send: function(msg) {
    socket.send(msg);
}

De ce să utilizăm websockets?

Specificația Websocket abordează problemele întâlnite de aplicațiile web moderne care folosesc protocolul http.

Cu Websockets primești:

Dezavantaje

Principalul dezavantaj al specificației websocket este că nu este specificat nici un protocol de nivel al aplicației. Aici suntem provocați noi ca dezvoltatori, să preluăm inițiativa și să dezvoltăm niște protocoale de comunicare generice pentru aplicații, care să completeze această lipsă.

Cu un protocol generic al aplicației, putem aborda al doilea dezavantaj: protocolul websocket nu specifică un mecanism pentru garantarea livrării mesajelor – o cerință majoră în utilizarea comercială.

Deși nu sunt de nerezolvat, aceste două probleme sunt răspunzătoare pentru faptul că Websockets nu are amploarea pe care o merită.

Pe scurt

După părerea mea, Websockets are într-adevăr beneficii imense în comparație cu comunicarea http tradițională. De aceea, aștept cu nerăbdare să utilizez Websockets pentru un produs comercial în viitor.

Aș dori să compar Websockets cu XMLHttpRequest, înainte de a avea parte de o reclamă exagerată, ca parte din AJAX: de îndată ce XMLHttpRequest a devenit utilizabil cu biblioteci terțe părți ușor de utilizat și de încredere, a început Web 2.0.

Websockets mai necesită îmbunătățiri în ceea ce privește utilizarea mai facilă a bibliotecilor de asemenea, conducându-se astfel aplicațiile client-server comerciale la un nivel nou.

Suport platformă

Browsers

Mobile Browsers

Servers

Link-uri utile