PROJET AUTOBLOG


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

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

⇐ retour index

Télécharger une page du blog en PDF

lundi 2 juin 2014 à 12:14

On m’a demandé de rendre possible téléchargement des articles en PDF.

Je suis paresseux, donc plutôt que d’installer un plugin, j’ai juste fait un lien vers un service externe en haut à droite de toutes les pages.

Si vous cliquez, ça vous génère le PDF pour la page complète, et ça marche pour toutes les URL, pas uniquement les articles.

De rien.

flattr this!

Le potentiel de WAMP, autobahn et crossbar.io

dimanche 1 juin 2014 à 12:09

Je sais, je sais, je vous fais chier avec crossbar et autobahn.

Mais ça me tue de ne pas voir plus de monde exploiter cette techno.

Pendant que Max fait la sieste, j’ai pris mon stylo et j’ai fait la liste des besoins d’une app Web actuelle. Quels sont les composants qu’on utilise presque systématiquement, mais en agrégeant divers bouts de trucs à droite et à gauche ?

Ensuite j’ai regardé les possibilités des outils WAMP :

M’inspirant de cela, et du travail que je suis en train de faire avec l’équipe de Tavendo pour faire une API flaskesque pour autobahn, j’ai prototypé une API d’un framework Web qu’on pourrait coder au dessus de cette techno.

Voilà ce que ça donne…

Une API qui mélange flask et nodejs pour le Web

app = Application('YourProjectName')
 
# Envoyer et recevoir des requêtes HTTP
@app.http.post(r'/form')
def _(req, res):
    res.json({'data': 'pouet'})
 
@app.http.get(r'/user/:id/')
def _(req, res):
    res.render('index.html', {'data': 'pouet'})
 
# Servir des fichiers statiques
@app.http.serve('uri', '/path/to/dir', [allow_index])
 
app.run()

Comme c’est asynchrone, on a de très bonnes perfs. Comme c’est basé sur Twisted, on a pas besoin d’un serveur wsgi (gunicorn, uwsgi, etc) ni d’un proxy (nginx) devant. On peut le mettre en prod tel quel.

Parti de ce principe, on peut ajouter la gestion du PUB/SUB et du RPC pour WAMP :

# Callback attendant l'événement
@app.wamp.event('auth.signedin')
def _(ctx, a, b, c):
    pass
 
# déclenchement de l'événément
app.wamp.pub('auth.signedin')
 
# Déclaration du fonnction appelable à distance
@app.wamp.remote('auth.signin')
def _(ctx, a, b, c):
    pass
 
# appel de la fonnction
app.wamp.call('auth.signin')

On est souvent perdu quand on fait de l’asynchrone pour la première fois avec Python car on ne sait pas comment lancer du code après .run(). On peut régler la question proposant des hooks pour les instants clés de l’app.

# Callback à lancer quand l'app est prête
@app.on('app.ready')
def _(ctx, args):
    pass
 
# Signalement que l'app est prête (fait automatiquement en interne
# pour les moments les plus importants)
app.emit('app.ready')

Et tant qu’on y est, puisqu’on a une event loop, profitons en pour proposer du CRON intégré à l’app. C’est moins chiant à déployer qu’un script CRON, c’est cross plateforme, et on a accès facilement à toute sa stack.

# Lancer du code tous les x temps ou a une date précise
@app.cron(every=seconds)
@app.cron(every=timedelta, overlap=False)
@app.cron(hour=7, minute=30, day_of_week=1)
@app.cron(when=datetime)
def _(ctx, args):
    pass

Pourquoi s’arrêter là ? Event loop + message passing + safe queues + workers = tasks queues !

# Créer une file d'attente
queue = @app.queue('name', [workers], [result_backend])
 
# Callback appelé par un worker quand il depop ce 
# message dans la file
@queue.task('encode.video')
def _(ctx, data):
    pass
 
# Envoie d'une tache dans la queu
queue.append('encode.video', data)

Comme on utilise Twisted, on a accès à une chiée de protocoles, et on peut aussi créer les siens. On peut donc imaginer un système de plugins qui rajoute des protocoles supportés :

