Construire un microservice SMS avec Python, Twilio et Redis Pub/Sub

December 04, 2020
Rédigé par
Gabriela Cavalcante
Contributeur
Les opinions exprimées par les contributeurs de Twilio sont les leurs

Construire un microservice SMS à l'aide de Python, Twilio et Redis Pub/Sub

Il y a quelques semaines, j'avais besoin de construire une solution pour envoyer des messages d'alerte aux clients de mon entreprise. Le service de base avait accès à la base de données, mais il avait déjà de nombreuses responsabilités et l'ajout d'une nouvelle fonctionnalité pour envoyer un grand nombre de messages aurait pu le surcharger. J'ai donc décidé d'adopter une approche différente : le service principal récupère les utilisateurs qui devraient recevoir le message de la base de données et construit le contenu du message, mais envoie ensuite le tout à un service distinct qui se charge d'envoyer les messages.

Ce nouveau service qui envoie des SMS peut être utilisé pour de nombreuses autres applications : un service d'alerte pour avertir les clients des promotions, voire un service de rappel des rendez-vous planifiés. Puisqu'il est entièrement générique, c'est facile de le réutiliser.

Pour ce tutoriel, nous allons construire un exemple simple de deux services Python communiquant à l'aide de Redis Pub/Sub. Le service principal va envoyer des SMS à l'aide de Twilio Programmable SMS. Nous allons ensuite créer une deuxième application qui envoie un SMS.

Configuration requise pour le tutoriel

Pour suivre ce tutoriel, vous avez besoin des composants suivants :

  • Python 3.7 ou version ultérieure. Si votre système d'exploitation ne fournit pas d'interpréteur Python, vous pouvez vous rendre sur python.org pour télécharger un programme d'installation.
  • Redis, un magasin de données open source, en mémoire, NoSQL que nous allons utiliser pour stocker des messages.
  • Un smartphone avec un numéro de téléphone actif sur lequel WhatsApp est installé.
  • Un compte Twilio. Si vous êtes nouveau sur Twilio, créez un compte gratuit maintenant. En utilisant ce lien pour ouvrir votre compte, vous recevrez 10 $ de crédit une fois que vous l'aurez mis à niveau.

Configurer votre compte Twilio

Pour suivre ce tutoriel, vous avez besoin de votre Account SID Twilio, d'un Auth Token (token d'authentification) et du numéro de téléphone Twilio. Lorsque vous vous connectez à votre console Twilio, vous verrez le Account SID (SID de compte) et le Auth Token (token d'authentification). Le Account SID est un identifiant unique pour votre compte et le Auth Token est une clé secrète (ne partagez jamais ce token !). Il s'agit de vos informations d'identification Twilio, qui permettront au code Python d'accéder au compte Twilio et d'utiliser l'API.

Informations d’identification de compte Twilio

Saisissez maintenant votre Account SID (SID de compte) et votre Auth Token (token d'authentification) dans votre console de compte Twilio et définissez-les dans vos variables d'environnement en saisissant ceci dans le terminal :

export TWILIO_ACCOUNT_SID='YOUR_ACCOUNT_SID' 
export TWILIO_AUTH_TOKEN='YOUR_AUTH_TOKEN'

Notez que si vous suivez ce tutoriel sur un ordinateur Windows, vous devez utiliser set au lieu de export.

Si vous n'avez pas de numéro de téléphone Twilio avec la fonctionnalité SMS, vous devez en acheter un. Cliquez sur le bouton « Get a trial phone number » (Obtenir un numéro de téléphone d'essai) sous vos identifiants de connexion dans la console Twilio, ou utilisez la page Buy a number (Acheter un numéro).

Une fois que vous avez votre numéro de téléphone, copiez-le et ajoutez-le à une autre variable d'environnement comme indiqué ci-dessous :

export TWILIO_PHONE_NUMBER='YOUR_PHONE_NUMBER' 

Ceci créera le répertoire pubsub et installera les modules que nous allons utiliser dans ce projet, à savoir :

Vous devrez ensuite installer le service Redis sur votre machine. Cela peut être fait de plusieurs façons. Si vous utilisez un ordinateur Unix ou Mac OS, vous pouvez l'installer directement à partir du code source. Dans une nouvelle fenêtre de terminal, utilisez les commandes suivantes pour télécharger et créer Redis :

$ wget https://download.redis.io/releases/redis-6.0.9.tar.gz
$ tar xzf redis-6.0.9.tar.gz
$ cd redis-6.0.9
$ make

Une fois la construction terminée, vous pouvez démarrer Redis avec la commande :

$ src/redis-server

Si vous préférez ne pas traiter le code source, il y a une image Docker et des éléments binaires pour Microsoft Windows.

Assurez-vous que Redis est en cours d'exécution avant de continuer.

Construction du microservice SMS

La première application que nous allons construire est le service d'envoi de SMS, qui agit comme « abonné » à la solution Pub/Sub. Ce service effectue l'écoute sur un canal Redis. Ainsi, lorsqu'un message arrive sur ce canal, il reçoit une notification. Pour ce tutoriel, le message inclura tous les détails nécessaires à l'envoi d'un SMS à l'aide de la bibliothèque Twilio Python Helper.

Créez un fichier nommé sub.py dans le répertoire pubsub, puis ajoutez-y le code suivant :

import os
import redis
import json

from twilio.rest import Client
from multiprocessing import Process

redis_conn = redis.Redis(charset="utf-8", decode_responses=True)


def sub(name: str):
    pubsub = redis_conn.pubsub()
    pubsub.subscribe("broadcast")
    for message in pubsub.listen():
        if message.get("type") == "message":
            data = json.loads(message.get("data"))
            print("%s : %s" % (name, data))

            account_sid = os.environ.get("TWILIO_ACCOUNT_SID")
            auth_token = os.environ.get("TWILIO_AUTH_TOKEN")

            body = data.get("message")
            from_ = data.get("from")
            to = data.get("to")

            client = Client(account_sid, auth_token)
            message = client.messages.create(from_=from_, to=to, body=body)
            print("message id: %s" % message.sid)


if __name__ == "__main__":
    Process(target=sub, args=("reader1",)).start()

Passons en revue notre script. Après avoir importé quelques bibliothèques, nous créons une connexion Redis, en définissant decode_Responses à True, afin que le client décode les données de texte sous forme de chaîne :

redis_conn = redis.Redis(charset="utf-8", decode_responses=True)

Maintenant, nous devons instancier un objet pub/sub et nous abonner à un canal.

def sub(name: str):
   pubsub = redis_conn.pubsub()
   pubsub.subscribe("broadcast")
   ...

Redis prend également en charge les abonnements utilisant un modèle. Par exemple, si nous avons plusieurs canaux comme channel-broadcast, channel-alert, channel-reminder, nous pouvons nous abonner à tous les canaux qui commencent par channel- en utilisant le modèle channel-*, par exemple : pubsub.subscribe("channel-*").

Ensuite, nous devons écouter en permanence les canaux auxquels nous sommes abonnés. Pour ce faire, nous utilisons la méthode listen() de l'objet pubsub. Cette méthode renvoie un générateur qui bloque l'exécution et attend qu'un nouveau message arrive sur le canal.

def sub(name: str):
   pubsub = redis_conn.pubsub()
   pubsub.subscribe("broadcast")

   for message in pubsub.listen():
       ...

Nous ne pouvons publier que des messages de type string, int ou float, mais lorsque l'abonné reçoit un message du canal, il est fourni comme un objet dictionnaire. Par exemple, si nous publions hello, l'abonnement obtient :

{
   "type": "message",
   "pattern": None,
   "channel": b"broadcast",
   "data": b"hello"
}

Chaque message comporte quatre clés :

  • type : le type de message. Il y a six types : subscribeunsubscribepsubscribepunsubscribemessagepmessage. Dans ce tutoriel, seul le type message nous intéresse.
  • pattern : dans notre exemple, le modèle est None, c'est la valeur par défaut. Cependant, si nous utilisons l'abonnement de modèle, ce champ stockera le modèle utilisé, par exemple channel-*.
  • channel : le nom du canal.
  • data : le message réel publié sur le canal.

Dans ce tutoriel, nous attendons le contenu de data avec une structure json. Nous devons donc convertir avant de le publier et de le déconvertir en série sur l'abonné. Nous pouvons utiliser json.loads pour prendre le message de chaîne et renvoyer un objet json. Voici un exemple de message publié :

{
   "message": "hello",
   "from": "+12345678900",
   "to": "+558499999999"
}

Le champ message est le contenu du message ; le champ from est notre numéro de téléphone Twilio, qui sera l'expéditeur du SMS ; et le champ to définit le numéro de téléphone auquel nous envoyons un message. Le format de ce numéro est E.164, qui utilise un '+' suivi de l'indicatif du pays puis du numéro, sans tiret ni autre séparateur, par exemple +558499999999.

Si vous utilisez un compte Twilio d'essai, vous devez d'abord vérifier votre numéro de téléphone 'To', car Twilio a besoin de savoir que vous en êtes le propriétaire. Vous pouvez vérifier votre numéro de téléphone en l'ajoutant à vos IDs d'appelants vérifiés dans la console. Si vous tentez d'envoyer un SMS à un numéro non vérifié avec un compte d'essai, l'API renvoie l'erreur 21219.

def sub(name: str):
   pubsub = redis_conn.pubsub()
   pubsub.subscribe("broadcast")

   for message in pubsub.listen():
       if message.get("type") == "message":
           data = json.loads(message.get("data"))
           print("%s : %s" % (name, data))

           body = data.get("message")
           from_ = data.get("from")
           to = data.get("to")
           ...

Maintenant que nous avons toutes les données dont nous avons besoin pour envoyer un message, nous lisons les variables d'environnement TWILIO_ACCOUNT_SID et TWILIO_AUTH_TOKEN. Nous allons créer un Client Twilio et envoyer le SMS à l'aide de la fonction messages.create.

import os
import redis
import json

from twilio.rest import Client
from multiprocessing import Process

redis_conn = redis.Redis(charset="utf-8", decode_responses=True)


def sub(name: str):
   pubsub = redis_conn.pubsub()
   pubsub.subscribe("broadcast")
   for message in pubsub.listen():
       if message.get("type") == "message":
           data = json.loads(message.get("data"))
           print("%s : %s" % (name, data))

           account_sid = os.environ.get("TWILIO_ACCOUNT_SID")
           auth_token = os.environ.get("TWILIO_AUTH_TOKEN")

           body = data.get("message")
           from_ = data.get("from")
           to = data.get("to")

           client = Client(account_sid, auth_token)
           message = client.messages.create(from_=from_, to=to, body=body)
           print("message id: %s" % message.sid)


if __name__ == "__main__":
   Process(target=sub, args=("reader1",)).start()

Nous exécutons la fonction sub en utilisant Process à partir de multiprocessing. Il est conseillé d'utiliser Process ici, car la boucle d'événements générée lorsque nous appelons listen() est bloquée, ce qui signifie que nous ne pouvons rien faire d'autre qu'attendre de nouveaux messages. Dans cet exemple simple, ce blocage n'est pas un problème, mais dans une application réelle où vous voulez travailler sur d'autres choses en même temps, cela pourrait devenir un vrai problème.

Création d'un éditeur

Maintenant que nous avons l'abonné, nous pouvons construire un petit éditeur. Tout d'abord, nous créons la connexion Redis, comme nous l'avons fait avec le script d'abonné. Nous lisons ensuite la variable d'environnement TWILIO_PHONE_NUMBER et nous définissons le numéro de téléphone pour envoyer un message (remplacez YOUR_NUMBER par un numéro de téléphone que vous avez ajouté à vos IDs d'appelants vérifiés). Nous avons configuré un dictionnaire de messages au format indiqué dans la section précédente, et nous le publions sur le canal broadcast sous forme de chaîne JSON. Écrivez l'application suivante dans un fichier pub.py :

