Inicio > Hacking, Hacking Python, Networking, Programacion, Web Applications > Implementando WebSockets con Tornado

Implementando WebSockets con Tornado

En un articulo anterior he hablado sobre la librería Tornado y cómo se puede utilizar para implementar servidores y clientes TCP asíncronos. Además, también he hablado sobre algunas de las vulnerabilidades más comunes en WebSockets, una de las características más interesantes en la especificación HTML5. En esta ocasión veremos cómo utilizar los módulos disponibles en Tornado para crear un servidor web básico que soporte WebSockets para poder realizar pruebas de concepto rápidas y comprender el comportamiento tanto en clientes como servidores de los websockets.

Tornado es una librería que solamente se encuentra disponible para máquinas basadas en Unix, por este motivo, en un próximo articulo hablaré sobre otra implementación independiente de plataforma basada en Java llamada websocket4j. Otra buena solución cuando queremos realizar pruebas con un sistema basado en Windows.

  1. Implementación de un cliente utilizando la API de WebSockets

Antes de comenzar a implementar el servidor, vamos a comenzar creando un cliente básico que servirá posteriormente para probar cualquier implementación de un servidor con WebSockets. Para implementar un cliente, basta con utilizar la API en Javascript que se encuentra habilitada en prácticamente todos los navegadores modernos que soportan HTML5, como es el caso de Firefox, Opera o Chrome.

test.html

<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
    <script type="text/javascript">
        function WebSocketTest() {
                var ws = new WebSocket("ws://localhost:8888/ws?Id=123456789&name=Adastra&continue=Yes");
        if (ws != null && ws.readyState == WebSocket.OPEN) {
          ws.send("Data from the client to the server!");
        }
                ws.onopen = function() {
                    ws.send("Opening connection!");
                };
                ws.onmessage = function (evt) { 
                    var received_msg = evt.data;
                    alert("Message received... " + received_msg);
                };
                ws.onclose = function() { 
                    alert("Connection is closed...");
                };
        }
        </script>
    </head>
    <body>
        <a href="javascript:WebSocketTest()">Run WebSocket</a>
    </body>
</html>

En la página HTML se puede apreciar que la función “WebSocketTest” contiene todos los elementos necesarios para establecer una comunicación con el servidor web y posteriormente, enviar y recibir mensajes de forma asíncrona. Esto último es lo que hace tan interesantes los WebSockets, ya que después de establecer el handshake, tanto cliente como servidor pueden enviarse mensajes sin necesidad de esperar a que la otra parte conteste y el servidor, puede enviar datos sin necesidad de que exista una petición previa por parte del cliente.

Ahora bien, después de tener preparada la pieza de código correspondiente al cliente, lo siguiente consistirá en crear un servidor que se encargue de procesar las peticiones y manejar todas las conexiones utilizando el protocolo de WebSockets.

  1. Implementación del un servidor utilizando la API de WebSockets y la librería Tornado

Como comentaba anteriormente, Tornado cuenta con varias clases y funciones que permiten crear diversos tipos de elementos de red, tanto síncronos como asíncronos. En este caso concreto nos centraremos en el módulo que permite la creación de servidores y aplicaciones web con Tornado. Esto será útil para realizar pruebas de concepto y entender el funcionamiento de ciertas características propias en entornos web.
El siguiente programa permitirá la creación de un servidor web básico utilizando Tornado, el cual aceptará conexiones en HTTP normales si el usuario solicita el recurso “/” y conexiones utilizando el protocolo de WebSockets si el usuario solicita el recurso “/ws”.

serverTornado.py

</pre>
<pre>import tornado.ioloop
import tornado.web
import tornado.websocket
from tornado.options import define, options, parse_command_line
class Client:
    def __init__(self, clientId, name, cont, connection):
        self.id = clientId
        self.name = name
        self.cont = cont
        self.connection = connection 

clients = []
class IndexHandler(tornado.web.RequestHandler):
    def get(self):
        self.render("test.html")

class WebSocketHandler(tornado.websocket.WebSocketHandler):
    def open(self, *args):
        self.id = self.get_argument("Id")
        self.name = self.get_argument("name")
        self.cont = self.get_argument("continue")
        newclient = True
        for client in clients:
            if client.id == self.id:
                client.connection.write_message("Hello Again %s !" %(client.name))
                newclient = False
                break
        if newclient:
            clientRef = Client(self.id, self.name, self.cont, self)
            clients.append(clientRef)
            self.write_message("Hello %s !" %(self.name))
           

    def on_message(self, message):        
        for client in clients:
            if client.id == self.id:
                print "Message from %s received : %s" % (client.name, message)
    
      
    def on_close(self):
        for client in clients:
            if self.id == client.id:
                clients.remove(client)
                break