app = Application('YourProjectName')
app.plug('lib.ajoutant.sms', [namespace])

Si on en a beaucoup et que le namespace nous convient :

app = Application('YourProjectName', plugins=('lib1', 'lib2', 'etc'))

Exemples de plugins possibles :

# Recevoir et envoyer des SMS (via un service type twilio, une gateway kannel ou
# un modem physique)
@app.sms.receive(r'LOVE \w+ \w+')
def _(ctx, args):
    pass
app.sms.send('test', [contact])
 
 
# Envoyer et recevoir des emails (via un server SMTP ou IMAP)
@app.email.receive(src=r'.*@sametmax.com', dest=r'spam.*@*.')
def _(ctx, args):
    pass
app.email.send('test', [contact, title, attachments])
 
 
# techniquement n'importe quel service de message pour lequel on peut écrire
# un backend
@app.tweet.receive(r'Chat')
@app.fb.receive(r'Like')
@app.instagram.receive(r'Bouffe')
@app.irc.message(r'dtc')
def _(ctx, args):
    pass

Le problème des apps centrées sur un objet, c’est qu’elles ont souvent un design monolithique. Ce n’est pas un problème du concept d’app, c’est juste que les auteurs ont pensé “point d’entrée”, et pas “élément composable”.

Si besoin, on doit pouvoir composer une app via plusieurs sous-app :

app = Application()
app.embed('autre.app')

ou

app = Application(embed=['app1', 'app2', 'app3'])

Il faut des hooks pour overrider la configuration, mais vous avez compris le principe.

Un autre problème avec les plateformes comme NodeJS, c’est qu’il est difficile d’utiliser plusieurs coeurs. C’est une des raisons du succès de Go.

Or, Crossbar encourage la division en plusieurs process qui communiquent entre eux (un peu comme les channels). Créons aussi une API pour ça :

p1 = app.process()
p2 = app.process()
 
# Déclarer et appeler une procédure dans process 1
@p1.wamp.remote('auth.signin')
def _(ctx, args):
    pass
 
# Déclarer et appeler une procédure dans process 2
@p2.wamp.event('auth.signedin')
def _(ctx, args):
    pass

Ainsi on profite enfin de plusieurs CPU. La même chose en plus facile à changer:

# Déclarer et appeler une procédure
@app.wamp.remote('auth.signin')
def _(ctx, args):
    pass
 
# Déclarer et appeler une procédure
@app.wamp.event('auth.signedin')
def _(ctx, args):
    pass
 
app.processes({
    1: ['wamp.remote:auth.signin']
    2: ['wamp.event:auth.signedin']
})

En bonus, on fait la nique au GIL.

Mieux, on peut bouger ses process sur plusieurs machines :

Machine 1 (routeur):

router = Application(endpoint="0.0.0.0:8080")
router.run()

Machine 2 (authentification):

# IP du router
auth = Application('auth', connect_to="182.64.1.15:8080")
 
# Nommage automatique en fonction du nom de la fonction
# et de l'app, avec possibilité d'annuler ou overrider le prefix.
# Ici du coup la fonction s'appellera en RPC via 'auth.signin'
@auth.wamp.remote()
def signin(ctx, args):
    pass
 
auth.run()

Machine 3 (API REST):

web = Application('site', connect_to="182.64.1.15:8080")
 
@web.http.post(r'api/auth/')
def _(req, res):
    user = yield res.wamp.call('auth.signin',
                               req.POST['username'],
                               req.POST['password'])*
    if user
        user = yield res.wamp.pub('auth.signedin', user.userid)
        res.json({'token': user.token})
    else:
        res.json({'error': 'nope'})
 
 
@web.http.get(r'api/stuff/')
def _(req, res):
    res.json(get_stuff())
 
@web.http.serve('uri', '/path/to/dir', [allow_index])
 
web.run()

