Autour du codeDevelopper toujours mieux
Posté le , mis à jour le

Creation de service avec Crossbar.io

Présentation

Crossbar.io est une plateforme qui va nous permettre de développer des microservice qui communiqueront entre eux en utilisant le protocol Web Application Messaging Protocol (WAMP) basé sur les websocket. Comme avec Nameko on va pouvoir faire du pub/sub et du RPC.

Mise en place d'un router crossbar.io

Pour faire des testes en local, le plus simple reste d'utiliser un container.

docker run -it -p 8080:8080 crossbario/crossbar

J'ai quand même voulu voir ce que ça donnait sur une VM. Voici comment j'ai installer crossbar.io sur une CentOS-8

yum install gcc gcc-c++ make openssl-devel libffi-devel python3 python3-devel
yum install --enablerepo=PowerTools snappy-devel
python3 -m ensurepip
pip3 install crossbar

Vérifier que l'installation est OK.

crossbar version

Ouvrir le port par defaut de crossbar.io

firewall-cmd --zone=public --permanent --add-port=8080/tcp
systemctl stop firewalld.service
systemctl start firewalld.service

Configurer crossbar.io

On va d'abort générer un fichier de config en utilisant la commande:

crossbar init

On va ensuite éditer le fichier .crossbar/config.json

Ajout de realms

Les realms sont des espaces de nom dans lesquels ont pourra s'abonner à une files d'écoute de message ou appeler une fonction distante.

