Realizacja komunikacji dwustronnej na przykładzie zastosowania WebSocket + Play Framework

Programowanie

WebSocket to technologia będąca częścią specyfikacji HTML5, która mocno uproszczając w założeniu ma rozwiązać problemy stosowania nagminnie technologii AJAX wszędzie tam, gdzie się do tego po prostu nie nadaje. Jednakże do tej pory nie było innej alternatywy - wydaje się że świat webu jest już w pełni gotowy, aby nowa technologia w pełni zagościła w przeglądarkach swoich użytkowników.


WebSocket to technologia będąca częścią specyfikacji HTML5, która mocno uproszczając w założeniu ma rozwiązać problemy stosowania nagminnie technologii AJAX wszędzie tam, gdzie się do tego po prostu nie nadaje. Jednakże do tej pory nie było innej alternatywy - wydaje się że świat webu jest już w pełni gotowy, aby nowa technologia w pełni zagościła w przeglądarkach swoich użytkowników.

Technologia została wprowadzona do przeglądarek Google Chrome już od wersji 4, Firefox 4, Opera 11 czy IE 10, lecz każda nowinka musi dojrzeć do stosowania produkcyjnego, jak i sami deweloperzy dobierając odpowiednie rozwiązania do potrzeb. Aktualnie z powodzeniem możemy korzystać z tej technologii bez obaw o kompatybilność co możemy sprawdzić tutaj: http://caniuse.com/#feat=websockets

WebSocket zapewnia dwukierunkowy kanał komunikacji za pośrednictwem tylko jednego gniazda TCP, co umożliwia na otworzenie interaktywnej sesji komunikacyjnej pomiędzy użytkownikiem (jego przeglądarką), a serwerem. Co więcej specyfikacja API pozwala posługiwać się zdarzeniami podczas wysyłania wiadomości do serwera oraz jej odbierania. Należy zaznaczyć, że nie jest konieczne odpytywanie serwera o wiadomość, co należy robić posługując się technologią AJAX.

Specyfikacja WebSocket do celów komunikacji definiuje nowe URI:

  • ws: dla połączeń nieszyfrowanych
  • wss: dla połączeń szyfrowanych

Poniżej przedstawiamy przykład zastosowania API WebSocket w duecie z frameworkiem Play Framework na serwerze. Play sam w sobie bardzo dobrze wpisuje się w to połączenie swoją bezstanowością oraz natywnym wsparciem dla omawianej technologii.

Część odpowiadająca za nawiązanie połączenia i komunikację po stronie klienta przy założeniu że wysyłane wiadomości są w formacie JSON.

var WS = window['MozWebSocket'] ? MozWebSocket : WebSocket;
var socket = new WS('ws:/' + location.host + '/messenger/chat');

// zdarzenie otwarcia połączenia
socket.onopen = function() {
	console.log('Socket Status: ' + socket.readyState + ' (open)');                
}

// zdarzenie otrzymania wiadomości
socket.onmessage = function(event) {
	var message = JSON.parse(event.data);
	console.log(message);
}

// zdarzenie zamknięcia połączenia
socket.onclose = function() {
	console.log('Socket Status: ' + socket.readyState + ' (Closed)');
}

...

// koniecznie wykonaj jeśli chcesz zamknąć połączenie
socket.close();
socket = null;

...

// wysyłanie wiadomości
socket.send(JSON.stringify({
	messageText: 'Hello WebSocket!'
}));

API po stronie JavaScriptu jest naprawdę przystępne i ciężko w powyższym fragmencie kodu tłumaczyć coś więcej. Myślę że komentarze mówią same za siebie. Pierwsza linijka jest bardzo preferowana i zabezpiecza nas na wypadek kłopotów po stronie przeglądarki Firefoxa.

Część serwerowa jest nieco bardziej skomplikowana, jednakże nie powinna absolutnie sprawiać kłopotów. Na pierwszy rzut definiujemy kwestię routingu:

	GET	/messenger/chat		controllers.Messenger.chat()

Metoda statyczna chat w kontrolerze Messenger wygląda tak:

/**
 * Handle the chat websocket.
 */
public static WebSocket chat() {
	return new WebSocket(){

		// called when websocket handshake is done
		public void onReady(WebSocket.In in, WebSocket.Out out){
			MessengerConnector.connect(in, out);
		}
	};
}

Klasa MessengerConnector pełni tutaj rolę pomocniczą, porządkuje kod oraz robi go czytelniejszym.

package classes;

import com.fasterxml.jackson.databind.JsonNode;
import play.libs.F.Callback;
import play.libs.F.Callback0;
import play.libs.Json;
import play.mvc.WebSocket;

public class MessengerConnector {

    public static void connect(WebSocket.In in, WebSocket.Out out){

        in.onMessage(new Callback() {
            @Override
            public void invoke(JsonNode message) throws Throwable {
                String messageText = message.get("messageText").asText();
	out.write("Hello, your message: " + message);
            }
        });

        in.onClose(new Callback0() {
            public void invoke() {
	// zrób coś, oczyść zasoby etc.
            }
        });
    }
    
}

Tak wygląda bardzo uproszczona do celów prezentacji idei klasa obsługi WebSocket w frameworku Play. Oczywiście należało by ją doposażyć w zmienną przechowującą połączenia, identyfikację użytkownika, metodę notifyAll, czy writeTo, sprawdzanie czy dany użytkownik jest połączony itd. wg potrzeb ;)

Mamy nadzieję, że wielu nowicjuszom w tej dziedzinie ten wpis pomorze ogarnąć połączenie jakie zaprezentowano w tym duecie. Nic nie stoi na przeszkodzie, aby dowolnie rozbudować część kodu po stronie klienckiej jak i serwerowej.

Polecamy stronę dokumentacji Play Framework dotyczącą WebSocket - https://www.playframework.com/documentation/2.4.x/JavaWebSockets

Opublikowano Marzec 2015