Et vous savez le plus beau dans tout ça ? En Python on a plein de libs qui sont encore bloquantes. En théorie on ne peut pas les utiliser dans les apps asynchrones. Quand on a toute sa logique métiers dans des classes d’ORM, c’est balot. Mais pas ici ! On met un process avec tous ces appels bloquants, et on les appelle depuis des process non bloquant en RPC de manière asynchrone. Pif, paf, pouf, problème isolé.

Après, libre à son imagination de rajouter des fonctionnalités de confort…

Callback qui sera appelé seulement x fois :

# Déclarer et appeler une procédure
@p1.wamp.event('auth.signedin', options={'limit_calls': x} )
def _(ctx, args):
    pass

Raccourcis pour les opérations courantes :

# Recevoir et envoyer un événement
@app.sub('auth.signin')
def _(ctx, *args):
    # ctx.pub
@app.pub('auth.signedin')
 
# Déclarer et appeler une procédure
@app.proc('auth.signedin')
def _(ctx, args):
    # ctx.call
app.rpc()

Comme je vous l’avais expliqué, crossbar peut gérer le cycle de vie de services externes à votre application au démarrage. Autant exposer cette API programativement :

@app.service(['/urs/bin/nodejs', 'script.js'], [user], [group])

.run(), c’est cool, mais si on veut changer des options via la ligne de commande, faut se taper tout le boulot alors que ça pourrait très bien se générer automatiquement :

@app.cmd_run()

Et si vous faites : python sites.py --debug=true --endpoint=0.0.0.0:5252, ça le prend automatiquement en compte. Y a pas de raison de se faire chier.

En parlant de générer automatiquement des trucs, le fichiers de configs pour les services externes sur lesquels on peut avoir envie de brancher notre app, c’est toujours galère. Autant fournir un exemple de base qui est sûr de toujours marcher, généré avec les paramètres de notre app :

python site.py template centos:nginx
python site.py template ubuntu:upstart
python site.py template bsd:systemd # :D

On peut partir très loin dans le délire “battery included”. Typiquement, on peut fournir des services externes nous même puisque crossbar nous le propose, et coder des versions moins bien, mais compatibles (et suffisantes pour les petits sites), de projets toujours utilses :

On plug tout ça a une admin Web.

J’espère que je vous ai donné maintenant l’envie de vous plonger un peu plus dans cette techno, et peut être coder quelque chose avec.

Il n’y a plus d’excuses pour ne pas avoir de framework web next gen, ultime de la mort qui tue en Python. A part le fait qu’on soit des feignasses.

Ah, merde, on est foutus.

flattr this!

10 raisons égoïstes pour s’investir dans les préliminaires

vendredi 30 mai 2014 à 05:17

Je pense que vous vous êtes TOUS fait rabâcher que les préliminaires c’est important qu’il faut s’occuper de l’autre, le sexe c’est du partage, et gna gna gna, bla bla bla, trolololo lololo lololo.

Le truc c’est que ce genre d’arguments ne va parler qu’à des gens qui sont DÉJÀ de nature à en faire. Prêcher des convertis, c’est bon pour l’égo mais ça construit pas grand chose. C’est pas bon comme Lego. Ok, je => [].

Bref, voici 10 bonnes raisons de faire des préliminaires, parce que ça vous rapporte quelque chose à vous.

1 – On dure plus longtemps

Passé une certaine durée d’érection, le corps s’adapte pour passer en mode endurance. Du coup plus de préliminaires = plus de temps à bander sans trop monter en sauce = plus de temps à bander tout court.

Donc plus de baise. Des performances plus flatteuses. Le temps d’essayer plus de positions.

Et puis plus d’opportunité de la faire jouir, puisque ça prend souvent plus de temps que pour nous. Et franchement, qu’est-ce qu’il y a de plus sexy qu’une meuf à qui on est en train de faire prendre son pied ?

2 – On chope plus

Les meufs parlent entre elles. Si vous êtes un bon coup, ça se saura : un secret, c’est quelque chose que l’on ne répète qu’à une personne à la fois. Du coup, rapidement, les copines des copines (un degré de séparation est nécessaire le plus souvent pour en bénéficier) vous voient comme un partenaire sexuel valable.

