JSF Ext Push ist Bestandteil von JSF Ext zur Übertragung von Änderungen aufgrund eines Server-Ereignisses.
In den meisten Fällen werden Daten aufgrund einer Browser-Anforderungen übertragen. In einigen Fällen soll eine Aktualisierung des Browser-Inhalts aufgrund eines anderen Ereignis erfolgen, das zunächst nur am Server vorliegt. Mit sogenannten Push können Informationen aktiv an den Browser übertragen werden. Vor allem ist es dadurch möglich, eine Vielzahl von Benutzern gleichzeitig über ein Ereignis zu informieren.
Der Tag <e:socket> nutzt Web-Sockets um Browser-Push durchzuführen. Dies ist eine sehr effiziente Implementierung mit geringer Verzögerung zwischen dem Versenden der Nachricht und dem Eintreffen im Browser, sowie Netzwerk und Ressourcen schonend. Dies ist möglich, wenn der Web-Server den Web-Standard 3.0 beherrscht (z.B. ab Tomcat 7). Sind Web-Sockets nicht verfügbar aufgrund eines Web-Servers kleiner als 3.0, weil der Browser dies nicht beherrscht oder ein dazwischen liegender Proxy es nicht unterstützt, wird die Comet-Engine verwendet, die Long-Polling durchführt.
Dazu ist folgende Dependency zum Projekt hinzuzufügen:
<dependency> <groupId>org.atmosphere</groupId> <artifactId>atmosphere-runtime</artifactId> <version>2.1.0-beta1</version> </dependency>
Inhalt#
Push-Tag#
Der Push-Tag ist die einfachste Art, AJAX-Push zu implementieren. Der Tag <e:push> stellt eine transparente Verbindung zu einem Push-Channel her. Der Inhalt des Tags wird auf alle Clients verteilt, die mit dem gleichen Channel verbunden sind:<h:form id="form"> <h:commandButton value="Senden" action="#{pushTest.action}"> <f:ajax render=":test:value"/> </h:commandButton> </h:form> <e:push id="test" direct="true"> <h:panelGrid id="panel" columns="2"> <h:outputText id="text" value="Test"/> <h:outputText id="value" value="#{pushTest.count}"/> </h:panelGrid> </e:push>
Erklärung: Der Tag ":test:value" wird durch eine einfache AJAX-Render-Anweisung auf allen verbundenen Clients neu gerendert.
Direct- und AJAX-Mode#
Der Push-Tag kann in zwei unterschiedlichen Modi betrieben werden: Direct und AJAX:Eigenschaft | Direct Mode | AJAX Mode |
---|---|---|
Rendering | Inhalt wird im Context des Clients gerendert, der den Event ausgelöst hat und das Resultat auf allen Clients verteilt | Inhalt wird für jeden Client unabhängig gerendert |
Effizienz | Effizienter Modus, da das HTML-Update direkt an alle Clients geschickt werden kann | Für jeden Client wird ein kompletter JSF-Lifecycle durchgeführt inklusive Rendering |
Inhalt | Inhalt ist für jeden Client identisch | Inhalt kann für jeden Client individualisiert werden |
Daten | Anzeige von Daten die für mindestens eine Gruppe bestimmt oder öffentlich ist | Anzeige privater Daten möglich |
Auslöser | Auslöser ist ein Faces-Event | Auslöser kann ein Faces-Event, Timer oder beliebiger anderer Event sein |
Rendering#
Die beim Render-Attribut angegebenen Ids werden einzeln gerendert und nur der relevante Inhalt zum Client übertragen. Auch wenn sich innerhalb des Push-Tag große Mengen an Tags befinden, wird nur die relevante Änderung übertragen, sofern die Render-Anweisungen entsprechend ausgelegt sind.Damit ist es beispielsweise möglich, eine Tabelle zu erzeugen und einzelne Zellen per Push upzudaten.
Channel#
Wird der Channel-Attribut nicht angegeben, wird die Client-Id der Component als Channel benutzt.Warnung: Channel-Name und Push-Tag sollte für alle Clients übereinstimmen, damit die Inhalte übereinstimmen. Andernfalls kommt es zu sogenannten Cross-Talking - der Inhalt des Push-Tags ändert sich.
Kombination mit Render-Tag#
Der Push-Tag arbeitet mit vielen anderen Tags zusammen. Eine interessante Kombination ist der Render-Tag, der in diesem Fall das Push auslöst:<e:push id="push-user-list" direct="true"> <h:form id="user-list-form"> <e:render event="intersult.test.User"/> <p:dataTable id="user-table" value="#{userList.users}" var="user"> <p:column headerText="Id"> <h:outputText value="#{user.id}"/> </p:column> <p:column headerText="Name"> <h:outputText value="#{user.name}"/> </p:column> </p:dataTable> </h:form> </e:push>
Erklärung: Dadurch dass sich der Tag <e:render> innerhalb des Tag <e:push> befindet, löst dieser den Push-Event bei allen verbundenen Client aus. Die enthaltene User-Liste <p:dataTable> wird bei allen verbundenen Clients (also Browsern) synchronisiert angezeigt. Sobald der Event "intersult.test.User" ausgelöst wird - egal ob von einem Client oder vom Server - wird die Tabelle neu gerendert.
Hinweis: für die praktische Anwendung könnte man den Zeilen der Tabelle eine Id in Abhängigkeit der User-Id geben und die entsprechende Zelle neu rendern, hinzufügen oder löschen. JSF Ext unterstützt auch das Hinzufügen und Löschen von Components durch AJAX.
Das Ganze kann dann so aussehen:
Socket-Tag#
Mit dem Socket-Tag ist eine fortgeschrittenere Integration von AJAX-Push in die Applikation möglich. Dazu wird einfach ein sogenannter Socket in die XHTML-Seite gesetzt:<e:socket channel="/test"> <f:ajax render=":push-form:chat"/> </e:socket>
Auf der Java-Seite wird der Socket dann so angesprochen:
PushContext.instance().push("/test");
Hinweis: In vielen Fällen reicht die reine Benachrichtigung des Client aus, weil dieser dann per AJAX-Call eine Server-Anfrage startet. Dies bietet den Vorteil, dass die Session und der FacesContext des entsprechenden Benutzers verfügbar ist.
Die Socket-Component unterstützt folgende Attribute und Behaviors:
Attribut | Beschreibung |
---|---|
channel | Das Attribut wird benötigt und beschreibt den Kommunikationskanal zum Server. Indem der Server diesen Channel informiert, wird im Browser eine Nachricht ausgeliefert. Die Besonderheit am Channel ist, dass dieser von beliebig vielen Browsern d.h. eingeloggten Benutzern abonniert werden kann. Der Server braucht daher nicht zu wissen, wieviele Clients überhaupt die Nachricht empfangen werden. |
onopen | Der Event wird beim Öffnen des Channels ausgelöst. Der Client weiß so immer, ob er verbunden ist oder nicht. Dadurch kann dem Nutzer auch ein Verbindungsstatus angezeigt werden. |
onclose | Das Gegenstück zu onopen, wird aufgerufen, wenn die Verbindung abbricht. Im normalen Produktivbetrieb wird diese Methode nur in Ausnahmefällen aufgerufen. |
onmessage | Dies ist der eigentliche Push-Aufruf. Wenn am Server PushContext.push() aufgerufen wird, wird am Client dieses JavaScript-Behavior ausgelöst. |
onerror | Hier kann eine Fehlerbehandlung hinzugefügt werden, zum Beispiel dem Benutzer angezeigt werden, wenn ein Problem mit der Verbindung auftaucht. |
Push-Context#
Auf der Server-Seite gibt es die Klasse com.intersult.jsf.push.PushContext. Um eine Nachricht zu einem Tag <e:socket channel="/test"> zu schicken, wird hier ein entsprechender Methodenaufruf durchgeführt:PushContext.instance().push("/test");
Push-Parameter#
Optional kann der Aufruf auch mit einem Parameter erfolgen, der wird dann JSON-Serialisiert und an den Client übertragen:PushContext.instance().push("/test", parameter);
Auf der Client-Seite kann der Parameter dann wie folgt entgegen genommen werden:
<e:socket channel="/test" onmessage="alert(response.responseBody);">
Load Balancer und Reverse Proxy#
Wird ein Load-Balancer verwendet, sind die Web-Sockets zu beachten. Bei Apache werden folgende Proxy-Einträge vor der eigentlichen Applikation gebraucht:ProxyPass /<context-path>/faces/javax.faces.resource/<channel> ws://<ziel>/<context-path>/faces/javax.faces.resource/<channel> ProxyPassReverse /<context-path>/faces/javax.faces.resource/<channel> ws://<ziel>/<context-path>/faces/javax.faces.resource/<channel>
Erklärung: Die Web-Socket-Protokolle WS werden vor den eigentlichen Proxy-Einträgen für die Application abgefangen und mit dem WS-Protokoll umgeleitet. In Apache 2.4 steht dafür das Modul mod_proxy_wstunnel.so zur Verfügung.
Push Status#
Der mitgelieferte Tag <e:push-status> kann den Verbindungsstatus einer Push-Connection sichtbar machen:<ext:push-status channel="/push-user-list"/>
Der Status wird dann als rot/gründ "LED" angezeigt.