Bridge HTTP/WAMP


Il y a 4 gros freins à l’adoption de WAMP :

  1. L’incompréhension de la techno. Les gens ne voient pas à quoi ça sert, ce qu’ils peuvent faire avec, et ont peur. Logique. Je vais pas les blâmer, c’est pareil avec toute nouvelle techo. S’investir dans un nouveau truc a un coût, surtout un truc jeune.
  2. La doc. je vais travailler sur la question, mais c’est un travail de fond.
  3. L’API : ça va avec les deux points plus haut. Il faut quelque chose de plus haut niveau. L’API flaskesque est un bon début, il faut maintenant continuer dans ce sens.
  4. L’intégration avec les anciennes technos. Par exemple, un site Django. Personne n’a envie de mettre toute son ancienne stack à la poubelle.

Toute les libs qui introduisent une nouvelle façon de travailler rencontrent ce problème. Quand les ORM sont sortis, c’était pareil. Quand les Django et Rails, et Symfony sont sortis, c’était pareil.

Mais puisqu’on le sait, on peut agir.

Les 3 premiers points ont déjà un début de solution, ce qui nous intéresse c’est donc le 4ème point.

Il y a de nombreuses choses à faire pour l’intégration : authentification, actions bloquantes, communications…

On ne peut pas tout résoudre d’un coup, mais une solution qui ratisse large serait de créer un bridge HTTP/WAMP.

Le principe : faire un client WAMP qui soit aussi client HTTP avec une API REST.

Fonctionnement

En envoyant des requêtes HTTP avec un webtoken pour s’authentifier, on peut faire un register/subscribe/call/publish sur le bridge en spécifiant une URL de callback. Le bridge transmet tout ça à routeur WAMP. Quand un événement arrive sur le bridge qui concerne une des URLs de callback, il fait une requête sur l’URL avec les infos arrivées via WAMP.

API :

POST /register/

{
    // token d'authentification
    token: "fdjsklfqsdjm",
    // On enregistre des urls de callbacks pour chaque "function" exposées en RPC
    // Quand le bridge reçoit un appel RPC via WAMP, il fera une requête POST
    // sur la bonne URL. Votre app récupère les données via POST, et retourne
    // du JSON que le bridge va transmettre via WAMP.
    endpoints: {
        "nom_fonction_1":  "http://localhost:8080/wamp/rpc/nom_fonction_1/",
        "nom_fonction_2":  "http://localhost:8080/wamp/rpc/nom_fonction_2/"
    }
}

POST /subscribe/

{
    token: "fdjsklfqsdjm",
    // On enregistre des urls de callbacks chaque abonnement à un topic.
    // Quand le bridge reçoit un message PUB via WAMP, il fera une requête POST
    // sur la bonne URL. Votre app récupère les données via POST.
    endpoints: {
        "nom_fonction_1":  "http://localhost:8080/wamp/pub/nom_fonction_1/",
        "nom_fonction_2":  "http://localhost:8080/wamp/pub/nom_fonction_2/"
    }
}

POST /call/nom_fonction/

{
    token: "fdjsklfqsdjm",
    // Le bridge fera l'appel RPC, récupère la valeur de retour, et la renvoie
    // à cette URL via POST ou une erreur 500 en cas d'exception.
    callback: "http://localhost:8080/wamp/callback/nom_fonction",
    // Les params à passer à l'appel RPC
    params: ['param1', 'param2']
}

POST /publish/nom_sujet/

{
    token: "fdjsklfqsdjm",
    // Le bridge fera la publication WAMP
    // Les params à passer lors de la publication
    params: ['param1', 'param2']
}

On peut rajouter des fioritures : recharger le fichier de config qui contient les API keys, se désabonner, désinscrire un callback RPC, etc.

Bien entendu, du fait d’avoir un intermédiaire, c’est peu performant, mais suffisant pour permettre une communication entre des apps existantes et vos apps WAMP et mettre un pied dedans.

Intégration poussée

