Versiones comparadas

Clave

  • Se ha añadido esta línea.
  • Se ha eliminado esta línea.
  • El formato se ha cambiado.

...

Primero hay que activar los websockets mediante el siguiente parámetro de contexto booleano en web.xml:

Bloque de código
languagexml
<context-param>
    <param-name>es.um.atica.fundeweb.SOCKET_ENDPOINT_ENABLED</param-name>
    <param-value>true</param-value>
</context-param>

...

Declare la etiqueta <fw:socket> en la vista (página) con al menos un nombre de canal (channel) y una función JavaScript llamada onmessage. El nombre del canal no puede ser una expresión EL y solo puede contener caracteres alfanuméricos, guiones, guiones bajos y puntos. Aquí hay un ejemplo que hace referencia a una función de escucha de JavaScript existente (¡no incluya los paréntesis!).

Bloque de código
languagexml
<fw:socket channel="someChannel" onmessage="socketListener" />
Bloque de código
languagejs
function socketListener(message, channel, event) {
    console.log(message);
}

También se puede añadir la función JavaScript en el propio componente:

Bloque de código
languagexml
<fw:socket channel="someChannel" onmessage="function(message) { console.log(message); }" />

...

En caso de que su servidor esté configurado para ejecutar el contenedor WS en un puerto TCP diferente al contenedor HTTP, puede usar el atributo de puerto opcional para especificar explícitamente el puerto.

Bloque de código
languagexml
<fw:socket port="8000" ... />

...

En el backend, se puede inyectar una instancia de PushContext a través de la anotación @Push, junto con el nombre del canal en cualquier componente Seam, donde se necesite enviar un mensaje push. Para enviar el mensaje, se invoca al método PushContext.send(Object) con cualquier objeto Java que represente el mensaje.

Bloque de código
languagejava
@Push
private PushContext someChannel;

public void sendMessage(Object message) {
    someChannel.send(message);
}

...

Por defecto, el nombre del canal se toma del nombre de la variable en la que se realiza la inyección. También se puede especificar opcionalmente mediante el atributo channel de la anotación @Push. El siguiente ejemplo se inyecta el contexto push del canal foo en la variable bar.

Bloque de código
languagejava
@Push(channel="foo")
private PushContext bar;

...

El atributo opcional scope puede establecerse con el valor session, para restringir los mensajes push a todas las vistas de la sesión actual del usuario. El mensaje solo puede ser enviado por el usuario y no por la aplicación. Está característica es útil para recibir notificaciones a nivel de sesión sobre vistas lanzadas por el mismo usuario, ejemplo: el resultado de una tarea asíncrona lanzada por una acción del usuario).

Bloque de código
languagexml
<fw:socket channel="someChannel" scope="session" ... />

...

El atributo opcional scope puede establecerse con el valor session, para restringir los mensajes push sólo a la vista actual. Los mensajee push no son vistos por otras vistas en la misma sesión, incluso si son la misma URL. El mensaje solo puede ser enviado por el usuario y no por la aplicación. Está característica es útil para recibir notificaciones a nivel de vista (página) lanzadas por el mismo usuario, ejemplo: una barra de progreso sobre una acción especifica en la vista (página) actual).

Bloque de código
languagexml
<fw:socket channel="someChannel" scope="view" ... />

...

Además, el atributo opcional user puede establecerse con un identificador único, que hace referencia al usuario autenticado. Pude ser el nombre del usuario o un ID calculado. De esta forma, un mensaje push puede ser enviado a un usuario especifico y puede ser enviado por cualquier usuario o por la propia aplicación.  El valor el atributo user tiene que implementar la interface Serializable y debe gastar poca memoria, por lo que la entidad que representa al usuario como la clase org.umu.atica.servicios.gesper.gente.entity.Persona o org.jboss.seam.security.Identity (y descendientes) no son recomendables. Podemos generar identificadores únicos mediante la clase java.util.UUID. Ejemplo de uso con el username:

Bloque de código
languagexml
<fw:socket channel="someChannel" user="#{credentials.username}" ... />

...

Cuando el atributo user es establecido mediante una expresión EL, y este cambia durante un petición AJAX, entonces el usuario del socket también se actualizará, incluso si ninguna petición AJAX cubre la definición del <fw:socket>. Hay  que asegurarse de que el valor esta ligado al menos a una propiedad de la vista en caso de querer controlarlo durante el ámbito de la vista.

En el lado del servidor, el mensaje push puede ser dirigido al usuario especificado en el atributo user mediante PushContext.send(Object, Serializable). El mensaje push puede ser enviado por todos los usuarios o por la propia aplicación. Es útil para notificar a un usuario determinado por otros usuarios, por ejemplo: chat, mensajes por parte del administrador, etc.; o por tareas en segundo plano de la aplicación como por ejemplo: notificaciones, listeners, etc..

Bloque de código
languagejava
@Push
private PushContext someChannel;

