Les managers le détestent : faites tourner WAMP dans Django avec cette astuce insolite 9
dimanche 4 janvier 2015 à 20:45Il existe une lib appelée crochet qui permet de faire marcher des API de twisted entre deux bouts de code bloquants. Certes, ça ne marche qu’en 2.7 et c’est pas hyper performant, mais on peut faire des trucs mignons du genre cette démo qui mélange flask et WAMP.
C’est du pur Python, pas de process externe à gérer, c’est presque simple.
Bref, si on veut utiliser WAMP avec une app synchrone comme flask, c’est un bon moyen de s’y mettre. On aura jamais des perfs fantastiques, mais on peut pusher vers le browser.
Du coup je me suis demandé si on pouvait faire ça avec Django.
Évidement, ça a été un peu plus compliqué car par défaut runserver
lance plusieurs workers et fait un peu de magie avec les threads. Mais après un peu de bidouillage, ça marche !
On peut utiliser WAMP, directement
dans Django.
Suivez le guide
D’abord, on installe tout le bouzin (python 2.7, souvenez-vous) :
pip install crossbar crochet django |
Il vous faudra un Django 1.7, le tout dernier, car il possède une fonctionnalité qui nous permet de lancer du code quand tout le framework est chargé.
Vous vous faites votre projet comme d’hab, et vous ouvrez le fichier de settings et au lieu de mettre votre app dans INSTALLED_APPS
, vous rajoutez ça :
INSTALLED_APPS = ( '...', 'votreapp.app.VotreAppConfig' ) |
Puis dans le module de votre app, vous créez un fichier app.py, qui va contenir ça:
# -*- coding: utf-8 -*- import crochet from django.apps import AppConfig # On charge l'objet contenant la session WAMP définie dans la vue from votreapp.views import wapp class VotreAppConfig(AppConfig): name = 'votreapp' def ready(self): # On dit a crochet de faire tourner notre app wamp dans sa popote qui # isole le reactor de Twisted @crochet.run_in_reactor def start_wamp(): # On démarre la session WAMP en se connectant au serveur # publique de test wapp.run("wws://demo.crossbar.io/ws", "realm1", start_reactor=False) start_wamp() |
On passe à urls.py dans lequel on se rajoute des vues de démo :
url(r'^ping/', 'votreapp.views.ping'), url(r'^$', 'votreapp.views.index') |
Puis dans notre fichier views.py, on met :
# -*- coding: utf-8 -*- import uuid from django.shortcuts import render import crochet # Crochet se démerde pour faire tourner le reactor twisted de # manière invisible. A lancer avant d'importer autobahn crochet.setup() from autobahn.twisted.wamp import Application # un objet qui contient une session WAMP wapp = Application() # On enrobe les primitives de WAMP pour les rendre synchrones @crochet.wait_for(timeout=1) def publish(topic, *args, **kwargs): return wapp.session.publish(topic, *args, **kwargs) @crochet.wait_for(timeout=1) def call(name, *args, **kwargs): return wapp.session.call(name, *args, **kwargs) def register(name, *args, **kwargs): @crochet.run_in_reactor def decorator(func): wapp.register(name, *args, **kwargs)(func) return decorator def subscribe(name, *args, **kwargs): @crochet.run_in_reactor def decorator(func): wapp.subscribe(name, *args, **kwargs)(func) return decorator # Et hop, on peut utiliser nos outils WAMP ! @register('uuid') def get_uuid(): return uuid.uuid4().hex @subscribe('ping') def onping(): with open('test', 'w') as f: f.write('ping') # Et à côté, quelques vues django normales def index(request): # pub et RPC en action côté Python publish('ping') print call('uuid') with open('test') as f: print(f.read()) return render(request, 'index.html') def ping(request): return render(request, 'ping.html') |
Après, un peu de templating pour que ça marche…
Index.html :
{% load staticfiles %} <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title> UUID </title> <script src="{% static 'autobahn.min.js' %}"></script> <script type="text/javascript"> var connection = new autobahn.Connection({ url: "ws://localhost:8080/ws", realm: "realm1" }); connection.onopen = function (session) { session.call("uuid").then(function (uuid) { var p = document.getElementById('uuid'); p.innerHTML = uuid; }); }; connection.open(); </script> </head> <body> <h2>UUID</h2> <p id="uuid"></p> </body> </html> |
ping.html :
{% load staticfiles %} <!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <title> Ping </title> <script src="{% static 'autobahn.min.js' %}"></script> <script type="text/javascript"> var connection = new autobahn.Connection({ url: "ws://localhost:8080/ws", realm: "realm1" }); connection.onopen = function (session) { session.subscribe("ping", function () { var ul = document.getElementById('ping'); var li = document.createElement('li'); li.innerHTML = 'Ping !' ul.appendChild(li); }); }; connection.open(); </script> </head> <body> <h2>Ping me !</h2> <ul id="ping"> </ul> </body> </html> |
On ouvre la console, on lance son routeur :
crossbar init crossbar start |
On lance dans une autre console son serveur Django :
./manage.py runserver |
Et si on navigue sur http://127.0.0.1:8000
, on récupère un UUID tout frais via RCP.
On peut aussi voir dans le shell que ça marche côté Python :
94cfccf0899d4c42950788fa655b65ed ping
D’ailleurs un fichier nommé “test” est créé à la racine du projet.
Et si on navigue sur http://127.0.0.1:8000/ping/
et qu’on refresh http://127.0.0.1:8000
plusieurs fois, on voit la page se mettre à jour.
Achievement unlock : use WAMP and Django code in the same file.
A partir de là
Il y a plein de choses à faire.
On pourrait faire une lib qui wrap tout ça pour pas à avoir à le mettre dans son fichier de vue et qui utilise settings.py pour la configuration.
Il faut tester ça avec des setups plus gros pour voir comment ça se comporte avec gunicorn, plusieurs workers, le logging de Django, etc. Je suis à peu près sûr que les callbacks vont être registrés plusieurs fois et ça devrait faire des erreurs dans les logs (rien de grave ceci dit).
On pourrait aussi adapter le RPC pour qu’il utilise les cookies d’authentification Django, et pouvoir les protéger avec @login_required.
Mais un monde d’opportunités s’offrent à vous à partir de là.
Moi, ça fait 6 h que je taffe dessus, je vais me pieuter.