Cet article est écrit dans le cadre de ma collaboration avec Tavendo.
On a déjà vu que WAMP c’est cool, mais c’est asynchrone et nos frameworks Web chéris WSGI sont synchrones.
J’ai donné une solution de contournement avec la lib crochet qui permet de faire tourner du twisted de manière synchrone dans son projet.
Néanmoins, beaucoup sont, j’en suis certain, à la recherche d’un truc plus simple. En effet, le bénéfice le plus immédiat de WAMP sont les notifications en temps réel. Et pour ça, crossbar vient avec le HTTP PUSHER service : quelques lignes de JSON dans le fichier de config de crossbar et zou, on peut publier sur un topic WAMP avec une simple requête POST :
"transports": [ { "type": "web", "endpoint": { "type": "tcp", "port": 8080 }, "paths": { ... "notify": { "type": "pusher", "realm": "realm1", "role": "anonymous" } } } ] |
Et derrière, pour publier un event sur le sujet “super_sujet”, on peut faire :
import requets requests.post("http://ip_du_router/pusher", json={ 'topic': 'super_sujet' 'args': [queques, params, a, passer, si, on veut] }) |
Ceci va envoyer une requête POST à un service de crossbar qui va transformer ça en véritable publish WAMP.
Histoire d’illustrer tout ça, je vais vous montrer comment construire un petit service de monitoring avec Crossbar.io et Django. Pour suivre le tuto vous aurez besoin :
- De connaissances de base en JS.
- De connaître le principe de WAMP.
- De savoir installer des bibliothèques Python avec extensions sur votre machine. pip et virtualenv sont vos amis.
- De connaître Django. Même si le concept peut s’appliquer à Flask, Pyramid, ou autre.
Premiers pas
Le but du jeu est d’avoir un petit client WAMP qu’on lance sur chaque machine qu’on veut monitorer. Celui-ci va, toutes les x secondes, récupérer l’usage CPU, RAM et disque et faire un publish WAMP.
A l’autre bout, on a un site Django qui a un modèle pour chaque machine monitorée, avec des valeurs pour dire si on est intéressé par le CPU, la RAM ou le disque et la valeur de x.
Une page affiche en temps réel tous les relevés pour toutes les machines. Si dans l’admin de Django on change un modèle, la page reflète ce changement.
On aura donc besoin de django (pip install Django
, ça c’est pas trop dur), requests (pip install requests
, jusqu’ici tout va bien), et psutil.
psutil est la lib Python qui va nous permettre de récupérer toutes le valeurs pour la RAM, le disque et le CPU. Elle utilise des extensions en C, il faut donc un compilateur et les headers Python. Sous Ubuntu, il faut donc faire :
sudo apt-get install gcc python-dev |
Sous CentOS ça donne :
yum groupinstall "Development tools" yum install python-devel |
Sous Mac, les headers Python devraient être inclus, mais il vous faut aussi GCC. Si vous avez xcode, vous avez déjà un compilateur, sinon, il existe un installeur plus léger.
Sous windows, c’est un wheel donc rien à faire normalement.
Et reste plus qu’à pip install psutil
.
Enfin il nous faudra, logique, installer crossbar. pip install crossbar
, sachant que sous Windows vous aurez besoin de PyWin32 et comme toujours, d’avoir les dossiers C:\Python27\
and C:\Python27\Scripts
dans votre PATH.
Le HTML
On a besoin que d’une page. Afin de rendre le tuto agnostique, je l’ai fait en pur JS, pas de jQuery, pas d’Angular. Donc c’est verbeux :)
<!DOCTYPE html> <html> <head> <meta charset="utf-8" /> <!-- De quoi cacher un bloc facilement --> <style type="text/css"> .hide {display:none;} </style> <!-- La lib JS qui permet de parler WAMP . Ici je suppose qu'on utilise un navigateur qui support websocket. Il est possible de faire du fallback sur flash ou long poll, mais ce sont des dépendances en plus. --> <script src="https://autobahn.s3.amazonaws.com/autobahnjs/latest/autobahn.min.jgz" type="text/javascript"></script> <!-- Tout notre code client, inline pour faciliter votre lecture --> <script type="text/javascript"> /* Connexion à notre serveur WAMP */ window.addEventListener("load", function(){ var connection = new autobahn.Connection({ url: 'ws://127.0.0.1:8080/ws', realm: 'realm1' }); /* Quand la connexion est ouverte, exécuter ce code */ connection.onopen = function(session) { var clients = document.getElementById("clients"); /* Quand on reçoit l'événement clientstats, lancer cette fonction */ session.subscribe('clientstats', function(args){ var stats = args[0]; var serverNode = document.getElementById(stats.ip); /* Créer un li contenant un h2 et un dl pour ce client si il n'est pas encore dans la page. */ if (!serverNode){ serverNode = document.createElement("li"); serverNode.id = stats.ip; serverNode.appendChild(document.createElement("h2")); serverNode.appendChild(document.createElement("dl")); serverNode.firstChild.innerHTML = stats.name + " (" + stats.ip + ")"; clients.appendChild(serverNode); // Cacher les infos du serveur si il est désactivé. session.subscribe('clientconfig.' + stats.ip, function(args){ var config = args[0]; if (config.disabled){ var serverNode = document.getElementById(config.ip); serverNode.className = "hide"; } }); } // Remettre à zéro le contenu du li du serveur. serverNode.className = ""; var dl = serverNode.lastChild; while (dl.hasChildNodes()) { dl.removeChild(dl.lastChild); } // Si on a des infos sur le CPU, les afficher if (stats.cpus){ var cpus = document.createElement("dt"); cpus.innerHTML = "CPUs:"; dl.appendChild(cpus); for (var i = 0; i < stats.cpus.length; i++) { var cpu = document.createElement("dd"); cpu.innerHTML = stats.cpus[i]; dl.appendChild(cpu); }; } // Si on a des infos sur l'espace disque, les afficher if (stats.disks){ var disks = document.createElement("dt"); disks.innerHTML = "Disk usage:"; dl.appendChild(disks); for (key in stats.disks) { var disk = document.createElement("dd"); disk.innerHTML = "<strong>" + key + "</strong>: " + stats.disks[key]; dl.appendChild(disk); }; } // Si on a des infos sur l'usage mémoire, les afficher. if (stats.memory){ var memory = document.createElement("dt"); memory.innerHTML = "Memory:"; dl.appendChild(memory); var memVal = document.createElement("dd"); memVal.innerHTML = stats.memory; dl.appendChild(memVal); } }); }; // Ouvrir la connexion avec le routeur WAMP. connection.open(); }); </script> <title> Monitoring</title> </head> <body> <h1> Monitoring </h1> <ul id="clients"></ul> </body> </html> |
Comme vous pouvez le voir, c’est beaucoup de JS ordinaire et du DOM. Les seules parties spécifiques à WAMP sont :
var connection = new autobahn.Connection({ url: 'ws://127.0.0.1:8080/ws', realm: 'realm1' }); connection.onopen = function(session) { ... } connection.open(); |
Pour se connecter au serveur.
Et :
session.subscribe('nom_du_sujet', function(args){ ... } |
Pour réagir à la publication d’un sujet WAMP.
Le client de monitoring
C’est la partie qui va aller sur chaque machine qu’on veut surveiller.
# -*- coding: utf-8 -*- from __future__ import division import socket import requests import psutil from autobahn.twisted.wamp import Application from autobahn.twisted.util import sleep from twisted.internet.defer import inlineCallbacks def to_gib(bytes, factor=2**30, suffix="GiB"): """ Converti un nombre d'octets en gibioctets. Ex : 1073741824 octets = 1073741824/2**30 = 1GiO """ return "%0.2f%s" % (bytes / factor, suffix) def get_infos(filters={}): """ Retourne la valeur actuelle de l'usage CPU, mémoire et disque. Ces valeurs sont retournées sous la forme d'un dictionnaire : { 'cpus': ['x%', 'y%', etc], 'memory': "z%", 'disk':{ '/partition/1': 'x/y (z%)', '/partition/2': 'x/y (z%)', etc } } Le paramètre filter est un dico de la forme : {'cpus': bool, 'memory':bool, 'disk':bool} Il est utilisé pour décider d'inclure ou non les résultats des mesures pour les 3 types de ressource. """ results = {} if (filters.get('show_cpus', True)): results['cpus'] = tuple("%s%%" % x for x in psutil.cpu_percent(percpu=True)) if (filters.get('show_memory', True)): memory = psutil.phymem_usage() results['memory'] = '{used}/{total} ({percent}%)'.format( used=to_gib(memory.active), total=to_gib(memory.total), percent=memory.percent ) if (filters.get('show_disk', True)): disks = {} for device in psutil.disk_partitions(): usage = psutil.disk_usage(device.mountpoint) disks[device.mountpoint] = '{used}/{total} ({percent}%)'.format( used=to_gib(usage.used), total=to_gib(usage.total), percent=usage.percent ) results['disks'] = disks return results # On créé le client WAMP. app = Application('monitoring') # Ceci est l'IP publique de ma machine puisque # ce client doit pouvoir accéder à mon serveur # depuis l'extérieur. SERVER = '172.17.42.1' # D'abord on utilise une astuce pour connaître l'IP publique de cette # machine. s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.connect(("8.8.8.8", 80)) # On attache un dictionnaire à l'app, ainsi # sa référence sera accessible partout. app._params = {'name': socket.gethostname(), 'ip': s.getsockname()[0]} s.close() @app.signal('onjoined') @inlineCallbacks def called_on_joinded(): """ Boucle envoyant l'état de cette machine avec WAMP toutes les x secondes. Cette fonction est exécutée quand le client "joins" le router, c'est à dire qu'il est connecté et authentifié, prêt à envoyer des messages WAMP. """ # Ensuite on fait une requête post au serveur pour dire qu'on est # actif et récupérer les valeurs de configuration de notre client. app._params.update(requests.post('http://' + SERVER + ':8080/clients/', data={'ip': app._params['ip']}).json()) # Puis on boucle indéfiniment while True: # Chaque tour de boucle, on récupère les infos de notre machine infos = {'ip': app._params['ip'], 'name': app._params['name']} infos.update(get_infos(app._params)) # Si les stats sont a envoyer, on fait une publication WAMP. if not app._params['disabled']: app.session.publish('clientstats', infos) # Et on attend. Grâce à @inlineCallbacks, utiliser yield indique # qu'on ne bloque pas ici, donc pendant ce temps notre client # peut écouter les événements WAMP et y réagir. yield sleep(app._params['frequency']) # On dit qu'on est intéressé par les événements concernant clientconfig @app.subscribe('clientconfig.' + app._params['ip']) def update_configuration(args): """ Met à jour la configuration du client quand Django nous le demande. """ app._params.update(args) # On démarre notre client. if __name__ == '__main__': app.run(url="ws://%s:8080/ws" % SERVER) |
Le plus gros du code est get_infos()
qui n’a rien à voir avec WAMP. C’est nous, manipulant psutil
pour obtenir les relevés de cette machine. Je ne recommande bien évidement pas de faire ça en prod : une grosse fonction monolithique qui prend un dico en param. Mais c’est pour une démo, et ça me permet de grouper les instructions qui vont ensemble pour faciliter votre compréhension.
La partie qui concerne WAMP :
app = Application('monitoring') @app.signal('onjoined') @inlineCallbacks def called_on_joinded(): ... while True: ... app.session.publish('clientstats', infos) ... yield sleep(app._params['frequency']) |
app = Application('monitoring')
créé un client WAMP, et @app.signal('onjoined')
nous dit de lancer la fonction quand notre client est connecté et prêt à envoyer des événements. @inlineCallbacks
est une spécificité de Twisted qui nous permet d’écrire du code asynchrone sans avoir à mettre des callback partout : à la place on met des yield.
Tout le boulot de notre client a lieu dans la boucle : app.session.publish('clientstats', infos)
publie les nouvelles mesures de CPU/RAM/Disque via WAMP, puis attend un certain temps (yield sleep(app._params['frequency'])
) avant de le faire à nouveau. L’attente n’est pas bloquante car elle se fait avec le sleep
de Twisted.
N’oublions pas :
@app.subscribe('clientconfig.' + app._params['ip']) def update_configuration(args): app._params.update(args) |
La fonction update_configuration()
sera appelée à chaque fois qu’une publication WAMP sera faite sur le sujet clientconfig.<ip_du_client>
. Notre fonction ne fait que mettre à jour la configuration du client, qui est un dico de la forme :
{'cpus': True, 'memory': False, 'disk': True, 'disabled': False, 'frequency': 1} |
C’est ce dico qui est utilisé par get_infos()
pour choisir quelles mesures récupérer, et aussi par sleep()
pour savoir combien de secondes attendre avant la prochaine mesure.
La valeur initiale de ce dico est récupérée au lancement du client, en faisant :
app._params.update(requests.post('http://' + SERVER + ':8080/clients/', data={'ip': app._params['ip']}).json()) |
requests.post(url_du_serveur, data={'ip': app._params['ip']}).json()
fait en effet une requête POST vers une URL de django qui nous allons voir plus loin, et qui retourne la configuration du client portant cette IP sous forme de JSON.
On utilise donc une fois HTTP pour obtenir les valeurs de départs, et ensuite WAMP pour les mises à jours des futures valeurs. WAMP et HTTP ne s’excluent pas : ils sont complémentaires.
Petite parenthèse sur :
SERVER = '172.17.42.1' s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.connect(("8.8.8.8", 80)) app._params = {'name': socket.gethostname(), 'ip': s.getsockname()[0]} s.close() |
D’une part, j’ai mis l’IP du serveur qui va contenir Crossbar.io et Django en dur car je suis, je pense que maintenant vous le savez, une grosse feignasse. Mais en prod, vous me faites un paramètre, on est d’accord ? Ensuite, il faut que j’identifie mon client, ce que je fais avec l’adresse IP. Il me faut donc son adresse IP externe, et je l’obtiens avec une astuce consistant à me connecter à l’IP 8.8.8.8 (les DNS google \o/) et en fermant la connexion juste derrière. Ce me permet de voir comment les autres machines me voit depuis l’extérieur.
Le site Django
Puisque le prérequis de l’article et de connaître Django, ça va pas être trop dur.
On créé son projet et son app :
django-admin startproject django_project
./manage.py startapp django_app |
On se rajoute un petit modèle qui contient la configuration de chaque client (vous vous souvenez, le fameux dico) :
# -*- coding: utf-8 -*- import requests from django.db import models from django.db.models.signals import post_save from django.dispatch import receiver from django.forms.models import model_to_dict class Client(models.Model): """ Configuration de notre client. """ # Pour l'identifier. ip = models.GenericIPAddressField() # Quelles données envoyer à notre dashboard show_cpus = models.BooleanField(default=True) show_memory = models.BooleanField(default=True) show_disk = models.BooleanField(default=True) # Arrêter d'envoyer les données disabled = models.BooleanField(default=False) # Fréquence de rafraîchissement des données frequency = models.IntegerField(default=1) def __unicode__(self): return self.ip @receiver(post_save, sender=Client, dispatch_uid="server_post_save") def notify_server_config_changed(sender, instance, **kwargs): """ Notifie un client que sa configuration a changé. Cette fonction est lancée quand on sauvegarde un modèle Client, et fait une requête POST sur le bridge WAMP-HTTP, nous permettant de faire un publish depuis Django. """ requests.post("http://127.0.0.1:8080/notify", json={ 'topic': 'clientconfig.' + instance.ip, 'args': [model_to_dict(instance)] }) |
La partie modèle est connue. L’astuce est dans :
@receiver(post_save, sender=Client, dispatch_uid="server_post_save") def notify_server_config_changed(sender, instance, **kwargs): requests.post("http://127.0.0.1:8080/notify", json={ 'topic': 'clientconfig.' + instance.ip, 'args': [model_to_dict(instance)] }) |
On utilise ici les signaux Django, une fonctionnalité du framework qui nous permet de lancer une fonction quand quelque chose se passe. Ici on dit “lance cette fonction quand le modèle Client
est modifié”.
Donc notify_server_config_changed
va se lancer quand la config d’un client est modifiée, par exemple dans l’admin, et recevoir l’objet modifié via son paramètre instance
.
On fait alors une petite requête POST sur http://127.0.0.1:8080/notify
, l’URL sur laquelle on configurera plus loin notre service de push. En faisant une requête dessus, on va demander à Crossbar.io de transformer la requête HTTP en message publish WAMP, ici sur le sujet ‘clientconfig.<ip_du_client>’. On publie donc un message WAMP, depuis Django.
Ca marche depuis n’importe où, pas juste Django. Depuis le shell, depuis Flask, n’importe où on peut faire une requête HTTP vers le service de push de crossbar.
Ce message va être récupéré par notre client, où qu’il soit, puisqu’il est aussi connecté au routeur WAMP. Comme, je vous le rappelle, notre client fait ça :
@app.subscribe('clientconfig.' + app._params['ip']) def update_configuration(args): app._params.update(args) |
Il va recevoir ce message, et donc le contenu de 'args': [model_to_dict(instance)]
, c’est à dire la nouvelle configuration qu’on a changé en base de donnée. Il se met ainsi à jour immédiatement. La boucle est bouclée.
Comme on veut profiter de notre boucle toute bouclée, on rajoute le modèle dans l’admin :
from django.contrib import admin # Register your models here. from django_app.models import Client admin.site.register(Client) |
Ainsi, les configs des clients seront éditables dans l’admin, et quand on cliquera sur “save”, ça va lancer notre publish WAMP qui mettra à jour le bon client.
Le reste, c’est du fignolage. Une petite vue pour créer ou récupérer notre configuration de client au démarrage :
# -*- coding: utf-8 -*- import json from django.http import HttpResponse from django_app.models import Client from django.views.decorators.csrf import csrf_exempt from django.forms.models import model_to_dict @csrf_exempt def clients(request): """ Récupère la config d'un client en base de donnée et lui envoie.""" client, created = Client.objects.get_or_create(ip=request.POST['ip']) return HttpResponse(json.dumps(model_to_dict(client)), content_type='application/json') |
On désactive la protection CSRF pour la démo, mais encore une fois, en prod, faites ça proprement, avec une jolie authentification pour protéger la vue, et tout, et tout.
Donc, cette vue récupère la configuration d’un client avec cette IP (la créant au besoin), et la retourne en JSON. Souvenez-vous, cela permet à notre client de faire :
app._params.update(requests.post('http://' + SERVER + ':8080/clients/', data={'ip': app._params['ip']}).json()) |
Au démarrage et se déclarer dans la base de données, tout en récupérant sa config.
On branche tout ça via urls.py
:
from django.conf.urls import patterns, include, url from django.contrib import admin from django.views.generic import TemplateView urlpatterns = patterns('', url(r'^admin/', include(admin.site.urls)), url(r'^clients/', 'django_app.views.clients'), url(r'^$', TemplateView.as_view(template_name='dashboard.html')), ) |
L’admin, notre vue toute fraiche, et de quoi servir le HTML du début de l’article.
Y plus qu’à :
./manage.py syncdb |
Crossbar.io
Finalement, tout ce qu’il reste, c’est notre bon crossbar :
crossbar init |
Ceci nous pond le dossier .crossbar
dans lequel on a le fichier config.json
qu’on édite pour qu’il ressemble à ça :
{ "workers": [ { "type": "router", "realms": [ { "name": "realm1", "roles": [ { "name": "anonymous", "permissions": [ { "uri": "*", "publish": true, "subscribe": true, "call": true, "register": true } ] } ] } ], "transports": [ { "type": "web", "endpoint": { "type": "tcp", "port": 8080 }, "paths": { "/": { "type": "wsgi", "module": "django_project.wsgi", "object": "application" }, "ws": { "type": "websocket" }, "notify": { "type": "pusher", "realm": "realm1", "role": "anonymous" }, "static": { "type": "static", "directory": "../static" } } } ] } ] } |
La partie du haut c’est un peu l’équivalent du chmod 777
de crossbar :
"type": "router", "realms": [ { "name": "realm1", "roles": [ { "name": "anonymous", "permissions": [ { "uri": "*", "publish": true, "subscribe": true, "call": true, "register": true } ] } ] } ], |
“Met moi en place un router avec un accès nommé realm1 qui autorise à tous les anonymes de tout faire”. Un realm est une notion de sécurité dans Crossbar.io qui permet de cloisonner les clients connectés, nous on va tout mettre sur le même realm, c’est pour une démo je vous dis.
Ensuite on rajoute les transports pour chaque techno qui nous intéresse. On va tout regrouper sur le port 8080 car Twisted peut écouter en HTTP et Websocket sur le même port :
"transports": [ { "type": "web", "endpoint": { "type": "tcp", "port": 8080 }, |
A la racine, on sert notre app Django :
"/": { "type": "wsgi", "module": "django_project.wsgi", "object": "application" }, |
Car oui, crossbar peut servir votre app django en prod. Pas besoin de gunicorn. En fait même pas besoin d’nginx pour un site simple, car ça tient très bien la charge. On a juste à lui indiquer quelle variable (application
) de quel fichier WSGI (django_project/wsgi.py
) charger, et il s’occupe du reste.
Sur ‘/ws’, on écoute en Websocket :
"ws": { "type": "websocket" }, |
WAMP passe par là, et c’est pour ça que nos clients se connectent en faisant app.run(url="ws://%s:8080/ws" % SERVER)
et autobahn.Connection({url: 'ws://127.0.0.1:8080/ws', realm: 'realm1'});
.
‘/notify’ va recevoir le bridge WAMP-HTTP :
"notify": { "type": "pusher", "realm": "realm1", "role": "anonymous" } |
Tous les anonymes du realm1
peuvent l’utiliser. Grâce à ça, on a pu faire depuis notre signal Django :
requests.post("http://127.0.0.1:8080/notify", json={ 'topic': 'clientconfig.' + instance.ip, 'args': [model_to_dict(instance)] }) |
Et donc publier un message WAMP, via un POST HTTP.
Enfin, on sert les fichiers statiques Django avec Crossbar (oui, il fait aussi ça :):
"static": { "type": "static", "directory": "../static" } |
N’oubliez pas le de spécifier STATIC_ROOT
dans le fichier settings et lancer ./manage.py collecstatic
.
Tout ça en place, on lance notre routeur :
export PYTHONPATH=/chemin/vers/votre/project crossbar start |
(Remplacer export
par set
sous Windows>
La modification de PYTHONPATH
est nécessaire pour que crossbar trouve votre fichier WSGI.
On visite http:127.0.0.1:8080/, qui va charger notre template Django dashboard.html
.
Chaque machine qui lance un client via python client.py
va déclencher l’apparition des stats sur notre dashboard, qui seront mises à jour en temps réel.
Si on va sur http:127.0.0.1:8080/admin/ et qu’on change la config d’un client, notre client s’adapte, et notre dashboard se met à jour automatiquement.
Conclusion
Notre projet ressemble à ceci au final :
. ├── client.py ├── .crossbar │ ├── config.json ├── db.sqlite3 ├── django_app │ ├── admin.py │ ├── __init__.py │ ├── models.py │ ├── templates │ │ └── dashboard.html │ └── views.py ├── django_project │ ├── __init__.py │ ├── settings.py │ ├── urls.py │ └── wsgi.py ├── static └── manage.py
Vous pouvez récupérer le code ici.
Finalement, très peu de code WAMP : un peu dans le JS, un peu dans le client. Et la seule chose qui lie WAMP à Django est la config crossbar qui ajoute le service HTTP PUSHER et notre requête POST dans models.py
Cette technique n’est pas limitée à Django, et fonctionne bien pour toutes techno synchrones qui ne peut pas lancer un client WAMP directement en son sein. Pour le moment, le bridge HTTP-WAMP ne propose que PUB, pas de SUB, de pas de RPC. C’est déjà assez sympa pour avoir les notifications en temps réel un peu partout, et ça Tobias m’a dit qu’il ajoutera les autres actions dans un future proche.
En attendant, vous voyez le deal : on peut mélanger allègrement HTTP, WAMP, Python, JS, Client, Serveur, et monter sa petite architecture comme on le souhaite. Crossbar permet de démarrer du WSGI, mais aussi les clients WAMP sur la même machine et même n’importe quel process en ligne de commande (par exemple NodeJS) si besoin. C’est Mac Gyver ce truc.
On aurait pu écrire le client en Python 3 puisqu’il est sur une autre machine. Et en fait, si on lance Django en dehors de crossbar, aussi la partie Django en Python 3. Le code de crossbar n’est jamais modifié, on touche juste la configuration JSON.
Personnellement j’ai lancé plusieurs images dockers avec un client dedans à chaque fois, et c’est vraiment sympas de voir les machines se rajouter sur le dashboard en temps réel. On a une super sensation d’interactivité quand on change une valeur dans l’admin et qu’on voit le dashboard bouger.
super tuto,
tres tres utiles.
C’est accessible à tout public, j’ai beaucoup apris.
peux tu ajouter :
django-admin startproject django_project
cd django_project/
./manage.py startapp django_app
Ce serais un plus de montrer comment mettre en production. (avec supervisor par example.)
Et l’organisation des fichiers dans l’os et des droit sur les fichiers.
Tu peux le faire de maniere generic et joindre le lynk à chaque tuto.
Par exemple la tu as utiliser un nom generic django_project et django_app si tu fais ça dans tout les futur tuto wamp ou autre.
ça rendrais accessible à n’importe qui, le defaut sur la plus part des tuto que l’on trouve sur le net c’est qu’on peut pas faire une app end to end. La le fait de joindre la config crossbar l’arborescence de l’app les commandes ça facilite vraiment la vie et ça aide à comprendre.
Voici l’erreur que j’ai eu après avoir fait: crossbar start
Exception: invalid type ‘pusher’ for sub-path service in Web transport path service ‘notify’ configuration
Il semble que le service pusher ait été renommé en publisher.
Ce qui donne dans le fichier config.json
“notify”: {
“type”: “publisher”,
“realm”: “realm1″,
“role”: “anonymous”
}
Bien vu. J’ai ouvert un ticket : https://github.com/crossbario/crossbarwww/issues/55
Bonjour,
Je viens de découvrir depuis peu de temps le principe Crossbario/WAMP, ce qui m’intéresse fortement notamment dans un premier temps pour mettre à jour mon application domotique “maison” sous Django.
J’ai donc voulu tester ce topic dans un virtualenv en python 3.5, puisqu’apparemment c’est ok maintenant.
Seulement, crossbario me retournait systématiquement une erreur : WSGI unsupported.
Après plusieurs heures de galères à faire de multiples vérifications, je viens de trouver d’où vient le problème.
C’est Twisted, qui ne supporte pas encore cette fonctionnalité!
try:
from twisted.web.wsgi import WSGIResource
_HAS_WSGI = True
except ImportError:
## Twisted hasn't ported this to Python 3 yet
_HAS_WSGI = False
Ce que l’on peut également lire sur leur site dans les dernières news de septembre 2015:
More Python 3
Twisted 15.5 (coming Oct/Nov) will also contain the first release of twistd (the Twisted Daemon runner) on Python 3. The only plugin shipping with it right now is web, with most base features (file serving, running user-specified Resources). WSGI support and distributed web serving has not yet made it in, but the WSGI support is coming soon.
A moins que mon souci vienne d’ailleurs, ca serait bien de le préciser quelque part dans le topic pour d’autres personnes.
Merci et beau travail.
Merci pour la précision :)
Bonjour,
Pareil dans le script python. J’ai dû mettre un “u” devant “ws://%s:8080/ws” % SERVER
On démarre notre client.
if name == ‘main‘:
app.run(url=u“ws://%s:8080/ws” % SERVER)
Ca c’est pour Python < 3.3.