Websockets, Node.js und APEX: Hintergrund-Nachrichten ohne Polling
Erscheinungsmonat |
APEX-Version |
Datenbankversion |
Februar 2015 |
alle |
ab 10.2 |
Heute stellen wir Ihnen vor, wie Sie die neue HTML5-Technologie Websockets in APEX Anwendungen
nutzen können. Websockets (Wikipedia) sind eine Erweiterung des HTTP-Protokolls und erlauben es, dass der Browser und der
Webserver die Netzwerkverbindung nach dem Ausliefern der HTML-Seite offen halten. Über diese
offen gehaltene Verbindung kann der Server später noch Daten an den Browser senden.
Diese Technologie ist unglaublich nützlich, wenn es darum geht, "Hintergrundbenachrichtigungen"
an den Client, also den Browser zu senden. Als Beispiel sei eine Statusnachricht genannt, die
im Browser stets aktuell gehalten werden soll - auch wenn der Anwender gerade nicht arbeitet.
Ohne Websockets muss der Browser regelmäßig beim Server anfragen, ob es Aktualisierungen
gibt (Polling) - wie man das in APEX umsetzen kann, ist im Community-Tipp "Berichte automatisch aktualisieren" beschrieben. Der Nachteil dieses Ansatzes ist, dass das Polling-Interval recht kurz gehalten
werden muss, wenn der Endanwender ein "Real Time" Gefühl haben soll. Das bewirkt andererseits
sehr viel Netzwerkverkehr und viel Last auf der (APEX)-Datenbank durch das ständige Ausführen
ein- und derselben Abfrage. Und solange es keine
Aktualisierungen gibt - für "nichts und wieder nichts".
Mit Websockets ist das alles viel einfacher: Die Netzwerkverbindung wird einmal instanziiert
und bleibt dann offen. Es findet keinerlei Polling statt. Nur wenn es eine Aktualisierung gibt,
werden alle Clients vom Server aus benachrichtigt. Die Last auf den Systemen wird um ein
Vielfaches reduziert.
Erforderlich dafür ist, dass sowohl Browser als auch Webserver das Websocket-Protokoll
unterstützen. Was die Browser angeht, so ist dies bei allen modernen Browsern, die HTML5
unterstützen, der Fall. Serverseitig kommt es nicht nur auf die Protokollunterstützung
an sich an, sondern auch darauf, dass der Server mit sehr vielen gleichzeitig offenen
Websocket-Verbindungen umgehen kann. Denn es muss ja davon ausgegangen werden, dass sehr
viele Client (= Browser-Fenster) geöffnet sein werden.
Eine Technologie, die sich auf der Serverseite anbietet, ist
Node.js
(Wikipedia).
Node.js bietet sich durch seine eventgetriebene, asynchrone Natur als Technologie
für einen Websocket-Server geradezu an. Seit Januar 2015 steht nun auch noch ein Node.js Treiber für die
Oracle-Datenbank zur Verfügung - der Nutzung von Websockets in APEX-Anwendungen
steht also nichts mehr im Wege.
Vorbereitungen - Tabelle
Das Beispiel soll eine stets aktuelle "Tagesnachricht" in einer APEX-Anwedung
darstellen. Diese Nachricht wird aus einer Tabelle gelesen und mit einer
APEX-Anwendung gepflegt. In anderen APEX-Anwendungen wird sie in einer Alert Region
am Seitenanfang dargestellt. Und mit Hilfe von Websockets werden wir sie stets aktuell halten. Auf Basis
dieses einfachen Beispiels können Sie dann auch komplexere Szenarien umsetzen - die Vorgehensweise
ist immer die gleiche. Zunächst braucht es aber die Tabelle.
Vorbereitungen - Anwendung zur Nachrichten-Pflege
Erzeugen Sie dann die APEX-Anwendung zur Pflege dieser Tabelle MESSAGES. Machen Sie
es sich einfach: Nehmen Sie eine Seite vom Typ Report and Form zu Ihrer Anwendung
dazu und nehmen Sie ansonsten die Defaults (Abbildung 1).
Abbildung 1: Einfache APEX-Standardanwendung zur Pflege der Nachrichten
Navigieren Sie nach Erstellung der Anwendung zur Formularseite und stellen
Sie dort die Elemente für die Spalten ID und DATETIME auf Hidden um und machen
Sie aus dem Textfeld für die Nachricht eine Textarea. Das Ergebnis sollte dann
so aussehen. Testen Sie das Formular, in dem Sie die erste Nachricht direkt erstellen: Dies ist die erste Nachricht.
Abbildung 2: Formular zur Pflege der Nachrichten
Der Bericht zeigt die vorhandenen Nachrichten an.
Abbildung 3: Bericht mit Übersicht über die vorhandenen Nachrichten
Vorbereitungen - Nutzung der Nachrichten
So weit, so gut. Nun geht es an die Anwendung, in der die Nachrichten dargestellt
werden soll. Nehmen Sie eine vorhandene oder erzeugen Sie sich eine neue mit
einer Seite und Regionen (was diese enthalten, ist erst einmal egal).
Fügen Sie dieser Anwendung dann eine Global Page hinzu (Seite 0). Auf diese Seite
legen Sie eine Region vom Typ HTML mit folgendem Quelltext.
Das verwendete Element P0_MESSAGE brauchen Sie natürlich auch. Legen Sie
es als Hidden-Element an und setzen Sie folgende Einstellungen im Bereich
Source.
- Source Used: Always, replacing any existing value in session state
- Source Type: SQL Query
- Source value:
select msg from (select msg from messages order by datetime desc) where rownum = 1
Abbildung 4: Einstellungen für das Element P0_MESSAGE
Damit wird das Element P0_MESSAGE immer die jüngste Nachricht enthalten. Es wird
in einer Region auf der (globalen) Seite 0 dargestellt, also auf jeder Anwendungsseite.
Eine solche sieht dann in etwa wie in Abbildung 5 aus.
Abbildung 5: Anwendungsseite mit Nachrichten-Region
Damit sind alle Vorbereitungen abgeschlossen. In der "klassischen" Welt
wären Sie jetzt fertig. Denn beim Aufbau wird stets die jüngste Nachricht
dargestellt. Wenn die Seite aktualisiert wird, wiederholt sich der Vorgang
und die dann jüngste Nachricht wird dargestellt. Mit Websockets werden wir
nun aber die Nachricht austauschen, ohne dass die Seite neu aufgebaut wird
- und ohne, dass der Browser ständig per AJAX beim Server anfragt.
Node.js auf dem APEX-Server installieren
Laden Sie Node.js
und den Oracle-Datenbanktreiber node-oracledb herunter
und installieren Sie diesen auf dem Knoten, auf dem bereits Ihr APEX-Webserver läuft.
Eine Anleitung auf Deutsch ist für Linux verfügbar; für
Windows und andere Plattformen gibt es einen englischen Installation Guide.
Wenn Sie die Installation abgeschlossen haben, können Sie mit diesem kleinen
Programm emp.js testen, ob Ihre Node.js-Umgebung und Ihre Datenbankverbindung
funktioniert.
Ihre Verzeichnisstruktur sollte wie in Abbildung 6 aussehen. Speichern Sie den
Code in eine Datei namens emp.js, passen Sie den
Datenbank-Connection-String und ggfs. Usernamen und Passwort an Ihre
Umgebung an und führen Sie das Skript mit dem Executable node aus (das Vorhandensein der Tabelle EMP im Datenbankschema wird vorausgesetzt).
Abbildung 6: Arbeitsverzeichnis in Ihrer Node.js Umgebung
Wenn Sie folgende Ausgabe sehen, funktioniert Ihre Node.js-Umgebung.
Node.js Pakete für Websockets installieren
Nun kann der Websockets-Server auf Basis von Node.js angegangen werden. Node.js
kennt Packages und einen Package Manager: npm - der wurde beim Installieren des
Oracle-Datenbanktreibers auch schon verwendet. Mit diesem können Funktionsbibliotheken
zur Node.js Umgebung hinzugefügt werden. Für den Websocket-Server werden zwei
Pakete benötigt: Express
und Websocket.
Installieren Sie diese wie folgt in Ihre node.js Umgebung (achten Sie darauf, dass Sie sich in
Ihrem Arbeitsverzeichnis befinden).
Wenn sich Ihre Node.js-Umgebung hinter einer Firewall befinden, müssen Sie den
Proxy-Server setzen, damit npm sich die Pakete aus dem Internet holen kann. Das
geht einmalig wie folgt.
Anschließend sollte die Verzeichnisstruktur wie in Abbildung 7 aussehen.
Abbildung 7: Arbeitsverzeichnis in Ihrer Node.js Umgebung mit Express und Websocket
Node.js Skript für den Websocket-Server erzeugen und starten
Nun wird es Zeit für das eigentliche Skript, welches den Websocket-Server implementiert.
Passen Sie im folgenden Skript die Zeilen 14 bis 18 an Ihre Datenbank an (verwenden Sie
das APEX-Parsing-Schema, welches die eingangs erzeugte Tabelle MESSAGES enthält) und
speichern Sie alles in einer Datei namens apex-websocket.js, die
(wie schon emp.js) in Ihrem Node.js Arbeitsverzeichnis liegen muss.
Starten Sie das Skript nun wiederum mit dem Executable node. Wenn Sie alles
richtig gemacht haben, sollten Sie folgende Meldungen sehen.
Das Node.js Skript hat nun zwei Webserver gestartet.
- Auf Port 1337 "lauscht" der Websocket-Server; im nächsten Schritt werden
wir der APEX-Anwendung Javascript-Code hinzufügen, der sich auf diesen
Server verbinden soll. Über diesen Port bekommen die Clients dann die
Websocket-Nachrichten zugesendet.
- Auf Port 9099 "lauscht" ein "Kontrollserver" - schließlich
muss das Node.js Skript irgendwie das Kommando bekommen, "jetzt" die Inhalte aus der Tabelle
MESSAGES zu lesen und an die Clients auszuliefern. Das geschieht mit einem einfachen
HTTP-Zugriff auf den "Kontrollserver". Ruft man die URL {hostname}:9099/update/ auf,
so wird der Websocket-Server aktiv. Im Moment gibt es noch keine verbundenen Clients, daher
passiert nicht viel.
Beide Ports können Sie natürlich ändern: Port 1337 für den Websocket-Server wird
in Zeile 29 festgelegt, die 9099 für den "Kontrollserver" findet sich in Zeile 86.
APEX-Anwendung zum Websocket-Client machen
Navigieren Sie nun wieder zur APEX-Anwendung, welche die Nachrichten anzeigen soll
(nicht die zur "Pflege"). Dort wurde die Nachrichten-Region ja auf der globalen
Seite 0 erzeugt. Navigieren Sie wiederum zur dieser Seite und erzeugen Sie eine
neue Dynamic Action.
- Nennen Sie die Dynamic Action Page Load: Init Websocket Client
- Die Dynamic Action soll On Page Load ausgeführt werden
- Als TRUE Action soll folgender Javascript-Code ausgeführt werden:
Die Dynamic Action sollte dann wie in Abbildung 8 aussehen.
Abbildung 8: Dynamic Action on "Page Load": Websocket Client initialisieren
Damit sind Sie schon fast fertig. Der Websocket-Server läuft - wenn Sie die
APEX-Seite starten, können Sie in der Javascript-Konsole die Nachricht sehen,
dass der Browser eine Verbindung zum Websocket-Server geöffnet hat.
Abbildung 9: Der Client hat eine Websocket-Verbindung geöffnet
Wenn der Websocket-Server
nun eine Nachricht sendet, wird die Nachricht innerhalb der Alert Region aktualisiert.
Probieren Sie das einmal aus - rufen Sie in einem zweiten Browserfenster die URL
des "Kontrollservers" auf: http://{hostname}:9099/update/. Sie sollten sehen,
dass die Nachricht in der Alert Region nun kurz "aufleuchtet". Die Nachricht
selbst ändert sich aber nicht - kein Wunder: Sie haben ja auch keine neue Nachricht gespeichert.
Abbildung 10: Der Client hat eine Websocket-Nachricht erhalten"
Websocket-Nachricht nach Erstellen einer Nachricht triggern
Nun muss nur noch sichergestellt werden, dass die URL des "Kontrollservers"
aufgerufen wird, wenn eine neue Nachricht eingestellt wurde. Hierfür gibt es
verschiedene Ansätze - für den Anfang nehmen wir hier den einfachsten: Navigieren Sie
dazu zu der APEX-Seite mit dem Formular zum Erstellen einer neuen Nachricht
(das ist die, die Sie eingangs so verändert haben, dass nur noch eine Textarea
zu sehen ist).
Abbildung 11: APEX-Seite zum Erstellen einer neuen Nachricht
Bei Klick auf die Schaltfläche CREATE wird derzeit nur eine Zeile in der
Tabelle MESSAGES erzeugt. Um das Versenden der Websocket-Nachricht zu triggern,
erzeugen Sie einen weiteren PL/SQL-Prozess, der nach dem schon vorhandenen
Prozess Process Row of MESSAGES ausgeführt werden soll. Versehen Sie ihn mit
dem folgenden PL/SQL Code.
Damit der HTTP-Request aus APEX heraus funktioniert, muss das Parsing Schema der
APEX-Anwendung in die PL/SQL Netzwerk-ACL eingetragen sein. Hier brauchen Sie ggfs.
die Hilfe des DBA. Mehr Informationen dazu finden Sie im Community Tipp
Netzwerkzugriffe mit Application Express und Oracle11g.
Stellen Sie dann noch sicher, dass dieser Prozess nur bei Klick auf
die Schaltfläche CREATE ausgeführt werden soll (beim Löschen einer alten
Nachricht ist keine Websocket-Benachrichtigung nötig). Im Seitenkontext sieht
der Prozess dann wie in Abbildung 12 aus.
Abbildung 12: Ein PL/SQL Prozess zum Triggern des Websocket-Servers wurde erstellt
Wer es etwas fortgeschrittener haben möchte, kann auch dafür sorgen, dass
die Datenbank diesen HTTP-Request vollautomatisch nach einer Änderung an der
Tabelle durchführt. Dazu lässt sich
Continuous Query Notification nutzen - die
Details würden den Rahmen dieses Community-Tipps sprengen; folgen Sie einfach dem
Blog-Posting zum Thema und der
Dokumentation.
Achtung: Normale Trigger sind der falsche Weg - denn diese feuern unmittelbar nach
dem Kommando und noch vor einem Commit oder Rollback. Würde also ein Rollback erfolgen, so würde eine Phantom-Nachricht gesendet. Continuous
Query Notification
wird dagegen erst nach dem Commit ausgelöst.
Ergebnis
Und nun sind Sie tatsächlich fertig. Zum testen öffnen Sie einige Browserfenster
parallel und ordnen Sie diese auf Ihrem Bildschirm an. In einem weiteren Browserfenster
öffnen Sie das Formular zum Erstellen einer neuen Nachricht. Tippen Sie schon mal
Das ist die zweite Nachricht ein.
Abbildung 13: Testumgebung: Viele Browser mit der Anwendung - einer zum Erstellen einer Nachricht
Nach Klick auf die Schlatfläche CREATE wird die neue Nachricht zunächst in die Tabelle
geschrieben, dann wird die URL des "Kontrollservers" aufgerufen. Der Node.js Websocket-Server
wird aktiv, liest die zuletzt gespeicherte Nachricht aus der Tabelle aus und sendet sie
an alle verbundenen Clients.
Abbildung 14: Testergebnis - Die neue Nachricht erreicht alle Browser"
Sie haben nun also - asynchron - die Seiten Ihrer Anwendungsnutzer aktualisiert,
ohne dass diese ständig nach neuen Nachrichten gefragt haben - es findet keinerlei
Polling statt. Das ist eine sehr ressourcenschonende und moderne Art, eine asynchrone
Hintergrundbenachrichtigung zu implementieren. Mit Hilfe von Node.js, dem Oracle-Treiber
node-oracledb und APEX steht Ihren Ideen nun nichts mehr im Wege ...
Dieser Community Tipp fokussiert sich allein auf die Funktionalität des Websocket-Beispiels;
Fehlerbehandlung und Instrumentierung des Codes fehlen weitgehend. Diese dennoch
nötigen Schritte bleiben Ihnen bei der konkreten Umsetzung überlassen. Viel Spaß mit
APEX und Websockets.
Zurück zur Community-Seite
|