public void sendMessage(Object message, User recipientUser) {
    Long recipientUserId = recipientUser.getId();
    someChannel.send(message, recipientUserId);
}


Los mensajes push pueden enviarse a varios usuarios al mismo tiempo, pasando una Collection que contenga los identificadores de los usuarios a PushContext.send(Object, Collection).

Bloque de código
languagejava
@Push
private PushContext someChannel;

public void sendMessage(Object message, Group recipientGroup) {
    Collection<Long> recipientUserIds = recipientGroup.getUserIds();
    someChannel.send(message, recipientUserIds);
}

...

Se pueden declarar varios canales push con diferentes ámbitos con o sin especificar usuarios de la aplicación. Incluso el mismo nombre de canal se puede reutilizar entre varias vistas, incluso si su ámbito es de vista. Es más eficiente utilizar el menor número de nombres de canal y ligar el nombre del canal de la conexión push, a combinaciones especificas de ámbito/usuario y no para vistas Faces especificas. En caso de tener varios canales de ámbito vista para diferentes propósitos, lo mejor es tener un único canal de ámbito vista y tener una listener Javascript global que pueda distinguir los tareas de los diferentes mensajes enviados. Por ejemplo, enviando el mensaje al servidor como se muestra a continuación:

Bloque de código
languagejava
Map<String, Object> message = new HashMap<>();
message.put("functionName", "someFunction");
message.put("functionData", functionData); // Can be Map or Bean.
someChannel.send(message);

que es procesado por el listener Javascript onmessage como se indica a continuación:

Bloque de código
languagejs
function someSocketListener(message) {
    window[message.functionName](message.functionData);
}

function someFunction(data) {
    // ...
}

function otherFunction(data) {
    // ...
}

// ...

...

Se puede usar el atributo opcional connected para controlar cuando se auto-conecta o no, la conexión web. Acepta valores booleanos que pueden obtenerse mediante expresiones EL

Bloque de código
languagexml
<fw:socket channel="someChannel" ... connected="#{bean.pushable}" />

...

Se puede establecer el valor del atributo connected directamente a false y controlar manualmente la apertura de la conexión push en la parte cliente invocando con Javascript a FundeWeb.Push.open(channel), pasando el nombre del canal como parámetro. Por ejemplo, cuando se lance el listener onclick de un botón que inicia una ejecución de una tarea asíncrona de larga duración en la parte servidor. Es realmente útil, para conexiones de ámbito vista, que no necesitan realizar una carga en página de manera inmediata.

Bloque de código
languagexml
<p:commandButton ... onclick="OmniFaces.Push.open('foo')">
    <p:ajax ... />
</p:commandButton>
<fw:socket channel="foo" scope="view" ... connected="false" />

...

En el caso de querer tener una comunicación push de un solo mensaje, normalmente porque se quiere presentar el resultado de una tarea asíncrona lanzada desde la vista, como es el caso del ejemplo anterior, se puede cerrar la conexión push invocando en el cliente  FundeWeb.Push.open(channel), pasando el nombre del canal como parámetro. Por ejemplo, podemos usar el listener Javascript onmessage para relizar la acción:

Bloque de código
languagejs
function someSocketListener(message, channel) {
    // ...
    OmniFaces.Push.close(channel);
}

...

El listener opcional de Javascript onopen, se usa cuando se abre el websocket en el cliente. Se lanza en el primer intento de conexión, independientemente de que la conexión se pueda realizar o no. No se invoca, en las auto-reconexiones que se hagan posteriormente. Acepta como único parámetro channel, el nombre del canal.

Bloque de código
languagexml
<fw:socket channel="foo" scope="view" ... connected="false" onopen="socketOpenListener" />
Bloque de código
languagejs
function socketOpenListener(channel) {
    // ...
}

...

El listener opcional de Javascript onerror, se usa para detectar los errores de conexión antes de un nuevo reintento de conexión. Se invocará cuando el socket web pueda realizar un intento de reconexión automática en una conexión interrumpida después de la primera conexión correcta. No se invocará cuando falle el primer intento de conexión, o el servidor haya devuelto el código de motivo de cierre 1000 (cierre normal) o 1008 (política violada), o se haya excedido el número máximo de intentos de reconexión. En su lugar, se invocará listener Javascript onclose.

Bloque de código
languagexml
<fw:socket channel="foo" scope="view" ... connected="false" onerror="socketErrorListener" />
Bloque de código
languagejs
function socketErrorListener(code, channel, event) {
    if (code == 1001) {
        // Server has returned an unexpected response code. E.g. 503, because it's shutting down.
    } else if (code == 1006) {
        // Server is not reachable anymore. I.e. it's not anymore listening on TCP/IP requests.
    } else {
        // Any other reason which is usually not -1, 1000 or 1008, as the onclose will be invoked instead.
    }

    // In any case, the web socket will attempt to reconnect. This function will be invoked again.
    // Once the web socket gives up reconnecting, the onclose will finally be invoked.
}

...