Ceci permet une intégration générique, de telle sorte que tout le monde puisse intégrer son app facilement avec quelques requêtes HTTP. Néanmoins, avoir un plugin pour son framework plug and play faciliterait la vie de beaucoup de gens.

Donc la partie 2, c’est de faire des apps pour les frameworks les plus courants qui wrappent tout ça.

Par exemple, pour Django, ça permettrait de faire :

settings.py

WAMP_BRIDGE_URL = "http://localhost:8181/"

urls.py

from wamp_bridge.adapters.django import dispatcher
 
urlpatterns += ('',
    url('/wamp/, dispatcher),
    ...
)

views.py

from wamp_bridge.adapters.django import publish, call, rpc, sub
 
@rpc()
def nom_fonction_1(val1, val2):
    # faire un truc
 
@sub()
def nom_topic_1(val1, val2):
    # faire un truc
 
def vue_normale(request):
    publish('sujet', ['arg1'])
    resultat = call('function', ['arg1'])

Ca répond pas à des questions du genre : “comment je fais pour garder mon authentification Django” mais c’est déjà super glucose.

Implémentation

On peut créer un bridge dans plein de langages, mais je pense que Python est le plus adapté.

Les clients JS utiliseraientt de toute façon nodeJS, qui n’a pas besoin de bridge, puisque déjà asynchrone. Un routeur en C demanderait de la compilation. PHP est trop moche. Java, c’est tout un bordel à setuper à chaque fois. C#, si il faut se taper Mono sous Linux…

En prime, le routeur crossbar est déjà en Python, donc a déjà tout sous la main pour le faire. On peut même penser à l’intégrer à crossbar plus tard histoire que ce soit batteries included.

Il faut une implémentation Python 2 et Python 3, donc une avec Twisted, et une avec asyncio.

Pour le client twisted, on peut fusionner le client WAMP ordinaire avec treq.

Pour asyncio, il y a aiohttp qui fait client et serveur.

On met les clés API dans un fichier de conf ou une variable d’env, une auth via token, et yala.

C’est le genre de projet intéressant car pas trop gros, mais suffisamment complexe pour être un challenge surtout qu’il faudra des tests unitaires partout, de la doc, bref un truc propre.

Je laisse les specs là, des fois qu’il y ait quelqu’un qui ait des envies de code cet hiver et cherche un projet open source dans lequel se lancer.

Si on se sent un peut foufou, on peut même transformer ça en bridget HTTP <=> anything, avec des backends pour ce qu’on veut : IRC, XMPP, Redis, Trigger Happy…

5 thoughts on “Bridge HTTP/WAMP

  • Romain

    Salut et merci pour toutes ces infos sur WAMP. La stack à l’air bien cool. Toutefois, un truc me chiffonne, c’est que ça repose sur websocket qui propose en gros de faire de la communication “temps réel” sur HTTP qui fonctionne en requête / réponse. Alors, effectivement, c’est sympa de pousser des notifications au browser mais y a-t-il une réel plus value à généraliser pour tout ce qui est communication inter process ? Est-ce qu’on n’a pas tendance à se servir d’HTTP pour tout alors qu’il y a peut-être des protocoles plus mûrs / stables (je pense à AMQP ?).

    Je me réponds (partiellement) : la plus value est de brancher directement le browser sur une file de message sans passé par un serveur intermédiaire ce qui va bien dans le sens de micro service.

    Donc dans le premier frein à l’adoption, ce que je vois c’est le sentiment d’avoir une nouvelle technos pour la communication entre processus en plus des zillons de solutions existantes, qui plus est basée sur HTTP qui n’est pas forcément pensé pour à la base.

    A toi de de me dire si je dis de la merde ou pas :)

  • Sam Post author

    Salut et merci pour toutes ces infos sur WAMP. La stack à l’air bien cool. Toutefois, un truc me chiffonne, c’est que ça repose sur > websocket qui propose en gros de faire de la communication “temps réel” sur HTTP qui fonctionne en requête / réponse.

    Websocket n’est pas HTTP

    C’est un protocole distinct, fait pour le temps réel. Il va falloir que je fasse un article là dessus car je m’aperçois que beaucoup de gens ne savent pas ce qu’est Websocket. C’est dommage car c’est une très belle techno. Si on a la possibilité de l’utiliser (IE10 ou plus ou un shym flash), il ne faut pas hésiter. C’est beaucoup plus rapide, léger et efficace qu’AJAX car ça ne marche pas du tout pareil.

    Alors, effectivement, c’est sympa de pousser des notifications au browser mais y a-t-il une réel plus value à généraliser pour tout ce qui > est communication inter process ? Est-ce qu’on n’a pas tendance à se servir d’HTTP pour tout alors qu’il y a peut-être des protocoles plus mûrs / stables (je pense à AMQP ?).

    Websocket est fait pour ça. Ce n’est pas un hack, un détournement de la techno. C’est fait exprès pour une fois pour toute casser cette distinction communication client/server/process qui n’a lieu d’être qu’à cause des barrières technologiques.

    Je me réponds (partiellement) : la plus value est de brancher directement le browser sur une file de message sans passé par un serveur intermédiaire ce qui va bien dans le sens de micro service.

    On passe bien par un serveur intermédiaire. WAMP à besoin d’un routeur. Si tu veux du P2P, il faut utiliser WebRTC. Mais même là il y a un serveur pour le signaling. Le vrai P2P, c’est malheureusement pas pour demain. D’ailleurs, ça me fait bien chier que les browsers intègres par le protocole torrent par défaut. Il a fait ses preuves.

    Donc dans le premier frein à l’adoption, ce que je vois c’est le sentiment d’avoir une nouvelle technos pour la communication entre processus en plus des zillons de solutions existantes, qui plus est basée sur HTTP qui n’est pas forcément pensé pour à la base.

    A toi de de me dire si je dis de la merde ou pas :)

    Je le formulerai pas ainsi. Disons que ça mérite une complément d’informations ^^ Je vais écrire ça demain.

    • Romain

      Damned, j’ai fait une confusion TCP + port 80 = “websocket c’est sur HTTP”. Les url en ws:// auraient dû me mettre la puce à l’oreille.

      En attendant l’article sur S&M, faire un tour sur wikipedia c’est déjà pas mal :).

      Merci pour la précision. J’ai des choses à apprendre, c’est bien.

  • Tobias Oberstein

    Hi Sam,

    first, I think your list of blockers to WAMP adoption nails it. We simply need to get much better on these issues.

    Rgd 4):

    The “publisher” role of such a bridge is already implemented:

    https://github.com/crossbario/crossbar/wiki/HTTP%20Pusher%20Service

    This can be used to publish WAMP real-time events from plain Django or whatevers apps. It also supports “signed” requests.

    It differs in 2 aspects from the proposal above: a) the WAMP topic is in the POST body (and hence will be signed too with signed requests), and b) the signing works with a simple, secure scheme, not JSON token

    Rgd. 3):

    Why is the call result transmitted by invoking a callback? Why not simply return the result as part of the initial HTTP/POST?

    That would essentially make the bridge API of 3) very similar to 4).

    ===

    Rgd. 1) + 2) – that is REGISTER and SUBSCRIBE.

    Yes, here we need definitely REST callbacks.

    One problem: when a WAMP client that has registered/subscribed just dies, the router will notice and automatically unregister/unsubscribe.

    When a legacy app using the REST bridge dies, this will not be noticed by the router. It will only be noticed when the router would try to invoke a REST callback on the legacy app to invoke a procedure or deliver an event.

  • Sam Post author

    Hi Tobias,

    Why is the call result transmitted by invoking a callback?

    Because if the call is very long, it will block the wsgi process. If you call an external service to encode a video, you don’t want to wait for the whole video to be encoded to process the next HTTP request.

    One problem: when a WAMP client that has registered/subscribed just dies, the router will notice and automatically unregister/unsubscribe.

    No solution is perfect, if people want a perfect fit, they shall use autobahn :)

Comments are closed.

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