3 – On est en meilleure forme

Les préliminaire impliquent souvent de tenir des positions qui font travailler vos muscles, votre cœur, votre respiration. Plus il y a de préliminaires, plus votre séance de gym en l’air devient complète.

Franchement, garder la forme en niquant, y a pire.

4 – On a une plus belle peau

Plus de transpiration et plus de caresses, plus de frictions donc un scrubing + hammam gratos. Votre peau va s’en trouver toute renouvelée.

Or, scoop, la peau est un facteur hyper important de sex appeal. En fait, avoir une belle peau compense beaucoup d’autres problèmes. Par exemple, si vous êtes en surpoids avec un superbe épiderme, vous paraissez en bonne santé et donc baisable, si vous avez une super silhouette et une peau de merde, vous donnez autant d’envie qu’un lépreux.

Désolé pour les gens à peau difficile, je connais ça, j’ai été victime de tout un tas de trucs dermatologiquement inesthétiques.

5 – Plus de chance de pouvoir choisir où vous venez

Si vous aimez lui gicler dans la bouche, le cul ou sur les nichons, avoir bien fait monter en température la demoiselle aide beaucoup. Dans le feu de l’action, on accepte beaucoup plus de choses. La sodomie, douloureuse à froid, peut devenir super excitante quand elle est à fond. Avaler se fera parfois naturellement si vous lui avez léché copieusement la touffe avant. Etc.

6 – Plus de sexe avec la même partenaire

Ça stimule la libido. Et donc si vous avez une fille qui à pas souvent envie, tout d’un coup, paf, elle répond vachement mieux aux sollicitations. Merci capitaine obvious, tu peux refermer ta braguette maintenant.

7 – Plus de jeux pervers

Ça va avec les deux précédents, mais si vous faites frémir votre partenaire, elle sera forcément plus en confiance pour mettre un costume d’écolière et s’empaler sur une tentacule en plastique tout en criant “Yameru, Kyōju-Sama !”.

Il faut dire que le cul, ça peut facilement tourner en rond. Du coup, faire des trucs funs et osés en entrée, ça amène un peu de renouveau.

8 – Plus de temps pour mettre la capote

Franchement, c’est toujours mieux d’enfiler le bout de plastique quand on a sa langue dans sa chatte que de s’arrêter pour ça.

9 – Plus de temps pour voir si c’est une vraie rousse

Je me pose toujours la question.

10 – Un passport pour les quickies

Envie de venir en deux minutes ? Vous pouvez, vous lui avez révolutionné la Sardaigne hier ! C’est le moment de la monter directement sur la table basse, lui baisser le fut de 20 cm, venir, et repartir comme si de rien n’était.

flattr this!

Requests fait partie de la lib standard !

jeudi 29 mai 2014 à 07:07

Enfin, presque :)

J’étais en train d’installer en vieux requirements.txt quand soudain :

  File "
 
/usr/lib/python2.7/dist-packages/pip/download.py", line 23, in <module>
    from requests.adapters import BaseAdapter
ImportError: No module named adapters

Wut ?

Mon erreur était due à une version de requests trop ancienne. J’upgrade et ça remarche. Mais ça m’a intrigué :

⟩ grin "requests"  /usr/lib/python2.7/dist-packages/pip/
/usr/lib/python2.7/dist-packages/pip/download.py:
   22 : import requests, six
   23 : from requests.adapters import BaseAdapter
   24 : from requests.auth import AuthBase, HTTPBasicAuth
   25 : from requests.compat import IncompleteRead
   26 : from requests.exceptions import InvalidURL, ChunkedEncodingError
   27 : from requests.models import Response
   28 : from requests.structures import CaseInsensitiveDict
  120 :         # Store the new username and password to use for future requests
  182 :         # We only work for requests with a host of localhost
  211 : class PipSession(requests.Session):
  548 :         except requests.HTTPError as exc:
