PROJET AUTOBLOG


Sam & Max: Python, Django, Git et du cul

Site original : Sam & Max: Python, Django, Git et du cul

⇐ retour index

Le don du mois : VLC 6

samedi 14 mars 2015 à 12:12

Le concept du don du mois fait des emules, et c’est bien. Que nous réserve donc mars ?

Un petit flasback, comme souvent.

Souvenez-vous, ce temps des packs de codecs buggés et vérolés. Des multiples lecteurs à installer. Des fichiers incomplets ou corrompus illisibles pour un bit de travers. De votre ordi qui rame car le film est trop compressées. L’ère pré-historique de la lecture de vidéo.

Et puis, cocorico !

A l’école Centrale de Paris, des élèves décident de streamer des films entre deux points de leur internat, et ainsi naquis VLC.

Qui lit tout. Même les .mov, les trucs fait pour realplayer, les .ogg, les videos streamées, les films cassés, les DVD chiffrés ou zonés… Tout, en prenant le minimum de ressource. Avec tellement d’options en plus : les sous-titre, le décalage du son, le transcoding, le changement d’orientation, de format, de pitch, le saut des pubs, etc. Et il existe des interfaces ncurse, HTTP et telnet !

Cerise sur le gâteau, ça marche sur Mac. Sur Linux. Et sur Windows, en un exe, qu’on peut rendre portable sur clé USB. Et depuis la dernière version, sur mobile.

La fondation VLC accepte les bitcoins et reçoit donc 50€ d’amour inconditionnel.

Je suis très attachant 9

vendredi 13 mars 2015 à 13:03

Je n’ai rien contre le fait d’être attaché de temps en temps, mais il est vrai que je préfère attacher. Pas que ce soit une pratique quotidienne, pas plus que manger des frites à Mac Do, mais de temps en temps avec des amis c’est sympa.

Maintenant, quand on attache, il faut se poser la question de quoi, sur quoi, avec quoi et comment.

Bondage, weall started somewhere

Personne ne naît expert en ligotage

Quoi

Les bras, en priorité. C’est ce qui donne le plus de sensations.

Les chevilles en second, si on a la possibilité.

Homme attaché

On est pas obligé d’attacher à quelque chose en particulier

Après le reste n’est pas aussi important, c’est du bonus, c’est contextuel.

On peut attacher les bras aux chevilles pour donner un sentiment d’abandon total.

On peut attacher le cou pour le côté “laisse”.

Plus rare, mais intéressant, on peut attacher le taille en formant un harnais. Parfois pour suspendre le partenaire, parfois juste pour l’empêcher de gigoter. C’est quelque chose qu’on fait quand on veut donner un coup de pouce dans la progression de ce que peut supporter l’autre. Néanmoins le point d’attache est plus difficile à trouver. Astuce : si on ne peut pas attacher en face, diviser la corde ou autre, en deux, et attacher de part et d’autre. C’est presque aussi efficace.

On peut attacher les cheveux à quelque chose, mais c’est advanced, je le recommande pas aux débutants. Contrairement au cou, ce n’est pas très dangereux, mais il y a beaucoup de manière de casser l’ambiance avec cette idée.

On peut ligoter les seins. Bien entendu, le but n’est pas d’empêcher la personne de se débattre à coup de tétons, mais bien, pour certaines qui aiment cela, d’avoir les mamelles en tension. Dans le même registre, un bâillon est une forme d’attachement. Une chaussette dans la bouche et un bout de scotch font l’affaire. Perso j’aime bien la gags ball (c’est psychologique), mais c’est dur d’entre trouver à la bonne taille car la plupart sont trop grosses.

Et enfin, on peut attacher tout le corps pour un effet d’emprisonnement total.

Comme d’habitude, ce que vous allez attacher n’est pas juste dépendant du résultat que vous voulez obtenir, mais également de ce que peut accepter l’autre. Néanmoins, ne flippez pas : essayez, et si on vous dit non, essayez autre chose. Inutile de vous demander pendant des heures si oui ou non ça va le faire. Même demander directement à la personne n’est pas toujours une bonne solution, entre ceux qui ne savent pas ce qu’ils aiment, ceux qui n’osent pas l’avouer et ceux pour qui en parler leur retire l’excitation. Un peu comme quand vous voyez un enfant dire à un autre “tu veux sortir avec moi ?”. Adulte, ça vous paraîtrait non naturel de le demander à haute voix, et généralement une relation commence par la tentative d’un baiser, non la demande de permission de le faire.

Donc essayez, mais sans brusquer. Sans surprise. Montrez clairement ce que vous êtes en train de faire. Donnez le temps à la personne de dire non.

