Vert.x ist ein Framework, das sich Herausforderungen stellt, die durch neue Web-Technologien wie WebSockets auftreten. Herkömmliche Java-Web-Anwendungen laufen in einem Servlet-Container, bei dem jeder Request in einem separaten Thread abgearbeitet und das Ergebnis im Response zum Client zurückgeschickt wird. Anschließend wird die hierfür benötigt Netzwerkverbindung abgebaut und der Thread kann sich um den nächsten Request kümmern. Bei WebSockets wird eine bidirektionale Verbindung zwischen Server und Client benötigt, damit der Server in der Lage ist, dem Client Nachrichten zukommen zu lassen. Hierfür werden permanent offen Netzwerkverbindungen etabliert, so dass zukünftig Server eine wesentlich höhere Zahl von ihnen unterstützen müssen. Für diese Problemstellung hat Vert.x eine Lösung gefunden. Anfragen werden in Verticles abgearbeitet, die in Event Loops laufen. Bei letzteren handelt es sich um Threads, von denen mehrere parallel laufen können. Typischerweise entspricht die Anzahl der Event Loops der Anzahl an vorhandenen CPUs. Es ist wichtig, dass Verticles längere Operationen asynchron ausführen und für nachgelagerte Aktionen Callbacks registrieren. Anderenfalls werden auch andere Verticles blockiert, die auf dem gleichen Event Loop laufen.
Weiterhin ist Vert.x ein polyglottes Framework, welches nicht nur Java, sondern auch Groovy, Pyhton, Ruby, JavaScript und CoffeeScript unterstützt. Dabei geht Vert.x auf die jeweiligen Sprachspezifika ein, so dass sich die Verwendung recht natürlich anfühlt. Alle diese Programmiersprachen laufen auf der JVM, bzw. es existieren Implementierungen für die JVM. So gibt es für Jacascript und CoffeeScript die JavaScript-Engine Rhino, für Phyton JPhyton und Ruby JRuby.
Ein wichtiger Bestandteil von Vert.x ist die Modularisierung mittels Modulen. Diese bestehen aus Klassen und einer JSON-Konfiguration, die unter anderem das zu startende Verticle bestimmt. Andere Module können über includes referenziert und eingebunden werden. Es steht eine Vielzahl von Modulen zur Verfügung, eine vollständige Auflistung findet man unter https://github.com/vert-x/vertx-mods/tree/gh-pages/mods. Folgende Module verdienen eine extra Erwähnung:
- vertx.web-server-v1.0 (Web-Server)
- vertx.mongo-persistor-v1.2.1 (Mongo-Persistor)
- vertx.mailer-v1.1 (Mailer)
- vertx.auth-mgr-v1.1 (Authorisation-Manager)
Auch Module von Fremd-Anbietern sind hier zu finden, wie z.B:
- com.bloidonia.jdbc-persistor-v1.2 (JDBC-Persistor)
Die Kommunikation zwischen den Modulen findet über einen Event-Bus statt. Auf diesem werden Handler registriert, die auf bestimmte Topics reagieren. So können Nachrichten sowohl innerhalb, als auch modulübergreifend ausgetauscht werden. Nachrichten werden asynchron gesendet und empfangen und können auch von mehreren Empfängern konsumiert werden. Der Bus kann lokal in einer JVM laufen oder in einem Cluster, dass sich über mehrere Rechner verteilt. Nachrichten können außer aus primitiven Datentypen auch aus JSON-Objekte bestehen, so dass eine Kommunikation zwischen Modulen auch ohne gemeinsame Abhängigkeiten möglich ist. Als Alternative zum Event-Bus können Daten über einem gemeinsamen Speicher ausgetauscht werden. So lässt sich z.B. ein globales Caching realisieren.
Anfang Januar kam es zu einem Disput um die Zukunft des Projekts. Hintergrund war der Wechsel des führenden Vert.x Entwicklers Tim Fox von VMware zu Red Hat. Mittlerweile wurden die Unstimmigkeiten beigelegt und Vert.x wird zur Eclipse Foundation wechseln.
Im nun nachfolgenden Beispiel wird ein Chatprogramm mittels WebSockets realisiert. Teilnehmende Benutzer müssen sich authentifizieren, wobei ihre Zugangsdaten in einer MongoDB gespeichert sind. Hierfür kommen die Vert.x Module Mongo-Persistor und Authorisation-Manager zum Einsatz. Der Chat enthält zusätzlich zur eigentlichen Nachricht den Namen des sendenden Benutzers. Als Clients dienen HTML5-fähige Browser und ein Java-Client, der als Bot fungiert und auf bestimmte Anfragen Antworten zum Chat beiträgt.
Zunächst wird die aktuelle Vert.x Version 1.3.1 von der Projekt-Seite http://vertx.io gedownloadet und das Zip-Archiv in ein beliebiges Verzeichnis entpackt. Dieses wird anschließend der System-Variable Path hinzugefügt, wobei hier auf das Unterverzeichnis bin verwiesen wird. Nun kann die hier befindliche Datei vertx.bat von überall ausgeführt werden. Ob die Installation geklappt hat, kann mit vertx version überprüft werden.
Nun wird ein Maven-Projekt mit folgenden Dependencies erstellt:
<dependency>
<groupId>org.vert-x</groupId>
<artifactId>vertx-core</artifactId>
<version>1.3.1.final</version>
</dependency><dependency>
<groupId>org.vert-x</groupId>
<artifactId>vertx-platform</artifactId>
<version>1.3.1.final</version>
</dependency>
Vert.x läuft ausschließlich unter JDK 7, so dass das Maven-Compiler-Plugin eine entsprechende Konfiguration benötigt:
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.7</source>
<target>1.7</target>
<showDeprecation>true</showDeprecation>
<showWarnings>true</showWarnings>
<fork>true</fork>
<executable>${JAVA_HOME}/bin/javac</executable>
</configuration>
</plugin>
</plugins>
</build>
Innerhalb der Projekt-Struktur wird nun das Verzeichnis web erstellt, in dem die HTML-Seiten abgelegt werden. Zunächst wird hier die Datei index.html mit dem obligatorischen Hallo-Welt-Gruß erzeugt:
<html>
<body>
<p>Hallo Welt</p>
</body>
</html>
Nun wird die Klasse MyVertxServer angelegt, die von Verticle erbt und die start-Methode überschreibt. Bei dieser Klasse handelt es sich um eine der anfänglich beschriebenen Verticles, die von Vert.x in einem Event-Loop ausgeführt wird. Hier wird nun ein Http-Server gestartet, der auf Port 8080 erreichbar ist. Es wird ein Request-Handler registriert, der alle Anfragen annimmt und als Response den Inhalt der index.html-Datei liefert:
public class MyVertxServer extends Verticle {
@Override
public void start() {
HttpServer httpServer = vertx.createHttpServer();
httpServer.requestHandler(new Handler<HttpServerRequest>() {@Override
public void handle(HttpServerRequest req) {
req.response.sendFile(„web/index.html“);
}
});
httpServer.listen(8080);
}
}
Wenn man nun in der Konsole in das Projekt-Verzeichnis wechselt, kann der Server wie folgt gestartet werden:
vertx run src/main/java/de/mz/vertx/MyVertxServer.java
Hierbei wird die Java- und nicht die Class-Datei referenziert, da diese automatisch von Vert.x kompiliert wird. Bei der Erstellung eines Moduls, können aber auch Class-Dateien verwendet werden, um so die Kompilierung bei jedem Start des Moduls zu verhindern. Ruft man nun im Browser http://127.0.0.1:8080 auf, erscheint die „Hallo Welt“-Seite.
Theoretisch reicht es, einen Request-Handler zu registrieren und über den Path des HttpServerRequest auf die entsprechenden HTML-Seiten zu verweisen. Da dies recht schnell unübersichtlich wird, empfiehlt sich die Verwendung von RouteMatchern. Hier wird ein Handler für ein bestimmtes URL-Pattern registriert, nur bei passendem Request-Path wird nun der Handler ausgeführt:
RouteMatcher routeMatcher = new RouteMatcher();
routeMatcher.get(„/chat“, new Handler<HttpServerRequest>() {
@Override
public void handle(HttpServerRequest req) {
req.response.sendFile(„web/chat.html“);
}
});
Erfolgt ein Get-Request auf die URL http://127.0.0.1:8080/chat sorgt obiger RequestHandler dafür, dass der Inhalt von chat.html im Browser angezeigt wird. Hierbei handelt es sich um die Ressource, über die der Chat erfolgen soll. Die Implementierung der HTML-Seite wird später noch erläutert. Zunächst wird nun noch ein weiterer RequestHandler für alle nicht zuzuordnenden Requests registriert. Wird eine nicht vorgesehene Server-URL im Browser aufgerufen, soll automatisch der Inhalt der Start-Seite angezeigt werden:
routeMatcher.noMatch(new Handler<HttpServerRequest>() {
@Override
public void handle(HttpServerRequest req) {
req.response.sendFile(„web/index.html“);
}
});
Zu guter Letzt muss der RouteMatcher dem HttpServer als RequestHandler zugewiesen werden:
httpServer.requestHandler(routeMatcher);
Nun wird ein Repository benötigt, welches für die Verwaltung der WebSocket-Connections zuständig ist. Empfängt der Server einen WebSocket-Request von einem unbekannten Client, wird die Verbindung gespeichert. Beim Schließen der WebSocket-Verbindung wird die Verbindung wieder entfernt:
public class WebSocketRepository {
private List<ServerWebSocket> webSockets = new ArrayList<ServerWebSocket>();
public void addWebSocket(ServerWebSocket webSocket) {
webSockets.add(webSocket);
}public List<ServerWebSocket> getWebSockets() {
return webSockets;
}public void removeWebSocket(ServerWebSocket webSocket) {
webSockets.remove(webSocket);
}
}
In der Server-Klasse MyVertxServer wird nun eine Instanz des Repositorys erzeugt:
private WebSocketRepository repository = new WebSocketRepository();
Im nächsten Schritt wird ein EventBus registriert, der Chat-Nachrichten an alle registrierten WebSocket-Verbindungen weiterleitet:
vertx.eventBus().registerHandler(„de.mz.chat“, new Handler<Message<JsonObject>>() {
@Override
public void handle(Message<JsonObject> message) {
String out = message.body.getString(„name“) + „: “ + message.body.getString(„message“);
for (ServerWebSocket webSocket : repository.getWebSockets()) {
webSocket.writeTextFrame(out);
}
}
});
Hierfür wird auf dem Event-Bus ein Handler für das Topic de.mz.chat registriert. Die Message, die der Handler übergeben bekommt, enthält den Namen des Senders und die Nachricht. Im Chat werden beide mit einem Doppelpunkt separiert ausgegeben. Nun wird über alle im Repository registrierten WebSocket-Verbindungen iteriert und ihnen die Chat-Meldung mitgeteilt.
Anschließend wird ein WebSocketHandler registriert und hier der Client einmalig im Repository hinterlegt. Für jede WebSocket-Verbindung wird ein DataHandler angemeldet, der dann aufgerufen wird, wenn der Client dem Server eine Nachricht schickt. Damit die Nachricht an alle registrierten Clients verteilt wird, muss diese als Event an das oben definierte Topic gesendet werden. Hierzu werden die Informationen aus dem Buffer gelesen und in ein JSON-Objekt gewrappt. Zusätzlich wird ein CloseHandler benötigt, der den Client aus dem Repository entfernt, sobald die Connection Client-Seitig geschlossen wird. Wie man unschwer an den mehrfach geschachtelten Handlern erkennen kann, wird eine Vert.x Anwendung in Java recht schnell unübersichtlich:
httpServer.websocketHandler(new Handler<ServerWebSocket>() {
@Override
public void handle(final ServerWebSocket ws) {
repository.addWebSocket(ws);
ws.dataHandler(new Handler<Buffer>() {@Override
public void handle(Buffer data) {
String name = data.toString().split(„:“)[0];
String message = data.toString().split(„:“)[1];
JsonObject messageObject = new JsonObject();
messageObject.putString(„name“, name);
messageObject.putString(„message“, message);
vertx.eventBus().send(„de.mz.chat“, messageObject);
}
});
ws.closedHandler(new Handler<Void>() {@Override
public void handle(Void event) {
repository.removeWebSocket(ws);
}
});
}
});
Nun ist der Zeitpunkt gekommen, die Datei chat.html zu erstellen. Mittels Javascript wird eine WebSocket-Verbindung zum Vert.x Server aufgebaut, wobei das WS-Protokol zum Einsatz kommt. Ankommende Server-Nachrichten werden in der onmessage-Funktion behandelt und hier in das unten definierte Textfield mit der Id msgs geschrieben. Um eigene Nachrichten zum Server zu schicken, verweist das onclick-Attribut des Input-Feldes message auf die send-Funktion. Hier wird über die WebSocket-Verbindung die Nachricht zum Server geschickt. Später soll zusätzlich zur eigentlichen Nachricht, der Name des angemeldeten Benutzers mitgesendet werden. Da zurzeit noch keine Authentifizierung umgesetzt ist, wird statt dem Benutzername ‚unknown user‘ gesendet:
<html>
<body>
<script type=“text/javascript“>
var socket;
if (!window.WebSocket) {
window.WebSocket = window.MozWebSocket;
}
if (window.WebSocket) {
socket=new WebSocket(‚ws://127.0.0.1:8080‘);
socket.onmessage = function(event) {
var msg = document.getElementById(‚msgs‘);
msg.value = msg.value + ‚rn‘ + event.data;
};
}function send(message) {
if (!window.WebSocket) {
return;
}
if (socket.readyState == WebSocket.OPEN) {
socket.send(message);
var msg = document.getElementById(‚message‘);
msg.value=“;
} else {
alert(„The socket is not open.“);
}
}
</script>
<h3>Enter Message</h3>
<form onsubmit=“return false;“>
<input type=“text“ name=“message“ id=“message“/>
<input type=“button“ value=“send“ onclick=“send(‚unknown user:‘ + this.form.message.value)“/>
</form>
<h3>Messages</h3>
<p><textarea id=“msgs“ style=“width:500px;height:300px;“></textarea></p>
</body>
</html>
Nun ist die Zeit für den ersten Test gekommen. Nach einem Neustart des Servers wird hierzu die URL http://127.0.0.1:8080/chat in zwei HTML5 fähigen Browsern aufgerufen. Gibt man hier nun Chat-Nachrichten ein, müssen diese in beiden Browsern angezeigt werden. Damit steht der erste Teil des Chat-Programms. Im Folgenden soll die Benutzer-Authentifizierung umgesetzt werden, wozu auch eine Registrierung gehört. Hierfür werden die Module vertx.mongo-persistor-v1.2 und vertx.auth-mgr-v1.1 benötigt, die man in der start-Methode des Servers installiert:
getContainer().deployModule(„vertx.mongo-persistor-v1.2“);
getContainer().deployModule(„vertx.auth-mgr-v1.1“);
Startet man den Server erneut, erscheint folgende Nachricht auf der Konsole:
Attempting to install module vertx.mongo-persistor-v1.2 from http://vert-x.github.com:80/vertx-mods/mods/vertx.mongo-persistor-v1.2/mod.zip
Downloading module…
Installing module into directory ‚mods‘
Module vertx.mongo-persistor-v1.2 successfully installedAttempting to install module vertx.auth-mgr-v1.1 from http://vert-x.github.com:80/vertx-mods/mods/vertx.auth-mgr-v1.1/mod.zip
Downloading module…
Installing module into directory ‚mods‘
Module vertx.auth-mgr-v1.1 successfully installed
Wenn man sich anschließend die Projekt-Struktur anschaut, findet man beide Module im Verzeichnis mods. Sofern auf dem Server noch keine MongoDB läuft, muss diese eingerichtet werden. Hierfür kann die aktuelle MongoDB Version 2.4.2 unter der URL http://www.mongodb.org bezogen werden. Sofern bei der Installation die Standard-Einstellungen verwendet werden, kann das Mongo-Persistor-Modul ohne weitere Einstellungen mit der MongoDB kommunizieren. Falls Zugangsdaten definiert oder ein anderer Port angegeben ist, muss das Modul zunächst entsprechend konfiguriert werden. Hierzu kann die deployModule-Methode mit einem JSON-Objekt als zusätzlichen Parameter aufgerufen werden, welches die gewünschte Konfiguration beinhaltet. Es folgt eine Auflistung der Konfigurationsmöglichkeiten inklusive der jeweiligen Default-Einstellungen:
- host: localhost
- port: 27017
- db_name: default_db
- username: null
- password: null
Nun muss die Möglichkeit geschaffen werden, dass sich Benutzer registrieren können. Dafür wird die Seite register.html erstellt, die ein Formular mit den Eigenschaften Benutzername, Passwort, Vorname und Nachname besitzt. Beim Absenden wird das Formular per POST-Request an die URL http://127.0.0.1:8080/register geschickt:
<html>
<body>
<h1>Register</h1>
<form action=“/register“ method=“post“>
Username:<input name=“username“ id=“username“ type=“text“ /><br>
Password:<input name=“password“ id=“password“ type=“password“ /><br>
Forename:<input name=“forename“ id=“forename“ type=“text“ /><br>
Lastname:<input name=“lastname“ id=“lastname“ type=“text“ /><br>
<input value=“register“ type=“submit“>
</form>
</body>
</html>
Anschließend wird ein weiterer RouteMatcher-Eintrag für den Aufruf der Register-Seite benötigt:
routeMatcher.get(„/register“, new Handler<HttpServerRequest>() {
@Override
public void handle(HttpServerRequest req) {
req.response.sendFile(„web/register.html“);
}
});
Außerdem wird ein zusätzlicher RouteMatcher-Eintrag für die Verarbeitung der Formular-Daten des POST-Request notwendig. Auf dem Request wird hierfür ein DataHandler registriert, der die Formular-Daten in einem Buffer-Objekt erhält. Um die Parameter aus diesem auszulesen, wird der QueryStringDecoder von Netty verwendet. Anschließend sind die Parameter in einer Map verfügbar, sie können nun bequem ausgelesen und in einem JSON-Objekt abgelegt werden. In einem weiteren JSON-Objekt wird dem MongoDB-Modul mitgeteilt, welche Aktion auf welcher Collection durchgeführt werden soll. Zum Speichern wird die action save auf der Collection users ausgeführt, da hier das Authentication-Modul standardmäßig die Benutzer-Daten erwartet. Nun wird dem MongoDB-Modul per Event auf dem Topic vertx.mongopersistor die Datenbank-Änderung mitgeteilt. In dem hier definierten Callback-Handler wird nach erfolgter Speicherung auf die Seite registerSuccess.html verwiesen, so dass dem Benutzer eine Statusmeldung angezeigt wird. Auf den Inhalt der HTML-Seite wird hier einfachhalthalber nicht eingegangen:
routeMatcher.post(„/register“, new Handler<HttpServerRequest>() {
@Override
public void handle(final HttpServerRequest req) {
req.dataHandler(new Handler<Buffer>() {@Override
public void handle(Buffer buff) {
QueryStringDecoder decoder = new QueryStringDecoder(buff.toString(), false);
Map<String, List<String>> parameters = decoder.getParameters();
JsonObject user = new JsonObject();
user.putString(„username“, parameters.get(„username“).get(0));
user.putString(„password“, parameters.get(„password“).get(0));
user.putString(„forename“, parameters.get(„forename“).get(0));
user.putString(„lastname“, parameters.get(„lastname“).get(0));JsonObject matcher = new JsonObject();
matcher.putString(„action“, „save“);
matcher.putString(„collection“, „users“);
matcher.putObject(„document“, user);vertx.eventBus().send(„vertx.mongopersistor“, matcher, new Handler<Message<JsonObject>>() {
@Override
public void handle(Message<JsonObject> event) {
req.response.sendFile(„web/registerSuccess.html“);
}
});
}
});
}
});
Nun wird die Seite users.html erstellt, um eine Liste aller Benutzer anzeigen zu können. Die Ausgabe erfolgt in einer Tabelle, wobei hier nur die Header-Informationen hinterlegt sind. Da Vert.x unter Java von Haus aus keine Template-Unterstützung bietet, wird in der HTML-Datei der Platzhalter $USER_LIST eingetragen, der später durch die Benutzer-Liste ersetzt wird:
<html>
<body>
<h1>Users</h1>
<table>
<tr>
<th>Username</th>
<th>Forename</th>
<th>Lastname</th>
<th>Password</th>
</tr>
$USER_LIST
</table>
</body>
</html>
Anschließend wird der RouteMatcher für die URL users registriert und ein weiterer Request-Handler angemeldet. Wieder wird ein Event an das MongoDB-Modul gesendet, diesmal mit der action find. Als matcher wird ein leeres JSON-Objekt genommen, so dass keine Einschränkungen auf dem Such-Ergebnis vorgenommen werden. Der Event-Handler, der für die Behandlung des Datenbank-Ergebnisses registriert wurde, liest die Datei users.html ein. Auf den Inhalt der Datei kann in einem AsyncResultHandler zugegriffen werden, sobald die IO-Operation abgeschlossen ist. So werden Operationen auf dem Datei-System asynchron realisiert, damit auf demselben Event-Loop laufende Verticles nicht blockieren. Die Benutzer-Informationen werden in einzelnen Tabellen-Zeilen dargestellt, wobei der HTML-Inhalt in der For-Schleife zusammengebaut wird. Ist dies geschehen, wird der Platzhalter $USER_LIST durch den HTML-Inhalt ersetzt.
routeMatcher.get(„/users“, new Handler<HttpServerRequest>() {
@Override
public void handle(HttpServerRequest req) {
JsonObject query = new JsonObject();
query.putString(„action“, „find“);
query.putString(„collection“, „users“)
query.putObject(„matcher“, new JsonObject());
vertx.eventBus().send(„vertx.mongopersistor“, query, new Handler<Message<JsonObject>>() {@Override
public void handle(final Message<JsonObject> message) {
vertx.fileSystem().readFile(„web/users.html“, new AsyncResultHandler<Buffer>() {@Override
public void handle(AsyncResult<Buffer> event) {
JsonArray users = message.body.getArray(„results“);
StringBuffer userContent = new StringBuffer(100);
for (int i = 0; i < users.size(); i++) {
JsonObject user = (JsonObject) users.get(i);
userContent.append(„<tr>“);
userContent.append(„<td>“);
userContent.append(user.getString(„username“));
userContent.append(„</td>“);
userContent.append(„<td>“);
userContent.append(user.getString(„forename“));
userContent.append(„</td>“);
userContent.append(„<td>“);
userContent.append(user.getString(„lastname“));
userContent.append(„</td>“);
userContent.append(„<td>“);
userContent.append(user.getString(„password“));
userContent.append(„</td>“);
userContent.append(„</tr>“);
}String htmlContent = event.result.toString().replace(„$USER_LIST“, userContent.toString());
req.response.end(htmlContent);
}
});
}
});
req.response.setChunked(true);
}
});
Das oben verwendete Verfahren ist recht simple und bei einer so einfachen HTML-Vorlage gut einzusetzen. Kommen komplexe Vorlagen zum Einsatz, so bietet sich hierzu die Groovy Template Engine an.
Die Registrierung und Darstellung von Benutzern ist abgeschlossen, nun soll unter Verwendung der Benutzer-Daten eine Authentifizierungs-Möglichkeit geschaffen und die Ressourcen Chat und Users geschützt werden. Hierfür wird zunächst ein Login-Dialog erstellt:
<html>
<body>
<h1>Login</h1>
<form action=“/login“ method=“post“>
Username:<input name=“username“ id=“username“ type=“text“ /><br>
Password:<input name=“password“ id=“password“ type=“password“ /><br>
<input value=“login“ type=“submit“>
</form>
</body>
</html>
Der RouteMatcher erhält einen weiteren Handler, der auf die URL login reagiert und den Inhalt der soeben erstellten HTML-Datei anzeigt:
routeMatcher.get(„/login“, new Handler<HttpServerRequest>() {
@Override
public void handle(HttpServerRequest req) {
req.response.sendFile(„web/login.html“);
}
});
Neben dem Get- wird auch ein Post-Request für dieselbe URL registriert, um Parameter aus dem Login-Formular auszulesen und in einem JSON-Objekt zu hinterlegen. Anschließend wird ein Event an das Topic vertx.basicauthmanager.login gesendet, auf dem das Modul vertx.auth-mgr-v1.1 lauscht. Dieses wertet die Parameter username und password aus dem übergebenen JSON-Objekt aus. Befindet sich in der MongoDB in der Collection users ein entsprechender Benutzer, so war die Authentifizierung erfolgreich. Die Antwort des Authentifizierungs-Moduls kann in einem Handler erfragt werden. Ist hier das Attribut sessionID gesetzt, war die Authentifizierung erfolgreich und SessionId und Benutzername werden in einem Cookie gespeichert. Solange dieses Gültigkeit besitzt, muss keine erneute Anmeldung seitens des Benutzers stattfinden und der Chat kann uneingeschränkt genutzt werden. Im umgekehrten Falle, waren also Benutzername oder Passwort fehlerhaft, wurde keine SessionId erzeugt und der Benutzer wird erneut auf die Login-Seite verwiesen.
routeMatcher.post(„/login“, new Handler<HttpServerRequest>() {
@Override
public void handle(final HttpServerRequest req) {
req.dataHandler(new Handler<Buffer>() {@Override
public void handle(Buffer buff) {
QueryStringDecoder decoder = new QueryStringDecoder(buff.toString(), false);
Map<String, List<String>> parameters = decoder.getParameters();
final String username = parameters.get(„username“).get(0);
String password = parameters.get(„password“).get(0);
JsonObject user = new JsonObject();
user.putString(„username“, username);
user.putString(„password“, password);
vertx.eventBus().send(„vertx.basicauthmanager.login“, user, new Handler<Message<JsonObject>>() {@Override
public void handle(Message<JsonObject> event) {
String sessionId = event.body.getString(„sessionID“);
if (sessionId != null && !sessionId.equals(„“)) {
req.response.putHeader(„Set-Cookie“, „sessionId=“+sessionId+“&username=“+username);
req.response.sendFile(„web/chat.html“);
} else {
req.response.sendFile(„web/login.html“);
}
}
});
}
});
}
});
Nun wird ein AuthenticationHandler erstellt, von dem alle Handler erben, die auf geschützte Ressourcen verweisen. Zurzeit sind das die Handler für die Benutzer-Liste und den Chat, die statt direkt das Handler-Interface zu implementieren, nun die Klasse AuthenticationHandler erweitern. Die Funktionalität, die bislang direkt in der handle-Methode abgearbeitet wurde, wird in die internalHandle-Methode verschoben. Hierbei handelt es sich um eine abstrakte Methode, die alle Unterklassen zu implementieren haben. In der handle-Methode des AuthenticationHandler wird aus dem Header der Cookie ausgelesen und die Attribute sessionId und username überprüft. Sofern einer der Parameter nicht gesetzt ist, bzw. auf dem Client-System kein Cookie gefunden wurde, wird der Benutzer auf die Login-Seite verwiesen. Andernfalls wird die internalHandle-Methode aufgerufen und hier die gewünschte Funktionalität ausgeführt.
public abstract class AuthenticationHandler implements Handler<HttpServerRequest> {
@Override
public final void handle(HttpServerRequest req) {
String cookie = req.headers().get(„Cookie“);
if (cookie != null) {
QueryStringDecoder decoder = new QueryStringDecoder(cookie, false);
Map<String, List<String>> parameters = decoder.getParameters();
if (parameters.get(„sessionId“).size() > 0 && parameters.get(„username“).size() > 0) {
String sessionId = parameters.get(„sessionId“).get(0);
String username = parameters.get(„username“).get(0);
internalHandle(req, sessionId, username);
} else {
req.response.sendFile(„web/login.html“);
}
} else {
req.response.sendFile(„web/login.html“);
}
}protected abstract void internalHandle(HttpServerRequest req, String sessionId, String username);
}
In der internalHandle-Methode des Chat-Handlers wird – wie gehabt – auf die Chat-Seite verwiesen:
req.response.sendFile(„web/chat.html“);
Nun muss die Datei chat.html angepasst werden, so dass beim Chat-Protokoll auch der Name des jeweiligen Benutzers angezeigt wird, statt pauschal „unknown user“. Hierfür wird in der send-Funktion aus dem Cookie der Parameter username mit Hilfe eines regulären Ausdrucks erfragt. Die Websocket-Nachricht enthält dann sowohl den Benutzernamen als auch die Nachricht:
function send(message) {
if (!window.WebSocket) {
return;
}
if (socket.readyState == WebSocket.OPEN) {
var regex = „[\?&]username=([^&#]*)“;
var regexp = new RegExp(regex);
var results = regexp.exec(document.cookie);
var username = null;
if (results != null) {
username = results[1];
var msg = username + ‚:‘ + message;
socket.send(msg);
var msg = document.getElementById(‚message‘);
msg.value=“;
}
} else {
alert(„The socket is not open.“);
}
}
Zusätzlich zu Benutzern die mittels Browser am Chats teilnehmen, soll ein Bot auf bestimmte Keywords reagieren und entsprechende Antworten zum Chat beitragen. Dieser wird als Java-Client umgesetzt. Hierfür wird über eine Vertx-Instanz ein HttpClient erzeugt, über den eine WebSocket-Verbindung zum Server aufgebaut wird. Der asynchrone Verbindungs-Aufbau erfolgt über das HTTP-Protokoll, erst die darauf folgende Kommunikation verwendet das WS-Protokoll. In einem zu registrierenden Handler steht im Anschluss das WebSocket-Objekt zur Verfügung, über welches Chat-Nachrichten gesendet werden kann. Bei Text-Nachrichten wird dafür die writeTextFrame-Methode verwendet. Um ankommende Chat-Nachrichten verarbeiten zu können, wird ein DataHandler registriert. Hier wird überprüft, ob ankommende Nachrichten ein bestimmtes Keyword enthalten, auf welches der Bot reagieren soll.
Vertx vertx = Vertx.newVertx();
final HttpClient client = vertx.createHttpClient().setPort(8080);
client.connectWebsocket(„http://127.0.0.1“, new Handler<WebSocket>() {@Override
public void handle(final WebSocket websocket) {
websocket.dataHandler(new Handler<Buffer>() {
@Override
public void handle(Buffer buff) {
if (buff.toString().endsWith(„.question“)) {
websocket.writeTextFrame(„Bot:The answer of all questions is 42.“);
} else if (buff.toString().endsWith(„.weather“)) {
websocket.writeTextFrame(„Bot:Perhaps good, perhaps bad. Who knows.“);
}
}
});
}
});
Wird nun im Chat die Frage .question oder .weather gestellt, fühlt sich der Bot angesprochen und gibt eine geistreiche Antwort.
Hiermit ist das Chat-Programm auf Grundlage des Frameworks Vert.x vollständig. Der Chat wurde mittels WebSockets realisiert, wobei neben diversen HTML5 fähigen Browsern auch eine Java-Anwendung als Client fungiert. Zusätzlich wurde Einblick in die Verwendung von Fremd-Modulen gegeben, über welche die Persistierung von Benutzer-Daten in eine MongoDB und eine Authentifizierung realisiert wurde. Vert.x ist ein interessantes Framework, das sich den Anforderungen moderner Web-Anwendungen stellt. Ich denke, entscheidend für den Erfolg von Vert.x wird die Integration in bestehende Frameworks wie Spring sein. Nichts desto trotz ist Vert.x es wert, einen genaueren Blick zu riskieren.