/usr/lib/python2.7/dist-packages/pip/index.py:
   17 : import html5lib, requests, pkg_resources
   18 : from requests.exceptions import SSLError
   80 :         # The Session we'll use to make requests
  689 :         except requests.HTTPError as exc:
  692 :         except requests.ConnectionError as exc:
  697 :         except requests.Timeout:

Je regarde le code source github, qui diffère de celui de mon système :

from pip._vendor.requests.adapters import BaseAdapter, HTTPAdapter
from pip._vendor.requests.auth import AuthBase, HTTPBasicAuth
from pip._vendor.requests.compat import IncompleteRead
from pip._vendor.requests.exceptions import ChunkedEncodingError

Mais ça reste bien requests.

Donc, si vous avez la 3.4, vous avez pip installé, qui maintenant embed requests. Donc Python 3.4 vient forcément avec requests \o/

Si vous supportez uniquement cette plateforme, vous pouvez faire :

try:
    import requests
except ImportError:
    import pip._vendor.requests as requests

Et bénéficier de requests sans rien installer.

Tout ça pour ça.

flattr this!

Crossbar, le futur des applications Web Python ?

dimanche 25 mai 2014 à 12:24

Je suis crossbar.io depuis quelques temps maintenant, et je suis très, très étonné de ne pas plus en entendre parler dans la communauté Python.

Bon, en fait, à moitié étonné.

D’un côté, c’est une techno qui, à mon sens, représente ce vers quoi Python doit se diriger pour faire copain-copain avec Go/NodeJs et proposer une “killer feature” dans le monde des applications serveurs complexes.

De l’autre, hum, leur page d’accueil explique à quoi ça sert de cette manière :

Crossbar.io is an application router which implements the Web Application Messaging Protocol (WAMP). WAMP provides asynchronous Remote Procedure Calls and Publish & Subscribe (with WebSocket being one transport option) and allows to connect application components in distributed systems

Moui, moui, moui monseigneur, mais concrètement, là, hein, je peux faire quoi avec ?

C’est toujours le problème avec les gens intelligents (hein Cortex ?) : ils font des trucs super cool, et personne ne comprend à quoi ça sert parce qu’il ne sont pas foutu de l’expliquer.

Moi je suis un peu con, alors je vais profiter qu’on soit tous au même niveau pour vous faire passer le message.

J’étais persuadé d’avoir mis la musique habituel… Je la remet:

Web Application Message Protocol

Je vous avais parlé d’autobahn dernièrement, un client WAMP qui embarque aussi un routeur basique. Crossbar est la partie serveur, un routeur WAMP plus serieux.

Crossbar permet à tous les clients d’échanger des messages WAMP à travers lui. Bien entendu, un client WAMP peut parler au serveur crossbar et inversement comme un client HTTP peut parler à un serveur Apache/Nginx et inversement. Mais plus que ça, les clients peuvent parler entre eux, de manière transparente et simple. Comme un client AMQP peut parler aux autres à travers un serveur RabbitMQ.

Cependant ça ne vous avance pas si vous ne savez pas ce qu’est WAMP ou à quoi ça sert. La charrue avec la peau de l’ours, tout ça.

WAMP est un protocole standardisé pour échanger des messages entre deux systèmes. Ce n’est pas particulièrement lié à Python, on peut parler WAMP dans n’importe quel langage.

Il fonctionne en effet, principalement, au dessus de Websocket, donc on peut l’utiliser directement dans le navigateur, dans Firefox, Chrome, Opera, Safari et même IE10, via une lib Javascript. Qui est en fait juste un gros wrapper autour de l’API websocket standardisant la manière d’envoyer des données. Il n’y a rien de magique derrière, pas de formats compliqués, pas de binaire, c’est vraiment juste des appels websocket contenant des données formatées en JSON avec une certaine convention. En ce sens il fait penser à SockJS et (feu) socket.io.

Seulement contrairement aux solutions type SocketJS, il n’est pas limité au navigateur. Il y a des libs pour l’utiliser dans un code serveur Python, C++ ou NodeJS, dans une app Android et même directement depuis les entrailles de Nginx ou d’une base de données Oracle (en SQL) avec certains routeurs.

Schéma général du fonctionnement de WAMP

