Cuando a principio de los 90 el contenido de la web era principalmente estatico, se decidio mejorar la interaccion entre el usuario y el contenido mostrado. Para ello, la solucion pasaba por JavaScript en el navegador, los primeros navegadores ofrecian distintas implementaciones y JavaScript era lento podia provocar ataques a la seguridad. A lo largo de los años se ha mejorado con diferentes frameworks (Dojo, jQuery, node.js, Angular), que nos permiten crear aplicaciones ricas en efectos, interaccion, compatibles entre navegadores, usando poco codigo. El mayor hito reciente, llego con la aparicion de Ajax.
Ajax, esta asociado a la comunicación asincrona en la que el servidor remoto utiliza tecnologia Javascript (Json, Html, Xml, Javascript). Con AJAX los navegadores, pueden comunicar con el servidor si que tenga que actualizarse la pagina entera. La comunicación es transparente para el usuario y se usa para enviar datos de formularios, recibir datos del servidor. Esta forma de comunicación Cliente/Servidor, trae sin embargo sus problemas. El navegador, recibe los datos que se mostraran (despues de validar, calcular, transformar), desde el servidor y sin embargo el cliente no gestiona los datos que se actualizan (depende de la response del servidor ). De la misma forma que solo el navegador sabe cuando tiene nuevos datos para enviar.
Pongamos el ejemplo de un usuario A y otro B usan una aplicación de chat, solo el servidor conoce los datos de A y de B y controla que enviar a cada uno. Incluso con AJAX el problema de sincronizar y enviar la informacion era dificil de resolver (a nivel de rendimiento, trafico de datos, sincronizacion).
Entre las distintas soluciones de los ultimos 14 años algunas dependian demasiado de la implementacion del navegador.
Vamos a intentar comprender algunas de ellas.
Es quiza una de las tecnicas que mas se utilizan, consultar en intervalos si el servidor tiene nuevos datos listos para recibir. El concepto es sencillo, a intervalos regulares (supongamos 1 por segundo), el navegador envia una peticion Ajax (con datos en la request o vacio) al servidor. El servidor responde con nuevos datos o vacio (lo que puede ser un objeto JSON vacio, o un Content-Lenght header a 0). Como vemos hay una gran cantidad de request-response en las que muchas de ellas se usan para controlar el estado si contener nuevos datos.
Browser GET /ajaxEndpoint ~100 miliseconds Server 200 OK Content-Length: 156 Browser GET /ajaxEndpoint ~100 miliseconds Server 200 OK Content-Length: 0 Browser GET /ajaxEndpoint ~100 miliseconds Server 200 OK Content-Length: 0 Browser GET /ajaxEndpoint ~100 miliseconds Server 200 OK Content-Length: 241
Es similar a Polling por intervalos a diferencia de que el servidor no envia la respuesta hasta que contiene datos a enviar. Lo cual evita sobrecarga de recursos, sin embargo presenta los siguientes problemas. Supon que el navegador tiene datos actualizados a enviar y esta esperando que el servidor responda. El navegador, deberia poder realizar una nueva peticion con esos datos o poder cancelar (de manera que el servidor la descarte) y lanzar una nueva. El tiempo de duracion de la conexión viene especificada en cada uno de los protocolos TCP y HTTP, el cliente y el servidor deben cerrar y reconectar. Mantener la conexión durante un largo periodo de tiempo representa un problema. Existe un tope en el numero de conexiones entre C y S especificada por el protocolo HTTP/1.1. Los navegadores no pueden mantener mas de dos conexiones simultaneas a un mismo servidor (ver). Si de las conexiones al servidor, una se encuentra en espera de nuevos datos, nos reduce a la mitad el rendimiento para cargar paginas, graficos y el resto de recursos del servidor. Si tener en cuenta estos aspectos, esta aproximacion gano popularidad en el pasado, a menudo denominada Comet.
Browser GET /longPollEndpoint 200 miliseconds Server 200 OK Content-Length: 120 Browser GET /longPollEndpoint 95 seconds Server 200 OK Content-Length: 96 Browser GET /longPollEndpoint 15 seconds Server 200 OK Content-Length: 204 Browser GET /longPollEndpoint 47 seconds Server 200 OK Content-Length: 191
Es semejante a long polling, aprovecha la funcionalidad que ofrece el protocolo HTTP/1.1 que permite al servidor responder sin anunciar una cabecera con el atributo content length. En lugar de la cabecera Content-Length: n, una respuesta puede contener la cabecera Transfer-Encoding: chunked. Esto indica al cliente que la respuesta se enviara por chunks. Dentro de la respuesta cada chunk empieza con un numero, que indica su longitud, mas una serie de caracteres opcionales que indican el tipo de chunk del que se trata, a continuacion con el caracer de fin de linea CRLF. Lo que sigue son los datos del chunk enviado y terminado en CRLF. Se pueden enviar en teoria cualquier numero de chunks y pueden ir espaciados en una cantidad de tiempo cualquiera. Cuando se envia un chunk de longitud cero (un 0 seguido de CRLF), esto indica que es el final de la secuencia de la respuesta. La manera mas corriente de usar chunked encoding suele ser cuando establecemos una conexión al comienzo con la intencion de recibir eventos desde el servidor. Cada chunk del servidor es un nuevo evento que lanza el manejador de evento del “onreadystatechanged” del objeto XMLHttpRequest. Algunas veces, sin embargo no tan a menudo como sucede en long polling, la conexión tiene que actualizarse. Cuando el cliente tiene que enviar nuevos datos al servidor, lo hace con una segunda request de corta duracion. Vemos el protocolo representado en la figura a continuacion. En la parte izquierda, vemos el cliente enviando datos hacia el “endpoint de subida” cuando es necesario, usando para ello pequeñas requests. En la parte derecha, el cliente establece una sola conexión larga con el “endpoint de bajada” y el servidor usa dicha conexión para enviar al cliente datos actualizados en “chunks”. Chunked enconding resuelve la mayoria de los problemas de timeouts que aparecen en la tecnica de long polling (los clientes aceptan respuestas que toman un tiempo grande para completarse mucho mejor que que las respuestas que necesitan de un largo tiempo para empezar – la descarga de grandes ficheros es un buen ejemplo), sin embargo, la limitacion del cliente a 2 conexiones por host conectado sigue presente. Añadir ademas que con long polling y con chuked encoding, los navegadores mas antiguos solian mostrar informacion de status indicando que la pagina se encontraba cargando – sin embargo los navegadores modernos han eliminado este comportamiento.
Los intentos anteriores intentaban la manera de emular una conexión full-duplex entre navegador y servidor sobre una conexión HTTP, lo cual no puede darse mediante Ajax y su objeto XMLHttpRequest. De forma extendida, aunque no por mucho tiempo, fue (es), el uso de Applets o objetos Flash embebidos en la pagina. Con este plug-in se pueden establecer conexiones TCP mediante sockets al servidor. Lo que elimina todas las limitaciones que presenta el protocolo HTTP. Así, mientras que el servidor envia mensajes al navegador, el Applet u objeto Flash puede llamar a una funcion JavaScript con los datos de dicho mensaje. Cuando el navegador tiene nuevos datos que enviar al servidor, puede llamar un metodo Java o ActionScript (Flash), mediante una funcion Javascript que el plug-in del navegador entiende, y dicho metodo enviara la informacion al servidor. Este protocolo consigue (uso de sockets TCP), una conexión full-duplex y elimina problemas tales como timeouts y numero maximo de conexiones (incluso saltaba las restricciones en seguridad que fija el navegador para las conexiones AJAX, que deben originarse desde un elemento que se encuentra dentro del mismo nombre de dominio, “problema de crosscripting”). El precio, es sin embargo el tener que instalar un plug-in (Java o Flash), que pueden tener algun agujero en la seguridad, cargan la memoria, consumo de CPU. Aun cuando esta tecnologia se mantiene, el auge de uso del movil, donde un navegador ejecutando contenido embebido resulta extremadamente lento, hizo que se buscasen nuevas maneras de comunicación entre cliente y servidor. Requisitos como conexiones TCP, seguras, rapidas, ligeras (no necesidad de plug-ins).
La especificación HTTP/1.1. contiene los requisitos que han de cumplir las comunicaciones basadas en HTTP. Dentro de la seccion 14.42 se incluye un apartado llamado HTTP Upgrade.
HTTP/1.1 Upgrade
Un cliente HTTP (no necesariamente un navegador) puede incluir la cabecera Connection: Upgrade dentro de una request. De esta manera se indica que es lo que el cliente solicita actualizar, dicho atributo Upgrade debe indicar una lista de uno o mas protocolos, los cuales han de ser incompatibles con HTTP/1.1, como por ejemplo IRelayChat. El servidor, si acepta la peticion de Upgrade, debe devolver el codigo 101 Switching Protocols junto a cabecera response upgrade con un solo valor: el primero de los protocolos soportados de la lista que se envio en la request. Al principio, esta funcionalidad se usaba para actualizar de HTTP a HTTPS, pero resulto ser una puerta para ataques a la seguridad (man in the middle) ya que no se aseguraba la conexión desde el principio. Por esta razon, la tecnica fue reemplazada por el esquema de URI (https://), y la negociacion con Connection:Upgrade, fue quedando fuera de uso. La caracteristica principal de esta especificacion es que es posible indicar HTTP Upgrade con cualquier tipo de protocolo. Una vez que se ha establecido el acuerdo entre el cliente HTTP Upgrade y el servidor, el protocolo puede ser incluso una conexión persistente entre sockets TCP. En teoria, seria posible establecer cualquier tipo de conexión TCP entre dos endpoints con un protocolo propio. Sin embargo, los fabricantes de navegadores, no estan por la labor de dejar a los desarrolladores JavaScript, perderse en la pila TCP, asi que era necesario llegar a un acuerdo en cuanto a que parte era accesible desde JavaScript, de ahí el protocolo WebSockets.
Una nota al respecto: Si existe algun recurso de un servidor cualquiera que solo acepta peticiones HTTP Upgrade y un cliente conecta a dicho recurso sin haber enviado dicha peticion el servidor puede enviar la respuesta “426 Upgrade Required” para indicar que Upgrade es obligatorio. En este caso, la respuesta puede incluir la cabecera Connection: Upgrade que contiene la lista de protocolos que el servidor soporta. Si un cliente envia en su request Upgrade un protocolo no soportado por el servidor, el servidor puede responder con 400 Bad Request e incluir el header Upgrade con la lista soportada. Finalmente, si el servidor no acepta peticiones Upgrade, envia la respuesta 400 Bad Request
Una conexión de WebSocket, esquematizada, comienza con una peticion HTTP hacia un esquema particular. El esquema URI “ws” y “wss” se corresponden con los esquemas HTTP “http” y “https” respectivamente. La cabecera Connection:Upgrade y Upgrade:websocket se envian en la peticion indicando al servidor que se espera actualizar la conexión al protocolo WebSocket, un tipo de conexión persistente y full-duplex que se especifica en el RFC 6455 desde el año 2011. Una vez que se han realizado el handshake, se pueden realizar envios de texto y binarios en ambas direcciones, simultaneamente, sin tener que cerrar y reabrir la conexión. Llegado a este punto, no hay diferencia entre el cliente y el servidor – Ambos tienen caracteristicas de comunicación semejantes sobre la conexión establecida – semejante a conexión p2p.
Nota : Los esquemas URI “ws” y “wss”, no son exactamente parte del protocolo HTTP. Ya que la peticion HTTP no envia esquemas URI enteros sino URLs relativas al servidor al que conecta. Los esquemas especificos WebSocket se incluyen principalmente para indicar al navegador y sus implementaciones de si se intenta conectar mediante SSL/TLS o sin encriptacion.
Existen numerosas ventajas en la forma en que el protocolo WebSockets:
Las conexiones se establecen sobre los mismos puertos que en HTTP 80 ( ws ) o 443 ( wss ), no se bloquean por firewalls. El protocolo se adapta facilmente a navegadores y servidores HTTP ya que el hadshake se realiza sobre HTTP Los mensajes de Heartbeat pings (cliente) y pongs (servidor) mantienen la conexion viva casi de manera infinita. Servidor y cliente cuando empieza un mensaje y cuando se envia todo su contenido Al cerrarse una conexion (onclose) de WebSocket se envia un mensaje especial con el codigo y texto indicando la razon Una conexion WebSocket puede manejar de forma segura las conexiones cross-domain lo que elimina las restricciones que tenia Ajax Las restricciones de numero de conexion se eliminan ya que tras el handshake dicha conexion deja de ser de tipo HTTP
Las cabeceras de handshake en una peticion de conexión bajo WebSockets es simple. Veamos una conexión tipica de upgrade filtrada por Wireshark:
GET /webSocketEndpoint HTTP/1.1 Host: www.example.org Connection: Upgrade Upgrade: websocket Origin: http://example.com Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw== Sec-WebSocket-Version: 13 Sec-WebSocket-Protocol: game
Una vez indicado el preludio HTTP( GET /webSocketEndpoint HTTP/1.1 ) vemos la cabecera de Host. Ademas las cabeceras de Connection y Upgrade que se explicaron. A continuación la cabecera Origin es un mecanismo de seguridad indicando el nombre del host origen hacia el que se inicio la conexión, para impedir que se realicen peticiones “crossdomain”. El navegador establece este valor y el servidor verifica que dicho valor se encuentra dentro de los nombres de dominio aceptados.
La cabecera “Sec-WebSocket-Key” es un valor de verificacion dentro de la especificacion, de manera que el navegador genera una clave aleatoria en base64 encoding y la añade a la request. El servidor anexa a dicho valor de la request, otra clave, se cifra con SHA-1, de manera que se devuelve el valor cifrado en base64 en la cabecera de la response dentro del atributo Sec-WebSocket-Accept. Sec-WebSocket-Version indica la version del protocolo que el cliente soporta y Sec-WebSocket-Protocol es una cabecera opcional para indicar cual es el protocolo que encapsula el protocolo WebSocket.
La respuesta a la peticion previa podria ser esta:
HTTP/1.1 101 Switching Protocols Server: Apache 2.4 Connection: Upgrade Upgrade: websocket Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk= Sec-WebSocket-Protocol: game
Una vez alcanzado este punto, la conexión HTTP se sustituye por una conexión basada en WebSocket, que usa la misma conexión subyacente TCP. El mayor problema que tiene este tipo de conexión es el pasar a traves de HTTP proxies, los cuales historicamente no soportan las peticiones con cabeceras HTTP Upgrade muy bien. Cuando el navegador detecta que va a pasar por un Proxy envian la cabecera HTTP CONNECT antes, pero este truco no funciona siempre. Asi que la forma mas fiable de poder conectar mediante WebSockets es usando SSL/TLS ( wss ) ya que los proxies suelen permitir dichas conexiones realizar su tarea, con lo que con esta estrategia las conexiones WebSocket funcionan casi siempre y son ademas seguras ya que el trafico va cifrado en ambos sentidos como lo hace bajo HTTPS.
Existen numerosas APIs que encapsulan la mayoria de tareas complicadas del protocolo. La unica advertencia es de que al tratarse de una tecnologia bastante nueva, necesitamos versiones acutalizadas de navegador, para soportar las aplicaciones basadas en WebSockets.
El protocolo WebSocket se usa en multitud de aplicaciones, entre las que se incluyen aplicaciones web. Algunas de ellas existen ya fuera de un navegador, veamos algunos ejemplos.
Un Chat basado en JavaScript Juego Online multijugado (un MMORPG de Mozilla, llamadoBrowserQuest esta escrito en HTML 5 y JavaScript usa WebSockets.) Una tira informativa de valores de bolsa en tiempo real Una tira informativa de noticias en tiempo real Streaming de video en HD (!!) Comunicar nodos (compartir un contexto) en un cluster de aplicación. Envio transactional de gran cantidad de datos a traves de la red. Monitorizacion de recursos o rendimiento de software de forma remota.
Es importante tener en cuenta que el uso de WebSockets no se basa unicamente en comunicar el navegador con un servidor. Dos aplicaciones desarrolladas en cualquier framework que soporta WebSockets pueden, en teoria, conectar entre ellas. Por lo tanto, la mayoria de las implementaciones de WebSocket soportan tanto endpoints cliente y servidor. Por ejemplo, en Java y .NET. Sin embargo, en JavaScript, esta enfocado a utilizarse como endpoint cliente de una conexión WebSocket. Veremos por lo tanto como trabajar con un endpoint cliente en JavaScript, para a continuacion, ver el funcionamiento de un endpoint cliente en Java y para terminar un endpoint servidor Java.
Nota En el libro hablamos de las capacidades Js, hacemos referencia al soportado por el navegador. Ya que algunos frameworks JavaScript como Node.js, pueden ejecutarse fuera del contexto del navegador y aportar caracteristicas adicionales (incluso un servidor basado en WebSockets).
API cliente HTML5 (JavaScript)
al como se indico previamente, todo navegador moderno soporta el API WebSocket que es estandar en aquellos navegadores que la sopotan. W3C establece la interfaz WebSocket como una extension de la especificacion de HTML5.
/*Crear un Objeto WebSocket */ var connection = new WebSocket('ws://www.servidor1.net/stocks/stream'); var connection = new WebSocket('wss://sec.servidor1.net/games/chess'); var connection = new WebSocket('ws://www.servidor2.com/chat', 'chat'); var connection = new WebSocket('ws://www.servidor2.com/chat', {'chat.v1','chat.v2'}); /**/
El primer parametro del constructor es la URL (required), del servidor WebSocket al que deseamos conectar. El segundo parametro (optional), puede ser una cadena o un array de cadenas que indican el o los protocolos propuestos por el cliente que se aceptarian. Recordemos que estos protocolos son nuestros, no los que implementa la tecnologia WebSocket. Esto nos permite enviar información propia que deseamos manejar en las cabeceras de la conexión.
Manejar un Objeto WebSocket
Existen varias propiedades en la interfaz WebSocket. La primera, readyState, indica es estado acutal de la conexión ws. Su valor es siempre CONNECTING (0), OPEN (1), CLOSING(2), CLOSED(3).
/**/ if(connection.readyState == WebSocket.OPEN) { /* do something */ } /* */
Principalmente se usa de forma interna; sin embargo, a veces es util para asegurarnos de que no se intenta enviar cuando la conexión no esta aun abierta. A diferencia del objeto XMLHttpRequest, WebSocket no contiene un evento “onreadystatechange” que se lance cada vez que el objeto cambia, lo que nos fuerza a comprobar “readyState” para verificar el estado de una accion. En su lugar, WebSocket tiene cuatro eventos que representan cuatro cambios que pueden suceder en un WebSocket:
/**/ connection.onopen = function(event) { } connection.onclose = function(event) { } connection.onerror = function(event) { } connection.onmessage = function(event) { } /**/
Los nombres indican claramente cuando se lanza dicho evento. Es importante, el evento “onclose” que se lanza cuando la propiedad “readyState” cambia de CLOSING a CLOSED. Cuando termina el handshake y se llama a “onopen” ( “readyState” cambia de CONNECTING a OPEN), la url de solo lectura, las extensiones enviadas desde el servidor y el protocolo que ha seleccionado dicho servidor, se asignan al objeto WebSocket. El objeto de tipo evento que se asigna a “onopen” es un objeto Event de JavaScript con ninguna propiedad adicional. El objeto Event que se asigna a “onclose”, contiene sin embargo tres propiedades interesantes: 1-wasClean , 2- code , y 3-reason. Podemos comprobar estos valores para informar de comportamientos impropios al usuario:
/**/ connection.onclose = function(event) { if(!event.wasClean) alert(event.code + ': ' + event.reason); } /**/
Los codigos de cada atributo se especifican en el RFC 6455 Seccion 7.4 ( http://tools.ietf.org/html/ rfc6455#section-7.4 ). El valor 1000 indica comportamiento normal el resto indica alguna anomalia. El evento “onerror” contiene el objeto error que puede ser de cualquier tipo (normalmente es una cadena). Este evento se lanza en errores del lado del cliente; los errores de protocolo terminan por un cierre en la conexión. El manejador de evento “onmessage” es de los eventos que debemos manejar mas cuidadosamente. Su evento contiene ademas una propiedad “data”. Se trata de un string si el mensaje es texto, un Blob si el mensaje es binario y por defecto la propiedad “binaryType” de WebSocket es “blob”, o un ArrayBuffer si el mensaje es de tipo binary y la propiedad “binaryType” es “arraybuffer”. Es recomendable asignar el valor de la propiedad “binaryType” de un WebSocket inmediatamente despues de instanciarlo y dejar este valor hasta el final de la conexión; aunque es posible cambiarlo (rw) cuando fuese necesario.
/**/ var connection = new WebSocket('ws://www.example.net/chat'); connection.binaryType = 'arraybuffer'; ... /**/
El objeto WebSocket contiene dos metodos: “send” and “close” . Close() acepta un argumento opcional como argumento (que es por defecto 1000) y un string indicando la razon (por defecto vacio). El metodo send(), que acepta como unico argumento un string, Blog, ArrayBuffer o ArrayBufferView, es el unico lugar en el que probablemente tengamos que usar la propiedad “bufferedAmount” de nuestro WebSocket. “bufferedAmount” indica la cantidad de datos desde la ultima llamada a send() queda por enviar al servidor. Aunque podriamos seguir enviando aun cuando los datos estan todavia esperando ser enviados, a veces es posible que queramos enviar unicamente datos hacia el servidor, cuando no existen datos pendientes de enviar:
/**/ connection.onopen = function() { var intervalId = window.setInterval(function() { if(connection.readyState != WebSocket.OPEN) { window.clearInterval(intervalId); return; } if(connection.bufferedAmount == 0) connection.send(updatedModelData); }, 50); } /**/
En el codigo anterior, enviamos nuevos datos hacia el servidor, como maximo cada 50 mills, unicamente cuando el buffer de envio esta vacio. Si la conexión esta cerrada, se para de enviar y se reinicia el intervalo del timeout.
La especificacion WebSocket de Java aparece en JCP como JSR 356 y esta incluida en la version Java EE 7. Implementa el API cliente y servidor. La base se encuentra en el API cliente: se especifican un conjunto de clases e interfaces dentro del paquete “javax.websocket” que incluyen toda la funcionalidad basica para un peer WebSocket. El API servidor, contenida dentro de “javax.websocket.server” incluye clases e interfaces que se basan en las clases cliente de manera que se añade funcionalidad. De esta manera, existen dos artifacts para el API: client-only y full. Ambos APIs contienen gran cantidad de clases e interfaces que no vamos a explicar aquí.
El API WebSocket Client
Se basa en la clase ContainerProvider y los interfaces WebSocketContainer ,RemoteEndpoint , y Session. WebSocketContainer permite acceder a todas las funcionalidades de un cliente WebSocket, y la clase ContainerProvider contiene un metodo static getWebSocketContainer, para acceder a la implementancion de cliente WebSocket subyacente. WebSocketContainer contiene cuatro metodos sobrecargados de “connectToServer”, los cuales aceptan una URI como parametro, para conectar al endpoint remoto e iniciar un handshake. Estos metodos, aceptan o bien una instancia de POJO de cualquier tipo que haya sido anotada con @ClientEndpoint , una Class<?> que representa un POJO y anotado con @ClientEndpoint, una instancia de una Endpoint, o una clase Class<? extends Endpoint>. Cualquiera de las maneras de declarar un Endpoint, la clase ha de contener un constructor sin argumentos y debe ser creada por nosotros. Si usamos los metodos de Endpoint o Class<? extends Endpoint>, debemos incluir una ClientEndpointConfig. Cuando se realice el handshake, el metodo “connectToServer” devolvera una Session. Es posible realizar varias operaciones con el objeto Session, la mas destacable es cerrar la Session (lo que cierra la conexión WebSocket) o enviar mensajes hacia el endpoint remoto.
Nota El API Java para WebSocket no especifica como ha de ser la implementacion. Es posible desarrollar con ella pero es preciso incluir un paquete con la implementacion. Si nuestras aplicaciones se ejecutan en un contenedor J2EE o en un Application Server, las implementaciones servidor y cliente se encuentran añadidas. Sin embargo si lo ejecutamos de forma standalone, es necesario incluir la implementacion apropiada para desplegar nuestra aplicación.
Los Endpoint de tipo WebSocket contiene los metodos onOpen , onClose , and onError, por otro lado las clases anotadas con @ClientEndpoint pueden tener sus metodos anotados con @OnOpen , @OnClose , y @OnError . Una clase que extiende de @ClientEndpoint o de Endpoint puede indicar uno o mas metodos anotados con @OnMessage para manejar los mensaje que se reciben desde otro endpoint remoto. El uso de clases y metodos anotados, nos aporta flexibilidad respecto a los parametros que podemos necesitar.
@OnOpen puede tener los siguientes parametros: Session (optional) EndpointConfig (optional)
@OnClose: Session (optional) CloseReason (optional)
@OnError: Session (optional) Throwable (required)
Los metodos anotados con @OnMessage son mas complejos. Ademas del parametro optional Session, han de contener exactamente una de las siguentes combinaciones de parametros:
Un parametro String para recibir la totalidad del mensaje Un String mas un boolean para recibir un mensaje de texto en partes, con el valor del boolean a “true” en la ultima parte Una tipo o clase Java, para recibir el mensaje de texto recibido convertido a dicho tipo Un java.io.Reader para recibir un mensaje de texto como stream bloqueante Un array byte[] o un “java.nio.ByteBuffer” para recibir el mensaje binario completo Un array byte[] o un “ByteBuffer”, mas un boolean para recibir un mensaje binario en partes Un “java.io.InputStream” para recibir un mensaje binario como stream bloqueante Un “PongMessage” (ping enviado como respuesta desde el servidor), para manejar respuestas de control del servidor Cualquier Objeto Java si el endpoint tiene un “Decoder.Text” , “Decoder.Binary” , “Decoder .TextStream” , o un “Decoder.BinaryStream” registrado para transformar dicho tipo de Objeto. El mensje de tipo de texto o binario debe corresponderse con el decoder registrado.
Un endpoint puede tener un solo metodo para manejar los eventos open, close y error; sin embargo podria tener mas de tres metodos para manejar mensajes: no mas de uno para los mensajes de texto, no mas de uno para mensajes binarios, y no mas de uno para respuestas de control del servidor (pongMessages).
Como nota final, acerca del cliente, se incluye el codigo de la dependencia Maven para el API client.
javax.websocket javax.websocket-client-api 1.0 provided
El API WebSocket Server
Se basa en toda el API cliente y añade un conjunto de clases e interfaces utiles. ServerContainer extends WebSocketContainer añade metodos para registrar instancias de “ServerEndpointConfig” o clases anotadas con @ServerEndpoint . Dentro de un contexto de servlets, se obtiene una instancia de ServerContainer invocando a “ServletContext .getAttribute( " javax.websocket.server.ServerContainer " )” . En una aplicación “standalone” necesitamos seguir las especificacion de la implementacion WebSocket que usamos para obtener una instancia de ServerContainer.
Sin embargo en casi todos los casos (incluido dentro de un contenedor J2EE), no es necesario obtener una referencia a ServerContainer. Basta con anotar la clase endpoint de servidor con “@ServerEndpoint”; de esta manera, la implementacion WebSocket buscara entre las clases la clase anotada de esta manera para registrar los server endpoints.
Cuando usamos la anotacion “@ServerEndpoint”, debemos indicar al menos el atributo “value”, el cual indica la URL relativa al recurso desplegado, al que dicho endpoint escuchara. La URL relativa ha de comenzar con “/”, y puede contener parametros. Por ejemplo, veamos la siguiente anotacion:
@ServerEndpoint("/game/{gameType}")
Si nuestra aplicación estuviese desplegada en la siguiente direccion: http[s]://www.example.org/app , el server endpoint responderia a las llamadas: ws[s]://www.example.org/app/game/chess , ws[s]://www.example.org/ app/game/checkers, y asi sucesivamente. De esta manera, los metodos anotados con @OnOpen , @OnClose , @OnError , or @OnMessage dentro de este server endpoint tendrian un parametro opcional indicado con la siguiente anotacion:
@PathParam( " gameType")
, y el container lo remplazaria por “chess” , “checkers” respectivamente. El evento que maneja los metodos en el servidor, funciona de la misma manera en los client endpoints. La diferencia entre cliente y servidor solo se produce durante el handshake. Una vez que termina el handshake y se establece la conexión, es cliente y el servidor son ambos endpoints equivalentes con las mismas funcionalidades y caracteristicas.