{
"realms": [
    {
        "name": "realm1",
        "roles": [
            {
                "name": "frontend",
                "permissions": [
                    {
                        "uri": "",
                        "match": "prefix",
                        "allow": {
                            "call": true,
                            "register": false,
                            "publish": false,
                            "subscribe": true
                        },
                        "disclose": {
                            "caller": false,
                            "publisher": false
                        },
                        "cache": true
                    }
                ]
            },
            {
                "name": "backend",
                "permissions": [
                    {
                        "uri": "",
                        "match": "prefix",
                        "allow": {
                            "call": false,
                            "register": true,
                            "publish": true,
                            "subscribe": false
                        },
                        "disclose": {
                            "caller": false,
                            "publisher": false
                        },
                        "cache": true
                    }
                ]
            }
        ]
    },
}

Ajout d'utilisateurs

On va ajouter une section auth and transports.paths.ws qui condiendra deux utilisateurs, frontend et backend ayant chaqu'un un role du même nom. ticket contient le mot de passe des utilisateurs.

{
"transports": [
    {
        "type": "web",
        "endpoint": {
            "type": "tcp",
            "port": 8080,
            "backlog": 1024
        },
        "paths": {
            "ws": {
                "type": "websocket",
                "serializers": [
                    "cbor", "msgpack", "json"
                ],
                "auth": {
                    "ticket": {
                        "type": "static",
                        "principals": {
                            "frontend": {
                                "ticket": "****",
                                "role": "frontend"
                            },
                            "backend": {
                                "ticket": "****",
                                "role": "backend"
                            }
                        }
                    }
                },
            },
        }
    }

On peut vérifier que notre configuration est fonctionnel en utilisant la command:

crossbar check

Création de micro-services

Exemple de RPC

import asyncio
from autobahn.asyncio.wamp import ApplicationSession, ApplicationRunner

URL = 'ws://127.0.0.1:8080/ws'
REALM = 'realm1'
TICKET = '****'
PRINCIPAL = 'frontend'

class Component(ApplicationSession):

    def onConnect(self):
        # Les deux méthodes qui suivent onConnect et onChallenge vont permetre à l'application
        # de s'authentifier.
        print("Client connecté. Authentification en cours sur le realm '{self.config.realm}' en tant que '{PRINCIPAL}' ...")
        self.join(self.config.realm, ["ticket"], PRINCIPAL)

    def onChallenge(self, challenge):
        if challenge.method == "ticket":
            print(f"Demande de ticket reçu: {challenge}")
            return TICKET
        else:
            raise Exception(f"La méthode d'authentification {challenge.method} est invalide")

    async def onJoin(self, details):
        # Ici on appelle une méthod sur l'Application backend.
        result = await self.call('com.myapp.hello', 'Tonton', 'Christobal')
        print(result)
        # Et on quitte l'application.
        self.leave()

    def onDisconnect(self):
        asyncio.get_event_loop().stop()


if __name__ == '__main__':
    runner = ApplicationRunner(URL, REALM)
    runner.run(Component)

from autobahn.asyncio.wamp import ApplicationSession, ApplicationRunner
from autobahn.wamp.types import RegisterOptions

URL = 'ws://127.0.0.1:8080/ws'
REALM = 'realm1'
TICKET = '****'
PRINCIPAL = 'backend'

class Component(ApplicationSession):

    def onConnect(self):
        # Les deux méthodes qui suivent onConnect et onChallenge vont permetre à l'application
        # de s'authentifier.
        print("Client connecté. Authentification en cours sur le realm '{self.config.realm}' en tant que '{PRINCIPAL}' ...")
        self.join(self.config.realm, ["ticket"], PRINCIPAL)

    def onChallenge(self, challenge):
        if challenge.method == "ticket":
            print(f"Demande de ticket reçu: {challenge}")
            return TICKET
        else:
            raise Exception(f"La méthode d'authentification {challenge.method} est invalide")

    async def onJoin(self, details):
        # On indique que la méthode say_hello pourra être appelée
        # a distance.
        # L'option invoke permet de faire de la répartion de charge
        # si on la met à roundrobin, on va pouvoir avoir plusieur
        # backend qui propose cette méthode. par defaut, elle est à single
        # et une erreur apparait si un autre backend tente d'enregistrer
        # une méthode du même nom.
        reg = await self.register(self.say_hello, 'com.myapp.hello', RegisterOptions(invoke='roundrobin'))
        print(reg)

    def say_hello(self, first_name, last_name):
        # Dans une méthod exposé, on peut retourner
        # n'importe quoi du moment que c'est serialisable en json.
        return f'Bonjour {first_name} {last_name} !'


if __name__ == '__main__':
    runner = ApplicationRunner(URL, REALM)
    runner.run(Component)

Exemple de PUB/SUB

import asyncio
from autobahn.asyncio.wamp import ApplicationSession, ApplicationRunner

URL = 'ws://127.0.0.1:8080/ws'
REALM = 'realm1'
TICKET = '****'
PRINCIPAL = 'frontend'

class Component(ApplicationSession):

    def onConnect(self):
        # Les deux méthodes qui suivent onConnect et onChallenge vont permetre à l'application
        # de s'authentifier.
        print("Client connecté. Authentification en cours sur le realm '{self.config.realm}' en tant que '{PRINCIPAL}' ...")
        self.join(self.config.realm, ["ticket"], PRINCIPAL)

    def onChallenge(self, challenge):
        if challenge.method == "ticket":
            print(f"Demande de ticket reçu: {challenge}")
            return TICKET
        else:
            raise Exception(f"La méthode d'authentification {challenge.method} est invalide")

    async def onJoin(self, details):
        # ICI on va indiquer que la méthode on_temperature_change
        # sera appeler dès qu'un message arrive sur 'com.myapp.temperature'
        # C'est ici que l'on peut également initialiser quelque variable.
        # Dans cette exemple, on va compter le nombre de message reçue.
        self.nb_msg = 0
        await self.subscribe(on_temperature_change, 'com.myapp.temperature')

    def on_temperature_change(msg):
        # On affiche le message reçu et on quitte après en avoir
        # reçu 5.
        print(f"Temperature: {msg['Temperature']}")
        self.received += 1
        if self.received > 5:
            self.leave()

    def onDisconnect(self):
        asyncio.get_event_loop().stop()


if __name__ == '__main__':
    runner = ApplicationRunner(URL, REALM)
    runner.run(Component)

from random import randint
from autobahn.asyncio.wamp import ApplicationSession, ApplicationRunner
from autobahn.wamp.types import RegisterOptions

URL = 'ws://127.0.0.1:8080/ws'
REALM = 'realm1'
TICKET = '****'
PRINCIPAL = 'backend'

class Component(ApplicationSession):

    def onConnect(self):
        # Les deux méthodes qui suivent onConnect et onChallenge vont permetre à l'application
        # de s'authentifier.
        print("Client connecté. Authentification en cours sur le realm '{self.config.realm}' en tant que '{PRINCIPAL}' ...")
        self.join(self.config.realm, ["ticket"], PRINCIPAL)

    def onChallenge(self, challenge):
        if challenge.method == "ticket":
            print(f"Demande de ticket reçu: {challenge}")
            return TICKET
        else:
            raise Exception(f"La méthode d'authentification {challenge.method} est invalide")

    async def onJoin(self, details):
        # ICI on va publier toute les seconde une message sous forme de dictionnaire.
        # on peut publier n'importe quoi qui soit serialisable en json.
        while True:
            self.publish('com.myapp.temperature', {"temperature": randint(-10, 10)})
            await asyncio.sleep(1)

if __name__ == '__main__':
    runner = ApplicationRunner(URL, REALM)
    runner.run(Component)

Si vous souhaitez voir d'autre exemple et avoir une idée de tous ce que l'on peut faire avec crossbar.io jettez un oeil dans le dépot de crossbario:

Allez à vos IDE et bon code! laughing