WAMP et les outils de dev Web Python existants


Même si on peut créer un site Web en utilisant uniquement des libs WAMP, tout comme on peut le faire en utilisant uniquement flask ou tornado, il arrive immanquablement le moment où on veut mélanger, intégrer, faire cohabiter les techos ensemble. D’abord parce qu’il y a des sites existants, et qu’on va pas les jeter à la poubelle. Ensuite parce que ce sont des techos qu’on connaît et pour lesquelles ils y a beaucoup d’outils, que l’on veut mettre à profit.

C’est tout naturellement qu’on a fini par me poser (par mail), ze question :

Subject: crossabar et autobahn

Message Body:
Ha bah oui vous l’avez cherché à nous parler de truc comme ça: ça interroge !

Je m’interroge donc sur la manière d’intégrer WAMP à django. Pour la perstistence des données (l’ORM qui simplifie la création des tables tout de même), pour l’authentication et l’authorization, pour la robustesse et versatilité apportée par django…

J’ai pour habitude de mettre pas mal de logique dans mes modèles et je me demandais si il n’y aurait pas moyen de pluguer WAMP dans ceux ci… exposer une méthode update en RPC avec passage de JSON ? Avec namespace automatique à partir de la classe ?

En fait remplacer: Angular+tastypie+django+nginx par Angular+WAMP+django+crossbar avec du coup le bonus de WAMP pour le pubsub que n’a pas AJAX.

Comment vous verriez un (petit – après relecture ça parait dur) mixin WAMP pour des modèles django auto-détectés, auto-exposés en RPC ?

J’ai du mal à voir quelle tactique utiliser. J’ai peur que ça finisse en refaire tout le travail que fait déjà (très bien) tastypie/RESTframework.

Comment modulariser (là ou est sensé briller WAMP) les services déjà offerts par django: celery, authentication, etc ?
Ces services sont tous très dépendant du système de persistance des données (check des users, permissions, query) et donc l’approche plus monolithique de django n’est pas mauvaise car tout est lié et donc facilement manipulable au même endroit… où est le gain de WAMP dans ce cas, pour une application assez classique en fait ?

Merci encore pour cette découverte dans tous les cas. C’est top.

Du coup je me relis et ça part un peu dans tous les sens… désolé.

Comme je me suis fendu d’une réponse bien longue, je vais la paster verbatim :

Bonjour,

C’est une très bonne question.

D’abord, il faut savoir qu’on ne peut pas faire tourner un routeur WAMP dans un process Django (ou tout autre app WSGI) car Django est synchrone. En plus, l’ORM de django est bloquant, donc même sans utiliser django, utiliser son ORM au sein de WAMP va bloquer la boucle d’événements et on perdra tout l’interêt d’avoir une techno temps réel.

(Note a posteriori : y a surement un truc à faire avec gevent ou des threads ici, mais je sais pas encore quoi)

Ici on a donc 3 problèmes à résoudre :

– comment faire communiquer django et son app WAMP ?
– comment utiliser un ORM bloquant avec WAMP ?
– comment auto générer une API WAMP ?

Ces 3 questions n’ont pas encore de réponse définitive puisque, comme je l’ai précisé, WAMP est une techno jeune, et donc il y a beaucoup à faire. Mes articles sont précisément là pour tenter de générer un enthousiasme et pousser les gens à améliorer les outils autour de WAMP.

Prenons les problèmes un par un :

Comment faire communiquer django et son app WAMP ?
====================================

C’est le problème le plus facile à mon sens. Il faut coder une app WAMP qui fasse le bridge entre HTTP et WAMP. Quand on register côté app HTTP, on fait un post sur l’app WAMP (qui écoute aussi sur HTTP du coup) en fournissant une URL de callback. L’app WAMP fait le register, et quand on l’appelle, elle fait l’appel à l’app HTTP via l’url de callback, et retourne le résultat. On peut faire ça pour register, subscribe, call et publish, c’est le même principe.

(Note a posteriori : en me relisant moi-même je m’aperçois à quel point c’est pas clair. De toute façon il faudra que je le code un jour où l’autre, et avec un bon tuto pratique, la pilule passera mieux).

Ce faisant, on pourra appeler du WAMP côté app HTTP, et taper dans l’app HTTP côté client WAMP.