define("port", default=8888, help="run on the given port", type=int)
app = tornado.web.Application([
    (r'/', IndexHandler),
    (r'/ws', WebSocketHandler),
])

if __name__ == '__main__':
    parse_command_line()
    app.listen(options.port)
    tornado.ioloop.IOLoop.instance().start()

Los elementos más importantes del programa anterior son los siguientes:
– Objeto del tipo “
tornado.web.Application” el cual se encarga de definir las URI disponibles para el servidor web. En este caso concreto, se ha definido que el usuario podrá acceder a la ruta “/” y “/ws”. Si el usuario solicita el recurso “/” el servidor se encargará de ejecutar el handler “IndexHandler” y si el usuario solicita el recurso “/ws” el servidor se encargará de ejecutar el handler “WebSocketHandler”.

– IndexHandler: Clase que hereda de la clase “tornado.web.RequestHandler” y que se encarga de procesar las peticiones HTTP realizadas por los clientes que emplean el método GET. En este caso, la case se encarga simplemente de responder al cliente con la página “test.html”, la cual incluye el contenido que se ha explicado anteriormente en el la primera parte de este articulo, es decir, la página HTML con los elementos necesarios para interactuar con el servidor web.

– WebSocketHandler: Clase que hereda de la clase “tornado.web.WebSocketHandler” y que se encarga de procesar las peticiones entrantes que utilicen el protocolo de WebSockets. La clase incluye los métodos “open”, “on_message” y “on_close”, los cuales son invocados automáticamente cuando se abre una conexión, se recibe un mensaje y se cierra una conexión existente, respectivamente.

– Finalmente, la definición propiamente dicha del servidor web viene dada por una instancia de la clase “tornado.ioloop.IOLoop”, la cual se encarga de crear un hilo de ejecución que se mantendrá en funcionamiento de forma indefinida y que utilizará las opciones por línea de comandos que se han definido por medio de la función “tornado.options.define”.

Con todos lo anterior, ahora es posible ejecutar el servidor web y probar los métodos de los dos handlers definidos.

>python serverTornado.py

Si el usuario solicita el recurso “/”, el servidor se encargará de responder con la página “test.html” tal como se enseña en la siguiente imagen.

En enlace que se puede ver en la imagen, se encarga de invocar a una función en Javascript que permite interactuar con el servidor web y enviar mensajes utilizando el protocolo WebSockets, tal como se puede apreciar en la siguiente imagen.

Se trata de un ejemplo muy simple y que no solo permite conocer cómo funcionan los websockets, sino que también explica como utilizar Tornado para crear un servidor que los soporte. No obstante, tal como mencionaba anteriormente, Tornado solamente funciona para sistemas basados en Unix, con lo cual, en el próximo articulo hablaré sobre otra librería basada en Java llamada websockets4j.

Un Saludo y Happy Hack!
Adastra.

  1. Enrique
    octubre 15, 2015 en 1:28 pm

    Gracias por el articulo, he estado probando el codigo y la conexion se cierra inmediatamento luego de realizada, en el momento que en el cliente hago click en el boton websocket para iniciar la conexion aparece el alert de ConectionClose y me refleja el intento de conexion en el server pero nada mas.Por favor me gustaria saber cual es mi problema ? Gracias

    Me gusta

    • pep
      junio 2, 2016 en 1:19 pm

      El ejemplo es fantástico, pero me encuentro igual que enrique, inmediatamente al iniciar la connexión el server nos indica conexion closed, como si no pudiera crear el websocket.

      si coneceis la solución gracias.

      Me gusta

    • pep
      junio 2, 2016 en 1:21 pm

      Enrique encontraste el error del onclose() al establecer la conexión, tengo el mismo problema

      Me gusta

  2. Pyramidhead
    junio 18, 2016 en 2:10 pm

    Hola! gracias por el script, me sirvió para empezar en este tema del los websockets. y sobre el error que tienen los amigos de arriba. Se debe a que Tornado impide las conexiones bloqueas las conexiones que vengan de otro servidor. Para pruebas locales, solo basta de sobreescribir la función check_origin y devolver TRUE. OJO! sólo para pruebas en producción deberían implementar algo más seguro para permitir conexiones externas. Para más info http://www.tornadoweb.org/en/stable/index.html y ahí está todo. Aprendan a usar la documentación!!!

    Me gusta

    • junio 18, 2016 en 2:57 pm

      En efecto, por fin uno que se molesta en leer la documentación!
      Enhorabuena. 🙂

      Me gusta

  1. No trackbacks yet.

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

A %d blogueros les gusta esto: