<!-- title: Mes notes sur RabbitMQ -->
<!-- category: Développement -->

[RabbitMQ](https://www.rabbitmq.com) est un bus de messages Open Source qui
implémente le protocole Advanced Message Queuing (AMQP). Sa fonction est de
faire communiquer entre eux des programmes différents, potentiellement écrits
dans différents langages.<!-- more --> Le serveur RabbitMQ est lui-même écrit dans le
langage de programmation Erlang, ce qui est plutôt atypique. Aucune
connaissance de Erlang n'est nécessaire pour l'utiliser. C'est un produit
édité par Pivotal, un spin-off de VMWare et EMC, connu de tous les
développeurs JAVA pour son fabuleux framework
[Spring](https://en.wikipedia.org/wiki/Spring_Framework).

RabbitMQ demande une complexité de configuration proportionnelle aux exigences
demandées : queues de messages persistantes, dead letters, haute
disponibilité, optimisation des performances... Pour les configurations
compliquées et le support technique avancé dans des mises en oeuvre
d'entreprises, il y a des experts (j'ai une adresse pour ceux intéressés).
Pour une utilisation basique, dans un cadre de développement pépère à la
maison, RabbitMQ est très accessible, bien documenté et permet d'avoir
rapidement un bus de message pour faire communiquer ses applications.

Je m'en sers actuellement pour faire discuter mon petit éco-système hébergé. Dans ce cadre j'ai pris quelques notes sur sa mise en place, de l'installation à la configuration de base. 

### Installation 

Choix de l'OS : CentOS 7

Page d'aide de référence pour l'installation : https://www.rabbitmq.com/install-rpm.html

Pivotal fournit une installation d'une version allégée de Erlang avec les dépendances nécessaires à RabbitMQ : https://github.com/rabbitmq/erlang-rpm 

Et bien sûr, il fournissent aussi un RPM de RabbitMQ (actuellement en version 3.7.2-1) : https://dl.bintray.com/rabbitmq/all/rabbitmq-server/3.7.2/rabbitmq-server-3.7.2-1.el7.noarch.rpm

Gestion du service à la sauce CentOS : 

    # démarrer le service 
    /sbin/service rabbitmq-server start

    # stopper le service
    /sbin/service rabbitmq-server stop

    # démarrage automatique du service
    chkconfig rabbitmq-server on

Après avoir mis le service en démarrage automatique, on n'utilisa plus que l'outil *rabbitmqctl* :

    # démarrer le serveur rabbitmq
    rabbitmqctl start_app

    # stopper le serveur rabbitmq
    rabbitmqctl stop_app

### Droits et permissions 

Par défaut, un utilisateur *guest* (mot de passe idem) est créé et il est
attaché à l'interface réseau locale (localhost). Pour se connecter depuis une
autre machine, en distant, il faut créer un nouvel utilisateur.

    rabbitmqctl add_user admin <mot de passe dur>
    rabbitmqctl set_user_tags admin administrator
    rabbitmqctl set_permissions -p / admin ".*" ".*" ".*"

### Interface Web d'administration

Une belle interface permet de gérer la configuration et de visualiser des
indicateurs de fonctionnement. C'est un plugin qu'on active en ligne de
commande avec *rabbitmq- plugins*

    rabbitmq-plugins enable rabbitmq_management

L'interface Web répond à l'adresse *http://server-name:15672/cli/*

L'utilisateur *guest* n'a accès à l'interface que par localhost. Le nouveau compte admin est nécessaire pour se connecter en distant.

Si l'interface Web est derrière un proxy NginX et qu'elle répond à une sous-URL du domaine, la config pour pour une sous-url */rabbitwebmin* est la suivante : 

    location /rabbitwebmin/ {
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        rewrite ^/rabbitwebmin/(.*)$ /$1 break;
        proxy_pass http://192.168.2.1:15672;
    }

Optionnellement, on peut installer [le CLI de rabbitmqadmin](https://www.rabbitmq.com
/management-cli.html) en téléchargeant le programme (Python) depuis
https://raw.githubusercontent.com/rabbitmq/rabbitmq-
management/v3.7.2/bin/rabbitmqadmin et en le plaçant dans */usr/local/bin*.

### Mise en oeuvre 

On crée un utilisateur technique pour nos applications dans un virtual host spécifique.

    rabbitmqctl add_vhost devhub
    rabbitmqctl add_user techuser tech
    rabbitmqctl set_permissions -p devhub techuser ".*" ".*" ".*"

A ce niveau, on peut essayer de faire communiquer deux applications à travers Rabbit avec un classique producteur-consommateur écrit en Python, utilisant [la librairie Pika](https://pika.readthedocs.io), dérivé du tutorial de RabbitMQ.

Code du producteur :

``` python
#!/usr/bin/env python
import pika
import sys

credentials = pika.PlainCredentials('techuser', 'tech')
connection = pika.BlockingConnection(pika.ConnectionParameters(host='192.168.2.1',credentials=credentials, virtual_host="devhub"))
channel = connection.channel()

channel.exchange_declare(exchange='hub.topic',
                            exchange_type='topic')

routing_key = sys.argv[1] if len(sys.argv) > 2 else 'anonymous.info'
message = ' '.join(sys.argv[2:]) or 'Hello World!'
channel.basic_publish(exchange='hub.topic',
                        routing_key=routing_key,
                        body=message)
print(" [x] Sent %r:%r" % (routing_key, message))
connection.close()
```

Code du consommateur :

``` python
#!/usr/bin/env python
import pika
import sys

credentials = pika.PlainCredentials('techuser', 'tech')
connection = pika.BlockingConnection(pika.ConnectionParameters(host='192.168.2.1',credentials=credentials, virtual_host="devhub"))
channel = connection.channel()

channel.exchange_declare(exchange='hub.topic',
                            exchange_type='topic')

result = channel.queue_declare(exclusive=True)
queue_name = result.method.queue
print("Queue => " + queue_name)

# on s'abonne aux topics : 
binding_keys = ['mail.message', 'mail.command.*']

for binding_key in binding_keys:
    channel.queue_bind(exchange='hub.topic',
                        queue=queue_name,
                        routing_key=binding_key)

print(' [*] Waiting for logs. To exit press CTRL+C')

def callback(channel, method, properties, body):
    print("=>  %r:%r" % (method.routing_key, body))

channel.basic_consume(callback,
                        queue=queue_name,
                        no_ack=True)

channel.start_consuming()
```

Quelques tests : 

    # le consommateur lancé dans un shell s'abonne aux topics mail.message et mail.command.*
    $ python3 consumer.py 

le consommateur doit recevoir le message suivant : 

    # le producteur produit dans le topic 'mail.message'
    $ python3 producer.py "mail.message"

le consommateur doit recevoir le message suivant : 

    # le producteur produit dans le topic 'mail.command.test'
    $ python3 producer.py "mail.command.test"

le consommateur ne doit pas recevoir le message suivant : 

    # le producteur produit dans le topic 'mail.rate'
    $ python3 producer.py "mail.rate"