Une amorce de travail a été fait pour coder un tel bridge. Pour le moment il n’y a que le publish :

https://groups.google.com/forum/#!searchin/autobahnws/http/autobahnws/SbobAnoWVlQ/FnGhdYXj9aIJ

Ce n’est pas très dur à coder, c’est juste un boulot chiant à faire.

Cela dit, ça ne résout pas le problème de l’authentification, qu’il faudra à un moment on un autre, se poser. Je pense qu’on va se diriger vers une authentification hybride, qui va utiliser le session ID en cookie, mais l’envoyer via un token. Encore un truc à travailler.

De même, on voudra sûrement créer quelques facilités pour intégrer ça dans les frameworks les plus connus en proposant une app prêt à plugger. Rien d’insurmontable donc, mais pas mal de taff.

Par contre, pour ce qui est des tasks queues, à mon avis une solution de task queue WAMP sera bien plus intéressante qu’une solution type celery car on peut envoyer des messages WAMP depuis les tâches et donc avertir en temps réel de l’avancement du process. Je voterais donc pour coder soi-même une alternative.

Comment utiliser un ORM bloquant avec WAMP ?
===============================

Idéalement, il faudrait avoir des ORM non bloquant, mais on Python, on en a pas. On a quelques drivers non bloquant, notamment pour PostGres et Mongo, mais pas d’ORM, et ils demandent une forme de compilation d’extension C.

C’est là qu’on voit qu’on se traine la culture de l’API synchrone en Python, car côté NodeJS, ils commencent à avoir pas mal de solutions.

En l’occurrence, on a 3 solutions :

– utiliser le bridge dont je viens de parler pour garder les appels dans l’app HTTP. Ca veut dire que quand on veut faire un appel à de la base de données, ça fait WAMP => HTTP => connexion à la base, aller-retour. C’est pas idéal.
– créer une app WAMP pour héberger les appels bloquants et taper dedans en RPC. Une bonne solution à mon avis. Mais assez peu intuitive.
– faire tous les appels dans un threads à part. Le plus simple. Un peu verbeux par contre.

Dans les deux derniers cas, on à quand même le problème des querysets qui sont lazy, notamment au niveau des foreign keys. Il faudra faire particulièrement attention à ne pas accidentellement faire des appels bloquant, par exemple dans le rendu du template. Une solution viable est de créer un wrapper qui fait le rendu du template dans un threads.

Bref, encore pas mal d’outils à developper.

On peut aussi se lancer dans l’écriture d’un ORM non bloquant. Une bonne année de travail avant d’avoir quelque chose qui soit compétitif.

Comment auto générer une API WAMP ?
==========================

Là tu m’en poses une bonne.

C’est la suite logique, évidement, mais je n’avais jamais réfléchi aussi loin. C’est un taff énorme, surtout que ça dépend de l’outil derrière. La solution la plus simple c’est encore de faire un mapper dans le bridge HTTP-WAMP qui va traduire directement un appel WAMP en un appel JSON vers l’API générée par django-rest-framework ou autre.

Mais bon, je suis pas certains de la valeur ajoutée.

Je pense qu’il est difficile pour moi de répondre à cette question pour le moment car :

– je ne suis pas certain que WAMP soit un bon remplacement pour les API REST. Je pense plutôt que c’est un complément.
– il y a toute la question de l’authentification. Encore et toujours.
– il va falloir pas mal d’essais avec plusieurs architectures en prod (séparées, mixtes, mono culture…) pour pouvoir déterminer ce qui rend le mieux.

Mon intuition est qu’on utilise généralement 10% de l’API générée par les frameworks, et que la partie dont on a besoin à peut très bien se faire à la main. La raison pour laquelle les trucs comme django-rest-framework sont si pratiques, c’est qu’ils gèrent des problématiques comme l’authentification, la sérialisation et la pagination.

Je serais plutôt d’avis de s’attaquer à ça pour WAMP, et je pense qu’on s’apercevra que finalement, pour ses propres besoins, un API complète est overkill. Par contre, pour exposer une API au monde, c’est une autre histoire. J’ai eu récemment une discussion à propos de faire des APIs WAMP :) Il y a des possibilités fascinantes. Mais c’est peut être encore un peu loin tout ça.

