Skip to content

Commit 9cb65ac

Browse files
Ajout de l'intégration RabbitMQ
1 parent 9b8a420 commit 9cb65ac

7 files changed

Lines changed: 165 additions & 29 deletions

File tree

.env.exemple

Lines changed: 42 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,15 @@
1+
# Email configuration
2+
# Email domain (e.g., gmail.com)
13
EMAIL_DOMAIN=
4+
# SMTP server host
25
EMAIL_HOST=
6+
# SMTP server port
37
EMAIL_PORT=
8+
# SMTP username
49
EMAIL_USERNAME=
10+
# SMTP password
511
EMAIL_PASSWORD=
12+
# From email address (e.g., noreply@example.com)
613
FROM_EMAIL=
714

815
# Kafka configuration
@@ -15,10 +22,43 @@ KAFKA_CONSUMER_TOPIC=email_topic
1522
# Key for Kafka messages
1623
KAFKA_MESSAGE_KEY=
1724

18-
# Example values:
25+
# RabbitMQ configuration
26+
# Set to True to enable RabbitMQ integration
27+
USE_RABBITMQ=
28+
# RabbitMQ connection URL (e.g., amqp://user:password@host:port/vhost)
29+
RABBITMQ_URL=
30+
# RabbitMQ exchange name
31+
RABBITMQ_EXCHANGE=email_exchange
32+
# RabbitMQ routing key (optional)
33+
RABBITMQ_ROUTING_KEY=email_routing_key
34+
# RabbitMQ queue name
35+
RABBITMQ_QUEUE=email_queue
36+
# RabbitMQ default username
37+
RABBITMQ_DEFAULT_USER=
38+
# RabbitMQ default password
39+
RABBITMQ_DEFAULT_PASS=
40+
41+
42+
43+
# ========== Example values: ==========
1944
# EMAIL_DOMAIN=example.com
2045
# EMAIL_HOST=smtp.example.com
2146
# EMAIL_PORT=587
2247
# EMAIL_USERNAME=user@example.com
2348
# EMAIL_PASSWORD=yourpassword
24-
# FROM_EMAIL=noreply@example.com
49+
# FROM_EMAIL=noreply@example.com
50+
51+
# KAFKA_BOOTSTRAP_SERVERS=broker1:9092,broker2:9092
52+
# KAFKA_MESSAGE_KEY=email_notification
53+
# KAFKA_MESSAGE_KEY=email_notification
54+
# USE_KAFKA=True
55+
# KAFKA_MESSAGE_KEY=email_notification
56+
# KAFKA_MESSAGE_KEY=email_notificationemail_notification
57+
58+
# USE_RABBITMQ=True
59+
# RABBITMQ_URL=amqp://user:password@rabbitmq_host:5672/vhost
60+
# RABBITMQ_EXCHANGE=email_exchange
61+
# RABBITMQ_ROUTING_KEY=email_routing_key
62+
# RABBITMQ_DEFAULT_USER=guest
63+
# RABBITMQ_DEFAULT_PASS=guest
64+
# KAFKA_MESSAGE_KEY=email_notification

README.md

Lines changed: 35 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -282,33 +282,6 @@ curl http://localhost:9876/api/smtp-status
282282
}
283283
```
284284

285-
### Intégration du test SMTP en Python
286-
287-
```python
288-
import requests
289-
290-
def check_smtp_status():
291-
try:
292-
response = requests.get("http://localhost:9876/api/smtp-status")
293-
data = response.json()
294-
295-
if data["status"]:
296-
print("✅ Serveur SMTP accessible")
297-
return True
298-
else:
299-
print("❌ Serveur SMTP non accessible:", data["message"])
300-
return False
301-
except Exception as e:
302-
print(f"Erreur lors de la vérification : {e}")
303-
return False
304-
305-
# Utilisation
306-
if check_smtp_status():
307-
# Procéder à l'envoi d'emails
308-
send_email("test@example.com", "Test", "Message de test")
309-
else:
310-
print("Impossible d'envoyer des emails pour le moment")
311-
```
312285

313286
## 🔧 Gestion des erreurs
314287

@@ -412,6 +385,41 @@ services:
412385
restart: unless-stopped
413386
```
414387
388+
## 🐰 Intégration RabbitMQ
389+
390+
Le service peut consommer des messages RabbitMQ pour envoyer automatiquement des emails si l'intégration RabbitMQ est activée via les variables d'environnement (`USE_RABBITMQ`, `RABBITMQ_URL`, `RABBITMQ_EXCHANGE`, `RABBITMQ_ROUTING_KEY`, `RABBITMQ_QUEUE`).
391+
392+
Format JSON attendu pour le message publié sur RabbitMQ :
393+
394+
```json
395+
{
396+
"receiver_email": "user@example.com",
397+
"email_object": "Sujet",
398+
"message_text": "Contenu du message"
399+
}
400+
```
401+
402+
### Variables d'environnement RabbitMQ
403+
404+
| Variable | Description | Exemple |
405+
|----------|-------------|---------|
406+
| `USE_RABBITMQ` | Activer l'intégration RabbitMQ | `True` |
407+
| `RABBITMQ_URL` | URL de connexion RabbitMQ | `amqp://admin:admin@rabbitmq:5672` |
408+
| `RABBITMQ_EXCHANGE` | Nom de l'exchange | `email_exchange` |
409+
| `RABBITMQ_ROUTING_KEY` | Routing key pour lier la queue | `email_routing_key` |
410+
| `RABBITMQ_QUEUE` | Nom de la queue | `email_queue` |
411+
| `RABBITMQ_DEFAULT_USER` | Utilisateur RabbitMQ | `admin` |
412+
| `RABBITMQ_DEFAULT_PASS` | Mot de passe RabbitMQ | `admin` |
413+
414+
### Points importants pour l'intégration RabbitMQ
415+
416+
- Le conteneur qui exécute le service doit être sur le même réseau Docker que RabbitMQ afin d'utiliser l'adresse interne (ex. `rabbitmq:5672`). Sans réseau partagé, la résolution de nom et la connexion échoueront.
417+
- Pour les clients externes (depuis la machine hôte), utilisez l'endpoint exposé de RabbitMQ (ex. `localhost:5672`) si les ports sont mappés.
418+
- Assurez-vous que `RABBITMQ_URL` pointe vers l'endpoint correct selon le contexte (interne au réseau Docker vs externe).
419+
- Vérifiez que l'exchange et la routing key configurés correspondent à ceux utilisés par vos producteurs de messages.
420+
- Le service déclare automatiquement l'exchange de type `TOPIC` et la queue durable, puis les lie avec la routing key spécifiée.
421+
422+
415423
## 🚨 Sécurité et bonnes pratiques
416424

