Anteriormente ya hemos hablado del framework Atmosphere (https://github.com/Atmosphere) en estos post https://unpocodejava.wordpress.com/2012/05/02/un-poco-de-atmosphere-framework/ y https://unpocodejava.wordpress.com/2014/04/01/un-poco-mas-de-atmosphere/
Hoy vamos a mostrar lo sencillo que es crear y consumir un Websocket utilizando este framework:
Lo primero es añadir la depencia del runtime del framework al pom.xml del proyecto que expondrá el Websocket:
Una vez añadida la dependencia, lo siguiente, es crear la clase que recibirá las conexiónes desde las aplicaciones clientes, a través del protocolo Websocket.
Se trata de una clase que debe extender la clase del framework de Atmosphere WebSocketHandlerAdapter y ademas debe decorarse con la anotación @WebSocketHandlerService
@WebSocketHandlerService(path = "/api_websocket", broadcaster = SimpleBroadcaster.class) public class WebSocketsProtocolAdapter extends WebSocketHandlerAdapter { ··············· }
En este ejemplo, hemos creado un manejador que abrirá un Websocket en el path /api_websocket de nuestro servidor.
Asimismo, en esta clase tendremos que implementar el método onOpen, que será invocado por el framework para cada nueva conexión WebSocket, y que recibirá un objeto a través del cual podremos recuperar información de la conexión del cliente y registrar listeners del estado de la conexión.
@Override public void onOpen(WebSocket webSocket) throws IOException { logger.info("New websocket connection added: "+webSocket.resource().getRequest().getRemoteAddr()); webSocket.resource().addEventListener(new WebSocketEventListenerAdapter() { @Override public void onDisconnect(AtmosphereResourceEvent event) { try { logger.info("Disconnecting websocket connection"); event.getResource().close(); } catch (IOException e) { logger.error("Error closing websocket connection: "+e.getMessage()); } } @Override public void onClose(AtmosphereResourceEvent event) { logger.info("Closing websocket connection"); super.onClose(event); } }); }
En este ejemplo, cuando recibimos una conexión desde un cliente, registramos un listener para realizar ciertas acciones cuando se desconecta y cierra la conexión.
A su vez, para recibir peticiones desde los clientes y en su caso devolverles una respuesta, tenemos que sobreescribir los métodos onTextMessage y/o onByteMessage, heredados de WebSocketHandlerAdapter y que reciben por argumentos un objeto a traves del cual podremos recuperar información de la conexión del cliente, y el mensaje enviado desde el cliente.
@Override public void onTextMessage(WebSocket webSocket, String message) throws IOException { AtmosphereResource resource = webSocket.resource(); logger.info("Gateway WebSocket receive SSAP message: "+message); String response=getGatewayWebSockets().process(message, resource); logger.info("Gateway WebSocket response SSAP message: "+response); Broadcaster b = resource.getBroadcaster(); b.broadcast(response, resource); }
En este ejemplo, sobreescribirmos el método onTextMessage para procesar las peticiones recibidas desde los clientes en formato mensaje de texto, las elevamos a nuestra lógica de negocio para procesarlas, y devolvemos la respuesta al cliente que realizó la petición.
Finalmente, hay que registrar el servlet del framework de Atmosphere en el fichero web.xml de nuestra aplicación servidora:
<servlet> <description>AtmosphereServlet</description> <servlet-name>AtmosphereServlet</servlet-name> <servlet-class>org.atmosphere.cpr.AtmosphereServlet</servlet-class> <init-param> <param-name>org.atmosphere.websocket.maxIdleTime</param-name> <param-value>86400000</param-value><!-- 24h --> </init-param> <init-param> <param-name>org.atmosphere.websocket.bufferSize</param-name> <param-value>2097152</param-value><!-- 2MB --> </init-param> <init-param> <param-name>org.atmosphere.websocket.maxTextMessageSize</param-name> <param-value>2097152</param-value><!-- 2MB --> </init-param> <init-param> <param-name>org.atmosphere.websocket.maxBinaryMessageSize</param-name> <param-value>2097152</param-value><!-- 2MB --> </init-param> <load-on-startup>0</load-on-startup> </servlet> <servlet-mapping> <servlet-name>AtmosphereServlet</servlet-name> <url-pattern>/api_websocket/*</url-pattern> </servlet-mapping>
En este ejemplo, al mismo tiempo que registramos el servlet, configuramos parámetros como el máximo tiempo que puede estar inactiva una conexión, así como el tamaño máximo de los mensajes, que por defecto es algo bajo (8192 bytes).
Una vez hecho esto, cuando arranquemos nuestra aplicación, tendremos nuestro WebSocket escuchando en el path indicado y podremos consumirlo de manera estandar.
Pej con un navegador con soporte para WebSocket, mediante un código similar al siguiente:
function WebSocketTest() { if ("WebSocket" in window) { alert("WebSocket is supported by your Browser!"); // Let us open a web socket var ws = new WebSocket("ws://localhost:8080/sib/api_websocket"); ws.onopen = function() { // Web Socket is connected, send data using send() ws.send("mi mensaje"); alert("Message is sent..."); }; ws.onmessage = function (evt) { var received_msg = evt.data; alert(received_msg); }; ws.onclose = function() { // websocket is closed. alert("Connection is closed..."); }; } else { // The browser doesn't support WebSocket alert("WebSocket NOT supported by your Browser!"); } }
O bien utilizando las librerias cliente de Atmosphere.
Pej desde un proyecto Java:
Importando ela librería cliente de Atmosphere, wasync, añadiendola al pom.xml de nuestro proyecto cliente:
Creando a continuación un cliente de WebSocket y la clase RequestBuilder que interpretará las peticiones y respuestas:
Client client = ClientFactory.getDefault().newClient(); RequestBuilder request = client.newRequestBuilder() .method(METHOD.GET) .uri("http://localhost:8080/sib/api_websocket") .encoder(new Encoder<SSAPMessage, String>() { @Override public String encode(SSAPMessage data) { return data.toJson(); } }) .decoder(new Decoder<String, SSAPMessage>() { @Override public SSAPMessage decode(Event type, String data) { data = data.trim(); // Padding if(data.length() == 0) { return null; } if (type.equals(Event.MESSAGE)) { return SSAPMessage.fromJsonToSSAPMessage(data); } else { return null; } } }) .transport(Request.TRANSPORT.WEBSOCKET) .transport(Request.TRANSPORT.SSE) .transport(Request.TRANSPORT.LONG_POLLING);
Estableciendo la conexión con el cliente a través de un Socket y registrando los listener para recibir la respuesta desde servidor y manejar errores y eventos en la conexión:
Socket socket = client.create(); socket.on("message", new Function<SSAPMessage>() { @Override public void on(SSAPMessage message) { ····· } }).on(new Function<Throwable>() { @Override public void on(Throwable t) { ····· } }).on(Event.CLOSE.name(), new Function<String>() { @Override public void on(String t) { ···· } }).on(Event.OPEN.name(), new Function<String>() { @Override public void on(String t) { ···· } }).open(request.build(),this.config.getTimeOutConnectionSIB(), TimeUnit.MILLISECONDS);
Y finalmente enviando mensajes al servidor:
socket.fire(msg);
Genial Jesús!!!Un gran post, hasta con código fuente bonito!!! 🙂
[…] Para ilustrar como desarrollar un nuevo Gateway, vamos a basarnos en el Gateway Websocket que estará disponible a partir de la release 2.10 de Sofia2, y que se ha creado utilizando el framework Atmosphere(ver https://unpocodejava.wordpress.com/2014/07/25/creando-websockets-de-manera-sencilla-con-atmosphere/) […]