20 de diciembre de 2011

Django + Socket.io

Si queremos Websocket en nuestra aplicación Django, hay que calentarse un poco la cabeza.

La versión actual de mod_wsgi no es posible tener websocket. Aquí se discute como extender el módulo de Apache para permitir estas conexiones. Lo dejamos en espera.

Podemos utilizar el webserver Tornado, un server no bloqueante desarrollado por FriendFeed (Facebook). O utilizar soluciones con gevent. Esta librería de python nos permite realizar peticiones asíncronas tirando de libevent.

Para no complicarnos, podemos utilizar la aplicación django_socketio.

Con esta app tenemos un websocket y el cliente para javascript de socket.io con todas las dependencias que necesitamos.

Al turrón...

Instalamos django-socketio.

$ sudo apt-get install python-dev build-essential libevent-1.4-2 libevent-dev
$ sudo pip install django_socketio
[...]
Successfully installed django-socketio gevent-socketio sphinx-me gevent-websocket gevent greenlet

Añadimos la aplicación al settings.py

INSTALLED_APPS = (
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.sites',
    'django.contrib.messages',

    # Uncomment the next line to enable the admin:
    'django.contrib.admin',

    # Uncomment the next line to enable admin documentation:
    # 'django.contrib.admindocs',

    # socket
    'django_socketio',
)

Añadimos a urls.py

urlpatterns += patterns('',
    ("", include("django_socketio.urls")),

    [otras urls]
)

Ahora creamos las vistas que serán el punto de entrada para los eventos del websocket.

@events.on_subscribe(channel="^canal-")
def menssaje(request, socket, context, channel):
    if not request.user.is_authenticated():
        socket.send_and_broadcast_channel({'action': 'connect', 'text': 'Bienvenido Invitado'}, channel=channel)
    else:
        socket.send_and_broadcast_channel({'action': 'connect', 'text': 'Bienvenido %s' % str(request.user.username)}, channel=channel)

Hay diferentes decoradores para recibir los eventos que se envíen al socket. (Consultar sección Broadcast and Send Methods en el proyecto)

En este ejemplo, los usuarios se subscribirán a canales con nombre "canal-ID". Cuando lo hagan se enviarán mensajes, tanto al usuario que se ha suscrito, como al resto de clientes conectados al mismo canal.

Otra vista podría ser del tipo:

@events.on_message(channel="^canal-")
def message(request, socket, context, message):
    message = message[0]

    action = message["action"]
    text = message["text"]

    if action == 'talk':
        print text

        socket.broadcast_channel({'action': 'talk', 'text': text}, channel)

Un usuario envía al socket un mensaje que será enviado al resto de clientes.

Como se ve, se ha definido un protocolo de comunicación entre el cliente y el servidor. Con el parámetro action, se controla las diferentes acciones que se pueden hacer en nuestra aplicación.

Ahora la parte del cliente que se comunica con el server. (simplifico al máximo)

template.html


Para arrancar la aplicación

$ ./manage.py runserver_socketio

Ya veremos en próximas entregas como pasarlo a un entorno de producción.

Referencias:
https://github.com/stephenmcd/django-socketio
http://socket.io/