import os
import redis
import json

redis_conn = redis.Redis(charset="utf-8", decode_responses=True)

def pub():
    data = {
        "message": "hello",
        "from": os.environ.get("TWILIO_PHONE_NUMBER"),
        "to": "YOUR_NUMBER"
    }
    redis_conn.publish("broadcast", json.dumps(data))

if __name__ == "__main__":
    pub()

Test du service de messagerie

Êtes-vous prêt à tester les services ? Démarrez l'abonné en exécutant python sub.py, en veillant à le faire pendant que l'environnement virtuel Python est activé et que les variables d'environnement TWILIO_ACCOUNT_SID et TWILIO_AUTH_TOKEN sont définies.

(pubsub-venv) $ python sub.py

Notre abonné est en service ! Ouvrez un autre terminal, activez l'environnement virtuel et définissez la variable TWILIO_PHONE_NUMBER dans l'environnement. Exécutez ensuite le script de l'éditeur pour vous envoyer un SMS de test :

(pubsub-venv) $ python pub.py

Vous devriez recevoir le SMS et voir également le résultat suivant dans le processus d'abonné :

(pubsub-venv) $ python sub.py
reader1 : {'message': 'hello', 'from': '+12...'', 'to': '+55...'}
message id: SM6...

Vous pouvez trouver le code complet dans mon répertoire sur GitHub.

Conclusion

Félicitations ! Vous avez créé un canal de communication entre les microservices à l'aide de Redis pub/sub ! J'espère que vous avez apprécié ce tutoriel et qu'il vous a semblé utile. Vous pouvez maintenant étendre ce concept et construire d'autres projets à l'aide du modèle pub/sub.    

Bonne chance !

Gabriela Cavalcante est une passionnée de Python et une fan de Flask. Vous pouvez trouver certains de ses projets sur GitHub et parler de Python avec elle sur Twitter.