Creando Websockets de manera sencilla con Atmosphere

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);

2 comentarios

Deja un comentario