Je pense que je vais publier cette réponse sur le blog, car tu soulèves des points très importants.

8 thoughts on “WAMP et les outils de dev Web Python existants

  • manatlan

    Merger un server Wamp (sur 8080) + WSGI (sur 8000)


    from autobahn.twisted.wamp import Application

    wamp = Application('demo')

    @wamp.register()
    def toto(param):
    return "hello world "+param

    #========================================================
    from bottle import request, Bottle, abort, static_file
    app = Bottle()

    @app.get("/")
    def hello():
    return """hello"""

    @app.route('/:path')
    def static(path):
    return static_file(path, root='.')

    from twisted.web.server import Site
    from twisted.web.wsgi import WSGIResource
    from twisted.internet import reactor
    reactor.listenTCP(8000,Site(WSGIResource(reactor,reactor.getThreadPool(),app)))
    wamp.run()

    Mais, comme dit, faut pas faire du bloquant, côté WSGI …

  • manatlan

    A noter aussi, l’existence de gevent-websocket.
    Qui visiblement implémente (je ne sais pas dans quelle mesure) WAMP !

    J’avais tester de mélanger bottle+gevent+gevent-websocket mais je ne suis pas arrivé a qqchose de satisfaisant ;-(

  • Sam Post author

    Ouh, WAMP via gevent ! Faut voir si c’est WAMP 1 ou 2, mais c’est intéressant.

  • Jean-Baptiste

    Hi, i asked the question.
    Telegraphy is actually the kind of thing I was thinking of.
    To be honest: I had to read many times your answer (and read more about twisted) to start to understand something :)
    Just one more question: for a task queue using WAMP each task would be an independant Component ? But for a task using django models how to import them, query on them ?
    I don’t get how everything can fit together smoothly.
    Maybe I expect to much for now. Django + some RT events will be sufficient anyway in 99% of my cases.

  • Sam Post author

    Pour la task queue: non. Seule la task queu est un composant à part, les taches seraint justes des fonctions normales. Le principe serait le même que celery : un service qui spaw des workers et les workers qui depopent les files de tâche. La différence, c’est que le service principal et les workers sont tous des clients WAMPs qui peuvent donc utiliser PUB/SUB et RPC.

    Dans le cadre des workers, ce n’est pas grave de bloquer les tâches car ils ne doivent, de toute façon, traiter qu’une chose à la fois puisque ce sont des files d’attente.

    Il est facile d’utiliser l’ORM de django en dehors de django, il suffit de faire :

    from django.conf import settings
     
    settings.configure(la config de base de données)
     
    from ton_app.models import TonModel

    Et ça marche tout seul, à condition que ton PYTHON PATH soit ok.

    Mais oui, tu as raison, dans beaucoup de cas on peut se contenter du push.

    Dans tous les cas, ça ne peut pas encore “fitter smoothly together” parce que :

    – WAMP est très jeune. On a besoin de vous pour dev l’écosystème. Viendez :)
    – ce sont des technos fondamentalement incompatibles par nature. On essaye donc de faire rentrer un carré dans un trou rond. On va limer les bords avec le temps, mais ça ne se fera pas du jour au lendemain.

  • Seb

    Pas évident de tout saisir mais on tâte le gros potentiel derrière cette techno !
    En parlant de faire rentrer un carré dans un trou rond: j’ai une application très monolithique “de base” bien synchrone et j’aimerai en extraire certains services pour les rendre accessibles à tout moment pour n’importe qui (mon appli originale évidemment mais aussi d’autres applis web).
    Exposer un service en utilisant autobahn/crossbar est faisable assez facilement à mon avis (j’aime bien autobahn.twisted.wamp.Application et les décos @app.register() .signal()…) mais câbler mon vieux bousin dessus est une autre histoire (modèle synchrone VS asynchrone d’autobahn) => je viens de tomber sur crochet (https://crochet.readthedocs.org/en/latest/) => est-ce une solution ?
    En attendant, je vais creuser le sujet…

  • Sam Post author

    C’est une solution. Je ne l’ai jamais testé en prod, donc je suis très curieux de savoir si ça tient.

Comments are closed.

Des questions Python sans rapport avec l'article ? Posez-les sur IndexError.