Comme HTTP, WAMP est juste un moyen de faire parvenir des données d'un point A à un point B. Mais contrairement à HTTP, WAMP permet aux clients de parler entre eux, et pas juste au serveur.

Comprenez bien, ça veut dire qu’on peut envoyer et recevoir des données arbitraires, en temps réel, entre tous ces systèmes, sans se prendre la tête, et de manière transparente.

WAMP c’est donc comme, mais mieux que :

Bien utilisé, Crossbar permet d’amener Python dans la cours de frameworks “temps réel” novateurs comme MeteorJS, et potentiellement les dépasser.

Car WAMP permet de faire deux choses. Simplement. Et bien.

1 – Du PUB/SUB

Donc de dire dans son code “appelle cette fonction quand cet événement arrive”. C’est comme les signaux de Django ou QT, mais ça marche à travers le réseau. On le fait souvent avec Redis ces temps-ci. Avec WAMP et Javascript, ça donne :

// connection au routeur WAMP (par exemple, crossbar.io)
ab.connect("ws://localhost:9000", function(session) {
    // je m'inscris à un événement
    session.subscribe('un_evenement_qui_vous_plait', function(topic, evt){
        // faire un truc avec les données reçues
        // à chaque fois que l'événement est envoyé
        // par exemple mettre la page à jour
    });
});
Schéma de fonctionnement du subscribe de WAMP

Des client SUBscribe à un événément. Un événément est un nom arbitrairement choisit par le programmeur, et qu'il va déclencher lui-même quand il pense qu'il se passe quelque chose d'important auquel il faut que le reste du signal réagisse.

Et ailleurs, dans une autre partie du fichier, ou même sur un autre navigateur Web :