C’est l’éternel problématique du sexe : notre société vous dit qu’il faut demander la permission pour tout sinon vous êtes pire qu’Hitler, mais dans la pratique les personnes avec une sexualité avancée ne demandent pas tout. Certaines choses sont justes tentées, et on s’adapte au résultat et à la réaction. Il y a donc une véritable prise de risque social, comme souvent avec les trucs un peu fun.

Ça n’enlève pas le fait qu’il faut AVOIR la permission pour continuer quoique ce soit. Simplement que vous ne pouvez pas toujours la demander explicitement.

Dans les milieux SM expérimentés, il n’est cependant pas rare d’avoir des questionnaires à remplir au préalable pour que chacun sache ce qu’il peut faire ou pas. Mais le plupart des gens n’en sont pas à ce degré d’aisance sexuelle (tiens, je sais qu’on se connaît depuis une semaine, mais rempli ce questionnaire avant qu’on baise steupl). Et croyez-moi, ils font des trucs plus hard que de mettre une laisse.

Sur quoi

Bien entendu, les barreaux de lit viennent en première idée, et le radiateur en second. Mais on peut innover.

On peut s’attacher l’un à l’autre, de différentes manières. Une corde en corps à corps. Une main à une main. Une laisse du cou de l’un à la hanche de l’autre.

Double bondage

Nul besoin d’aller si loin, même attacher une seule main peut suffire à mettre la bonne ambiance

On peut s’attacher à une chaise, bien entendu. Les chaises roulantes sont marrantes 5 minutes, mais pas pratiques.

Un truc bien sympa est d’avoir un accessoire pour suspendre. Un crochet par exemple, attaché très solidement au mur ou au plafond. Une mezzanine avec des barreaux (chez Ikea ce sont de sacrés coquins) se prête naturellement à cela.

J’ai quand même une astuce spécial bricolo pour vous : prenez des attaches de sex shop, et faites un gros nœud d’un coté, puis passez les au dessus d’une porte qui ferme bien. Le nœud coincera les attaches dans le haut de la porte, et vous pouvez suspendre tout à loisir contre la porte. Avantage : ça s’enlève facilement après la séance.

Une chose à laquelle on pense plus rarement : attacher à un gros objet lourd mais transportable, à traîner comme un boulet de prisonnier. C’est utile surtout pour les scénarios domi de longue durée, par pour chahuter au goûter, mais les gens ne s’y attendent pas. Faites gaffe que le truc ne raye pas le parquet, ça m’a coûté une caution.

Si vous avez la chance de visiter des clubs SM, vous rentrerez avec tout un tas de supports inédits pour s’attacher : des croix, des tables spéciales, des “chevaux” de bois, etc. C’est impressionnant, et tout le monde n’a pas envie ni besoin d’aller aussi loin, mais il est bon de savoir que ça existe. Ça peut inspirer pour chez soi.

A ne pas tenter :

Avec quoi

Des choses larges. Les petites attaches coupent la circulation du sang, et peuvent être dangereuses. Je ne vais pas jeter la pierre, moi aussi j’ai attaché avec des lacets dans le feu de l’action, mais ce n’est pas idéal.

Les ceintures font de meilleures attaches improvisées.

En matière de corde, prenez de la grosse corde (chez Jardiland ce sont de sacrés coquins), et essayez la sur vous. Plusieurs longueurs sont utiles : courtes pour les brases et les jambes, longues pour les hanches ou le cou, très longue pour tout le corps, genre total bondage.

Shibari

Evidemment, les japonais ont trouvé le moyen d’en faire une forme d’art. Qui d’autres ?

Mais l’idéal pour débuter, ce sont encore les produits des sex shops. Beaucoup sont nuls à chier, il faut donc les essayer. Généralement, les attaches pour les bras et jambes sont pas mal. Un truc sympa qu’on ne trouve que dans ces boutiques ou sur le net, ce sont des rubans adhésifs qui se collent uniquement à eux même. Ça ne colle pas à la peau ni aux vêtements. On peut faire des choses intéressantes avec.

Sinon évidemment, le gros scotch gris est top pour un scénario type kidnapping. Évitez les zones poilues et les tissues fragiles, sauf si vous savez que ça plaît. Les attaches plastiques rapides, qui sont assez fines donc faites gaffe, brillent quand la rapidité de mise en place et la solidité sont importantes. Et elles permettent de créer de nouvelles zones d’attache ou d’en lier plusieurs (chez Bricorama ce sont de sacrés coquins).

De manière surprenante, les boutiques de sécurité (là où se fournissent les flics, gardiens, CRS, garde du corps, qui sont de sacrés coquins) possèdent plein de choses qui peuvent être détournées. Non, je ne parle pas des tasers bande d’abrutis, mais des différentes variantes des menottes (bien meilleures que celles des sex shop) et autres outils pour maîtriser “une menace”. Ne faites pas n’importe quoi néanmoins, ces magasins possèdent des choses dangereuses qui ne sont pas faites pour faire mumuse. Vous êtes des adultes (j’espère :)), vous vous devez le bon sens, dans le cas contraire je m’assurerai que vous receviez le Darwin Award qui vous est dû.