417425
### Recommandations de sécurité

app.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
from fastapi import FastAPI
22
from api import router as api_router
33
from kafka_config import consume_messages
4+
from rabbitmq_config import consume_rabbitmq_messages
45
from contextlib import asynccontextmanager
56

67

@@ -10,6 +11,7 @@ async def LifeSpan(app: FastAPI):
1011
import asyncio
1112

1213
loop = asyncio.get_event_loop()
14+
loop.run_in_executor(None, consume_rabbitmq_messages)
1315
loop.run_in_executor(None, consume_messages)
1416
yield
1517
print("-----------------APP ENDED----------------------")

rabbitmq.py

Whitespace-only changes.

rabbitmq_config.py

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
import datetime
2+
import aio_pika
3+
import asyncio
4+
from utils import SETTINGS, send_email
5+
6+
7+
async def connect_to_rabbitmq(rabbitmq_url: str = SETTINGS.RABBITMQ_URL) -> tuple:
8+
try:
9+
print("Connecting to RabbitMQ...")
10+
connection = await aio_pika.connect_robust(rabbitmq_url, timeout=5)
11+
12+
channel = await connection.channel()
13+
exchange = await channel.declare_exchange(
14+
SETTINGS.RABBITMQ_EXCHANGE, aio_pika.ExchangeType.TOPIC
15+
)
16+
queue = await channel.declare_queue(SETTINGS.RABBITMQ_QUEUE, durable=True)
17+
18+
await queue.bind(exchange, routing_key=SETTINGS.RABBITMQ_ROUTING_KEY)
19+
20+
print("Connected to RabbitMQ")
21+
return connection, exchange, queue
22+
except Exception as e:
23+
import traceback
24+
25+
print(f"Error connecting to RabbitMQ: {e}")
26+
traceback.print_exc()
27+
return None, None, None
28+
29+
30+
async def consume_rabbitmq_messages_():
31+
connection, exchange, queue = await connect_to_rabbitmq()
32+
if not connection:
33+
print("Failed to connect to RabbitMQ. Exiting consumer.")
34+
return
35+
36+
async with connection:
37+
async with queue.iterator() as queue_iter:
38+
async for message in queue_iter:
39+
async with message.process():
40+
try:
41+
payload = message.body.decode()
42+
print(f"Received message from RabbitMQ: {payload}")
43+
# Assuming payload is a JSON string with required fields
44+
import json
45+
46+
data = json.loads(payload)
47+
receiver_email = data.get("receiver_email")
48+
message_text = data.get("message_text")
49+
email_object = data.get("email_object")
50+
51+
if not all([receiver_email, message_text, email_object]):
52+
print(
53+
f"[{datetime.datetime.now()}] Incomplete email data received: {data}"
54+
)
55+
print("The correct format is:")
56+
print(
57+
'{"receiver_email": "email@example.com", "email_object": "Subject", "message_text": "Body"} or {"receiver_email": ["email@example.com"], "email_object": "Subject", "message_text": "Body"}'
58+
)
59+
print("Skipping this message.")
60+
continue
61+
62+
send_email(receiver_email, message_text, email_object)
63+
except Exception as e:
64+
print(f"Error processing message: {e}")
65+
finally:
66+
await asyncio.sleep(3) # Prevent tight loop
67+
68+
69+
def consume_rabbitmq_messages():
70+
if not SETTINGS.USE_RABBITMQ:
71+
print(
72+
"RabbitMQ integration is disabled. Setting USE_RABBITMQ to True to enable it and restart the service."
73+
)
74+
return
75+
print("Starting RabbitMQ consumer...")
76+
asyncio.run(consume_rabbitmq_messages_())

requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,3 +40,4 @@ uvicorn==0.37.0
4040
watchfiles==1.1.0
4141
websockets==15.0.1
4242
kafka-python==2.2.15
43+
aio_pika==9.5.7

utils.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,15 @@ class Settings(BaseSettings):
2525
KAFKA_CONSUMER_TOPIC: str = None
2626
KAFKA_MESSAGE_KEY: str | None = None
2727

28+
# RabbitMQ configurations
29+
USE_RABBITMQ: bool = False
30+
RABBITMQ_URL: str = None
31+
RABBITMQ_EXCHANGE: str = "email_exchange"
32+
RABBITMQ_ROUTING_KEY: str = None
33+
RABBITMQ_QUEUE: str = "email_queue"
34+
RABBITMQ_DEFAULT_USER: str = None
35+
RABBITMQ_DEFAULT_PASS: str = None
36+
2837
class Config:
2938
env_file = ".env"
3039

0 commit comments

Comments
 (0)