ab.connect("ws://localhost:9000", function(session) {
    // création d'un événement auquel on attache des données
    session.publish('un_evenement_qui_vous_plait', ['des données']);
Schéma de fonctionnement de PUB avec WAMP

Le programmeur décide que quelque chose d'important arrive (création d'un contenu, login d'un utilisateur, notification), et PUBlish l'événement

Et oui, c’est tout. On se connecte à crossbar, et on discute. La fonction du subscribe sera alors appelée avec les données du publish. Même si il y a 3000 km entre les deux codes. Même si le code A est sur un navigateur et le B sur un autre, ou sur un serveur NodeJS, ou une app Android.

Ce qui fait peur au début, c’est qu’il y a TROP de flexibilité :

Mais en fait c’est super simple : un événement c’est juste une action de votre application comme un élément (un post, un commentaire, un utilisateur…) ajouté, supprimé ou modifié. Finalement c’est le bon vieux CRUD, mais en temps réel, et en push, au lieu du pull. Vous choisissez un nom qui représente cette action, vous attachez des données à ce nom, voilà, c’est un événement que tous les abonnés peuvent recevoir.

Avec un bonus : ça marche sur le serveur aussi ! Votre code Python reçoit “ajouter un commentaire” comme événement ? Il peut ajouter le commentaire en base de données, envoyer un message à un service de cache ou à un autre site en NodeJS pour le mettre à jour, renvoyer un événement pour mettre à jour les pages Web et l’app Android, etc.

On peut passer n’importe quelles données qui peut se JSONiser. En gros n’importe quoi qu’on enverrait via HTTP. Donc des données très structurées, imbriquées et complexes comme des données géographiques, ou très simples comme des notifications.

Avec PUB / SUB, WAMP remplace tout ce qu’on ferait normalement avec des appels AJAX dans le browser, et tout ce qu’on ferait avec des files de message côté serveur. Plus puissant encore, il permet de relier ces deux mondes.

Et même si on atteint pas les perfs de ZeroMQ (qui n’a pas de serveur central), c’est très performant et léger.

2 – Du RPC

Appeler une fonction située ailleurs que dans son code. C’est vieux comme le monde (si vous avez des souvenirs douloureux de CORBA et SOAP, levez la main), et c’est extrêmement pratique. Pour faire simple, continuons avec un exemple en Javascript, mais rappelez-vous que ça marche pareil en C++ ou Python :

ab.connect("ws://localhost:9000", function(session) {
   function une_fonction(a, b) {
      return a + b;
   }
   // on déclare que cette fonction est appelable à distance
   session.register('une_fonction', une_fonction);
});
Schéma expliquant register avec WAMP

RPC marche à l'envers de PUB/SUB. Un client expose du code, et un autre demande explicitement qu'il soit exécuté.

Côté appelant :

ab.connect("ws://localhost:9000", function(session) {
    // on appelle la fonction à distance, on récupère une
    // promise qui nous permet de travailler sur le résultat
   session.call('une_fonction', 2, 3).then(
      function (res) {
         console.log(res);
      }
   );
Schéma expliquant CALL en WAMP

Contrairement à PUB/SUB, RPC ne concerne que deux clients à la fois. Mais ça reste asynchrone. Le client demandeur n'attend pas le résultat de l'appel de la fonction. Il est signalé par le serveur quand il est prêt.

Pareil que pour le PUB/SUB, les gens ont du mal à voir l’utilité à cause du trop de flexibilité que ça apporte. Imaginez que votre projet soit maintenant éclaté en de nombreux petits services qui tournent et qui sont indépendants :

Tous ces services peuvent ainsi communiquer entre eux via RPC, mais n’ont pas besoin d’être dans le même processus. On peut profiter pleinement de tous les cœurs de sa machine, on peut même les mettre sur des serveurs séparés.

Mieux, avoir un service bloquant ne pénalise pas tout le système. En effet, un problème avec les systèmes asynchrones en Python est que beaucoup de libs sont encore bloquantes (typiquement les ORMs). Avec ce genre d’architecture, on peut créer un service qui ne fait que les appels bloquant et laisser les autres services non bloquant l’appeler de manière asynchrone. Pendant qu’il bloque, le reste du système peut traiter d’autres requêtes.

Crossbar, plus qu’un routeur WAMP

L’idée des concepteurs de crossbar est de permettre de créer des systèmes avec des services composables qui communiquent entre eux plutôt que tout dans un gros processus central. Ils ne se sont donc pas arrêtés au routing.

Crossar est également un gestionnaire de processus, comme supervisor ou, plus légitimement, circus (Tarek, fait une pause, vient ici !) et sa communication ZeroMQ.

Il se configure avec un simple fichier JSON, et on peut y définir des classes Python qui seront lancées dans un processus séparé et pourront discuter avec les autres clients via WAMP :

{
   "processes": [
      { // premier processus
         "type": "worker",
         "modules": [
            {
               un_worker.Classe
            },
            {
               un_autre_worker.Classe
            }
         ]
      },
      {  // second processus
         "type": "worker",
         "modules": [
            {
               un_autre_worker_dans_un_autre_process.Classe
            }
         ]
      }
   ]
}

Mais si ça ne suffit pas, on peut également lancer des programmes extérieurs non Python dont crossbar va gérer le cycle de vie :

{
   "processes": [
      {
         "type": "guest",
         "executable": "/usr/bin/node",
         "arguments": ["votre_script.js"],
         "stdout": "log"
      }
   ]
}

Vous avez donc ainsi les deux atouts pour avoir une architecture découplée, scalable, exploitant plusieurs cœurs, et compensant en partie les bibliothèques blocantes :

Cas concret

WAMP est typiquement le genre de techno qui ne permet PAS de faire quelque chose qu’on ne faisait pas avant. Ce n’est pas nouveau.

En revanche, WAMP permet de le faire mieux et plus facilement.

Prenez le cas d’un utilisateur qui se connecte sur un forum. Il va sur un formulaire, il post ses identifiants, ça recharche la page, il est connecté. Si les autres utilisateurs rechargent leurs pages, ils verront un utilisateur de plus connecté.

Si on veut rendre ça plus dynamique, il faut utiliser de l’AJAX, et si on veut avoir une mise à jour presque en temps réel, il faut faire des requêtes Ajax régulière. Ce qui est assez bancal et demande beaucoup de travail manuel.

Certains sites modernes utilisent Websocket, et des serveurs asyncrones comme NodeJS, et un routeur PUB/SUB comme redis, pour faire cela de manière rapide et plus facile. L’application est très réactive. Mais le système est hétéroclite. Et si on veut envoyer des messages entre des composants serveurs, ça demande encore quelque chose de différent.

WAMP unifie tout ça. Un coup de RPC pour le login pour effectuer l’action:

Schéma d'un exemple concret de RPC WAMP

Notez que le RPC marche de n'importe quel client à n'importe quel client. Il n'y a pas de sens obligatoire. Le login est un exemple simple mais on peut faire des choses bien plus complexes.

Et un coup de PUB/SUB pour prévenir tout le monde que quelque chose s’est passé :

Schéma d'exemple concret de PUB/SUB avec WAMP

Je n'ai mis que des clients légers ici, mais je vous rappelle qu'un client peut être un serveur NodeJS, une base de données, un code C++...

Bien entendu, on pourrait faire ça avec les technos existantes. C’est juste moins pratique.

Notez également que Crossbar encourage à avoir un service qui ne se charge que du login, sans avoir à faire une usine à gaz pour cela. Si demain votre service de login a besoin d’être sur un coeur/serveur/une VM séparé pour des raisons de perfs ou de sécurité, c’est possible. Crossbar encourage ce genre de design.

Voici où est le piège

Car évidement, il y en a toujours un, putain de métier à la con.

Et c’est la jeunesse du projet.

Le projet est stable, le code marche, et les sources sont propres.

Mais la doc, mon dieu la doc… Les exemples sont pas à jour, il y a deux versions qui se battent en duel, on sait pas trop quelle partie sert à quoi.

Et comme tout projet jeune, l’API n’a pas été assez étudiée. Or la partie Python est basée sur Twisted, sans polish. Twisted, c’est puissant, c’est solide, et c’est aussi une API dégueulasse.

Un exemple ? Comment écouter un événement :

# Des imports légers
from twisted.python import log
from twisted.internet.defer import inlineCallbacks
 
from autobahn.twisted.wamp import ApplicationSession
from autobahn.twisted.wamp import ApplicationRunner
 
# Une bonne classe bien subtile pour copier Java
class ListenForEvent(ApplicationSession):
 
    # Deux méthodes de boiler plate obligatoires
    # et parfaitement soulantes pour l'utilisateur
    # final. Cachez moi ça bordel !
    def __init__(self, config):
        ApplicationSession.__init__(self)
        self.config = config
 
    def onConnect(self):
        self.join(self.config.realm)
 
    # Bon, on se décide, soit on fait une classe avec des noms
    # de méthode conventionnés, soit on met des décorateurs, 
    # mais pas les deux, pitié !
    @inlineCallbacks
    def onJoin(self, details):
        callback = lambda x: log.msg("Received event %s" % x)
        yield self.subscribe(callback, 'un_evenement')
 
# Python doit lancer explicitement un event loop.
# Ca pourrait (devrait) aussi être embeded dans une
# sousclasse de ApplicationSession.
# /me prend un colt. BANG !
if __name__ == '__main__':
   runner = ApplicationRunner(endpoint="tcp:127.0.0.1:8080",
                              url="ws://localhost:8080/ws",
                              realm="realm1")
   runner.run(ListenForEvent)

C’est la raison pour laquelle je vous ai montré le code JS et pas Python pour vous vendre le truc. Sur sametmax.com, on aura tout vu :(

Voilà à quoi devrait ressembler le code Python si l’API était mature :

from autobahn.app import App
 
app = App(url="ws://localhost:8080/ws")
 
@event("un_evenement")
def handle(details):
    app.log("Received event %s" % x)
 
if __name__ == '__main__':
   app.run()

Chose que je vais proposer sur la mailing list (ils sont réactifs et sympas, vive les allemands !) dans pas longtemps. Et si ils n’ont pas le temps de le faire, il est possible que je m’y colle. Ca me fait mal aux yeux.

flattr this!