Autre source peu connue de matériel cochon, les hôpitaux (qui sont pleins de sacrés coquins), et particulièrement les départements avec des patients subissant des traitements invasifs, en grande douleur ou en internement psychiatrique. Sans aller jusqu’à la camisole de force, que je n’ai pas eu le plaisir de tester, les attaches des lits sont à la fois très polyvalentes (on peut les combiner, les ajuster et les attacher à presque n’importe quoi) et très confortables (on peut tirer dessus très fort sans blesser la personne). J’ai des amis infirmiers qui m’ont fourni en la matière, et c’est super chouette.

Enfin, si vous avez les moyens, les balançoires et autres outils de suspensions SM sont très, très fun, mais demandent une installation contraignante.

Comment

Avec concentration.

Spock bondage

Il faut rester logique

On peut faire un peu mal. On peut fournir un peu d’inconfort. Ou plus. Ou moins. A doser selon l’attaché(e).

Mais la sécurité est importante.

Si tout ce que vous faites est d’attacher la personne aux barreaux du lit, le pire qu’il puisse vous arriver sont des poignets un peu rouges.

Mais dès que vous faites de la laisse, de la suspension ou des attaches sur plusieurs parties du corps, il faut réfléchir à ce que vous faites.

Et c’est ce que beaucoup de personnes ne comprennent pas, dans un moment de bestialité sexuelle, qu’on doive avoir la maturité et la discipline de prendre du recul. Mais c’est comme ça que la confiance se construit, qu’on peut aller de plus en plus loin, et qu’on s’amuse.

Quelques conseils mécaniques : essayez d’orienter les attaches aux poignets de telle sorte que la corde appuie plus sur l’extérieur du bras qu’à l’intérieur (où sont les veines). Par exemples, si vous utilisez un zipper plastique, mettez l’intérieur des bras l’un contre l’autre, ou contre une surface, pour immobiliser les deux mains à la fois. Si vous serrez fort, ce sera désagréable, mais pas dangereux.

Pas évident si vous avez convenu que la personne se débattrait pendant l’acte, j’en conviens. Des fois ça donne des sessions fou-rire et ça finit n’importe comment :)

Les attaches des chevilles ne sont pas très risquées, donc si vous voulez tirer sur quelque chose, ici, vous pouvez.

A moins que vous sachiez très bien ce que vous faites, contrairement aux bras et aux jambes qui peuvent être en tension pour renforcer le sentiment d’emprisonnement, le cou doit toujours avoir du mou et jamais avec un angle possible dans lequel on peut se coincer et se blesser. Il y a 3 choses à vérifier : s’assurer que la trachée n’est pas écrasée pour laisser passer l’air et ne pas casser la voix, que les jugulaires ne sont pas bloquées pour assurer un flux sanguin, et que la nuque ne subit pas de pression. Souvenez-vous : le collier, c’est du jeu. Il existe des personnes qui aiment être étranglées, mais elles sont rares, et ça demande de l’attention de la part de celui qui étrangle. Il y a des gens qui meurent chaque année à cause d’asphyxie érotiques mal maîtrisées.

Laisse avec du mou

Ca ne veut pas dire que vous ne pouvez pas tirer sur la laisse. Ca veut dire qu’il faut gérer.

Le porno hard que vous voyez en ligne est fait par des professionnels. Tout paraît toujours simple quand on voit une scène, mais ils ont l’habitude, et la préparation.

Je sais, je vous ai manqué 10

jeudi 12 mars 2015 à 16:00

Mais à partir de demain les articles reprennent. Faudra que je depop tous les mails, les tickets github, répondre aux commentaires, checker indexerror, etc.

Ca va prendre du temps, mais ça repart.

Je ferai bien un article de cul pour faire une rentrée sur le pompier. Heu le bon pied.

Et ça repart 11

lundi 9 février 2015 à 09:03

Je me barre un mois dans la nature.

J’aurai pas d’ordi, donc pas de nouveaux posts sauf si Max se sort les doigts du fion.

Profitez-en pour relire les anciens articles :)

Un petit dashboard de monitoring avec Django et WAMP 1

samedi 7 février 2015 à 11:58

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 :

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.

Chaque machine possède un client WAMP

Chaque machine possède un client 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.

Si je déclique "CPU" dans l'admin Django, les CPUs ne sont plus affichés

Si je déclique “CPU” dans l’admin Django, les CPUs ne sont plus affichés

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.