# Fichier: python_cheats/cheatsheets/api_gateway_custom.txt
# API Gateway Custom - Implémentation Complète
# Alternative à Kong, NGINX, AWS API Gateway


[OK] QU'EST-CE QU'UN API GATEWAY ?


# === DÉFINITION ===

Un API Gateway est un point d'entrée unique qui se place devant les microservices
pour gérer toutes les requêtes des clients. Il agit comme un reverse proxy intelligent
qui route les requêtes vers les services appropriés tout en fournissant des
fonctionnalités transversales.


# === FONCTIONNALITÉS PRINCIPALES ===

┌────────────────────────────────────────────────────────────────┐
│                           CLIENT                               │
│                     (Web, Mobile, IoT)                         │
└───────────────────────────────┬────────────────────────────────┘
                                │
                                v
┌────────────────────────────────────────────────────────────────┐
│                     API GATEWAY                                │
│  ┌──────────────────────────────────────────────────────────┐  │
│  │  1. Routing          -> Diriger vers le bon service       │  │
│  │  2. Authentication   -> Vérifier identité                 │  │
│  │  3. Authorization    -> Vérifier permissions              │  │
│  │  4. Rate Limiting    -> Limiter requêtes                  │  │
│  │  5. Load Balancing   -> Répartir charge                   │  │
│  │  6. Caching          -> Mise en cache réponses            │  │
│  │  7. Request/Response -> Transformation données            │  │
│  │  8. Logging          -> Enregistrer requêtes              │  │
│  │  9. Monitoring       -> Métriques et alertes              │  │
│  │  10. Circuit Breaker -> Protéger services défaillants     │  │
│  │  11. SSL Termination -> Gérer HTTPS                       │  │
│  │  12. API Versioning  -> Gérer versions API                │  │
│  └──────────────────────────────────────────────────────────┘  │
└────────────┬───────────┬──────────┬──────────┬─────────────────┘
             │           │          │          │
             v           v          v          v
      ┌──────────┐ ┌─────────┐ ┌────────┐ ┌────────┐
      │  Auth    │ │ Product │ │ Order  │ │ User   │
      │ Service  │ │ Service │ │Service │ │Service │
      └──────────┘ └─────────┘ └────────┘ └────────┘


# API Gateway — Explications détaillées (pour débutant), point par point

Super schéma — tu as déjà la liste complète. Je vais expliquer, dans l’ordre, ce que fait chaque fonction, **pourquoi** elle est utile, **comment** elle marche de façon simple, et **quelques bonnes pratiques / pièges** à connaître.

---

## 1. Routing — Diriger vers le bon service

**Quoi :** rediriger chaque requête (URL + méthode) vers le microservice ou backend approprié.
**Pourquoi :** un point d’entrée unique (l’API Gateway) prend toutes les requêtes et sait où les envoyer.
**Comment (simple) :** règles basées sur le chemin `/users/*` -> service utilisateur, `/payments/*` -> service paiement, ou sur l’en-tête, le host, la méthode.
**Exemple :** `GET /products` -> catalogue-service ; `POST /orders` -> order-service.
**Bonnes pratiques / pièges :** centraliser les routes facilite la maintenance ; attention aux règles conflictuelles et à la latence si le routing fait beaucoup de logique.

---

## 2. Authentication — Vérifier l’identité

**Quoi :** s’assurer que le client est bien celui qu’il prétend être (ex : token, clé API, JWT, OAuth).
**Pourquoi :** empêcher l’accès non autorisé dès le front door, éviter d’exposer les services internes.
**Comment :** vérifier un token JWT, valider une clé API, rediriger vers un fournisseur OAuth, ou intégrer SSO.
**Bonnes pratiques / pièges :** déléguer l’authentification au gateway réduit le code des microservices ; stocke les clés / secrets en sécurité et vérifie la validité & la signature des tokens.

---

## 3. Authorization — Vérifier les permissions

**Quoi :** décider si l’utilisateur authentifié a le droit d’effectuer une action (ex : lire/écrire, rôle admin…).
**Pourquoi :** l’authentification dit *qui tu es*, l’autorisation dit *ce que tu peux faire*.
**Comment :** vérification de rôles/permissions dans le token (claims) ou via une requête à un service d’ACL/Policy (ex : RBAC, ABAC).
**Bonnes pratiques / pièges :** garder les règles claires (ex : endpoints publics vs restreints) ; attention à la latence si la gateway interroge un service externe pour chaque requête.

---

## 4. Rate Limiting — Limiter les requêtes

**Quoi :** fixer combien de requêtes un client (ou IP, ou clé API) peut faire sur une période (ex : 100 req/min).
**Pourquoi :** protéger les services contre les abus, attaques DDoS, ou usages inattendus.
**Comment :** tokens bucket, leaky bucket, counters en mémoire ou via store partagé (Redis) pour les déploiements multiples.
**Bonnes pratiques / pièges :** définir des limites raisonnables par type d’utilisateur (gratuit vs payant). Surveiller et renvoyer des codes HTTP clairs (`429 Too Many Requests`) avec un en-tête `Retry-After`.

---

## 5. Load Balancing — Répartir la charge

**Quoi :** répartir le trafic entre plusieurs instances d’un même service.
**Pourquoi :** améliorer la performance et la disponibilité.
**Comment :** round-robin, least-connections, weighted, ou basée sur la session. La gateway peut faire le LB ou déléguer à un LB dédié.
**Bonnes pratiques / pièges :** surveiller la santé des instances (health checks) pour ne pas envoyer de trafic vers un nœud mort ; utiliser des poids si les instances n’ont pas la même capacité.

---

## 6. Caching — Mise en cache des réponses

**Quoi :** stocker temporairement certaines réponses pour les renvoyer rapidement sans appeler le service en backend.
**Pourquoi :** réduire la latence et la charge sur les services (ex : pages produits, configuration publique).
**Comment :** cache côté gateway (TTL), utiliser `Cache-Control`, invalidation par clé, ou cache partagé (Redis).
**Bonnes pratiques / pièges :** attention à la consistance (données très dynamiques ne doivent pas être trop longtemps en cache) ; gérer l’invalidation quand les données changent.

---

## 7. Request/Response Transformation — Transformer les données

**Quoi :** modifier la requête avant de la transmettre au service ou transformer la réponse avant de la renvoyer au client.
**Pourquoi :** découpler contrats externes et internes, normaliser formats, ajouter/enlever champs, protobuf <-> JSON, compatibilité entre versions.
**Comment :** réécrire en-têtes, changer chemins, mapper JSON, ajouter en-têtes d’audit, compresser/décompresser.
**Bonnes pratiques / pièges :** éviter transformations trop lourdes côté gateway (latence) ; documenter les transformations pour les équipes backend.

---

## 8. Logging — Enregistrer les requêtes

**Quoi :** journaliser qui appelle quoi, quand, le code de retour, latence, erreurs.
**Pourquoi :** audit, debugging, traçage d’incidents, facturation.
**Comment :** logs structurés (JSON), correlation IDs (trace-id), exporter vers un système central (ELK, Splunk).
**Bonnes pratiques / pièges :** ne pas loguer de secrets (tokens), garder logs indexables, configurer rotation/retention pour ne pas saturer le stockage.

---

## 9. Monitoring — Métriques et alertes

**Quoi :** mesurer la santé, la latence, le débit, les erreurs, l’utilisation de ressources et envoyer des alertes.
**Pourquoi :** détecter les problèmes avant qu’ils n’impactent les utilisateurs.
**Comment :** exposer métriques (Prometheus), dashboards (Grafana), règles d’alerte (CPU, erreurs, latence). Utiliser traces distribuées (OpenTelemetry) pour investiguer.
**Bonnes pratiques / pièges :** instrumenter les bons KPIs (p95/p99 latence, taux d’erreur), définir alertes utiles (éviter le bruit).

---

## 10. Circuit Breaker — Protéger les services défaillants

**Quoi :** couper (ou limiter) automatiquement les appels vers un service qui échoue trop souvent, puis tester la récupération.
**Pourquoi :** éviter les cascades d’échecs et réduire latence/timeouts inutiles.
**Comment :** états Closed/Open/Half-Open, seuils d’erreur et fenêtre de temps, test périodique.
**Bonnes pratiques / pièges :** combiner avec retries et fallback (répondre avec une version dégradée) ; bien calibrer les seuils pour ne pas couper trop tôt.

---

## 11. SSL Termination — Gérer HTTPS

**Quoi :** déchiffrer (terminer) la connexion TLS/HTTPS au niveau de la gateway, puis envoyer le trafic interne en HTTP ou en TLS selon l’architecture.
**Pourquoi :** centraliser la gestion des certificats, réduire la charge de chiffrement sur les services.
**Comment :** la gateway possède les certificats (Let’s Encrypt, CA), elle s’occupe du handshake TLS.
**Bonnes pratiques / pièges :** protéger les clés privées, utiliser TLS interne si nécessaire (surtout sur des réseaux non fiables), renouvellement automatique des certifs.

---

## 12. API Versioning — Gérer les versions de l’API

**Quoi :** permettre de déployer de nouvelles versions d’API sans casser les anciens clients.
**Pourquoi :** évolution contrôlée de l’API, compatibilité ascendante.
**Comment :** versionner dans le chemin (`/v1/` vs `/v2/`), via en-tête (`Accept`), ou via paramètres. Route vers les services correspondants selon la version.
**Bonnes pratiques / pièges :** maintenir la docs pour chaque version ; planifier la dépréciation (retirer une version après préavis) ; préférer des changements non cassants quand possible.

---

## Quelques conseils pratiques d’ensemble

* **Centraliser la logique commune** (auth, logging, security) dans la gateway simplifie les services internes.
* **Ne pas mettre trop de logique métier** dans la gateway — elle doit rester légère et rapide.
* **Sécurité avant tout** : protéger certificats/clefs, éviter fuite d’information dans les logs.
* **Observabilité** : corrélation IDs, métriques et traces sont essentielles pour diagnostiquer.
* **Tester les comportements de montée en charge** (rate limiting, circuit breaker, load balancing) en staging avant production.

---


# === POURQUOI IMPLÉMENTER SON PROPRE GATEWAY ? ===

[OK] AVANTAGES
- Contrôle total sur la logique
- Personnalisation complète
- Pas de dépendance externe
- Apprentissage approfondi
- Adapté aux besoins spécifiques
- Pas de coûts de licence
- Flexibilité maximale

[X] INCONVÉNIENTS
- Développement initial plus long
- Maintenance à assurer
- Sécurité à garantir soi-même
- Performance à optimiser
- Bugs potentiels
- Pas de support commercial


# === QUAND CRÉER SON PROPRE GATEWAY ? ===

[OK] OUI si:
- Besoins très spécifiques
- Logique métier dans le routing
- Budget limité
- Équipe compétente
- Temps de développement disponible
- Apprentissage souhaité

[X] NON si:
- Démarrage rapide nécessaire
- Équipe petite/inexpérimentée
- Besoin de features avancées (WAF, DDoS)
- Production critique
- Budget disponible
- Préférence pour solutions éprouvées


[OK] IMPLÉMENTATION API GATEWAY AVEC FLASK


# === ARCHITECTURE GLOBALE ===

api-gateway/
├── app/
│   ├── __init__.py
│   ├── config.py                    # Configuration
│   ├── core/
│   │   ├── __init__.py
│   │   ├── router.py                # Routing principal
│   │   ├── proxy.py                 # Proxy HTTP
│   │   └── service_registry.py      # Registry des services
│   ├── middleware/
│   │   ├── __init__.py
│   │   ├── auth.py                  # Authentication
│   │   ├── rate_limit.py            # Rate limiting
│   │   ├── logging.py               # Logging
│   │   ├── cache.py                 # Caching
│   │   ├── circuit_breaker.py       # Circuit breaker
│   │   └── cors.py                  # CORS
│   ├── plugins/
│   │   ├── __init__.py
│   │   ├── request_transform.py     # Transform requêtes
│   │   ├── response_transform.py    # Transform réponses
│   │   └── load_balancer.py         # Load balancing
│   ├── utils/
│   │   ├── __init__.py
│   │   ├── metrics.py               # Métriques Prometheus
│   │   └── health.py                # Health checks
│   └── routes/
│       ├── __init__.py
│       ├── admin.py                 # Routes admin
│       └── monitoring.py            # Routes monitoring
├── tests/
├── config/
│   └── gateway.yaml                 # Configuration gateway
├── Dockerfile
├── requirements.txt
└── run.py


Voici une **explication complète et claire** de chaque partie de ton architecture API Gateway, pour que tu comprennes bien **le rôle de chaque fichier et dossier**, exactement comme dans une vraie gateway (Kong, KrakenD, Traefik, Envoy…).

---

# [CONSTRUCTION] **ARCHITECTURE COMPLÈTE (EXPLIQUÉE EN DÉTAIL)**

```
api-gateway/
├── app/
│   ├── __init__.py
│   ├── config.py
│   ├── core/
│   │   ├── router.py
│   │   ├── proxy.py
│   │   └── service_registry.py
│   ├── middleware/
│   │   ├── auth.py
│   │   ├── rate_limit.py
│   │   ├── logging.py
│   │   ├── cache.py
│   │   ├── circuit_breaker.py
│   │   └── cors.py
│   ├── plugins/
│   │   ├── request_transform.py
│   │   ├── response_transform.py
│   │   └── load_balancer.py
│   ├── utils/
│   │   ├── metrics.py
│   │   └── health.py
│   └── routes/
│       ├── admin.py
│       └── monitoring.py
├── tests/
├── config/
│   └── gateway.yaml
├── Dockerfile
├── requirements.txt
└── run.py
```

---

# [HOT] **Explication dossier par dossier (clair et complet)**

---

# [DOSSIER] **app/**

C’est le **cœur de la gateway** : logique, modules internes, middlewares, routing.

---

# [FICHIER] `app/config.py`

Gère la **configuration centrale** de la Gateway :

* ports
* URL des services
* paramètres de sécurité
* limites de rate limiting
* activation/désactivation de fonctionnalités
* lecture du fichier `gateway.yaml`
* variables d’environnement

---

# [DOSSIER] **app/core/**

Le cœur logique des fonctionnalités obligatoires d'une API Gateway.

## [FICHIER] `router.py` — *Routing principal*

C’est le **cerveau** qui décide vers quel service envoyer la requête.

Fonctions clé :

* lire l’URL / méthode
* chercher dans le `service_registry`
* appliquer load balancing
* appeler le `proxy`

-> C’est **l’équivalent du router de Kong ou Traefik**.

---

## [FICHIER] `proxy.py` — *Proxy HTTP*

C’est lui qui **transfère réellement la requête** au microservice cible.

Il gère :

* préparation de la requête
* forwarding des headers
* forwarding du body
* réception de la réponse
* propagation des codes HTTP
* timeout
* retry (optionnel)

-> C’est **le cœur du comportement reverse-proxy**.

---

## [FICHIER] `service_registry.py` — *Registry dynamique des services*

Liste des microservices disponibles, par exemple :

```yaml
routes:
  - path: /users/*
    service: users-service
    url: http://localhost:7001
```

Le registry permet :

* découverte de services
* load balancing
* ajout dynamique (via admin API)
* health checks associés

-> C’est **l’équivalent du Service Discovery** (Consul, Eureka…).

---

# [DOSSIER] **app/middleware/**

Les middlewares appliqués **avant et/ou après** chaque requête.

## [SECURISE] `auth.py` — *Authentication*

Vérifie :

* tokens JWT
* clés API
* OAuth
* sessions
* signatures HMAC

---

## [PASSPORT_CONTROL] `authorization.py` (intégré à auth ou séparé)

Vérifie **permissions et rôles** avant d’autoriser l’accès.

---

## [SIGNAL] `rate_limit.py` — *Rate Limiting*

Limite le nombre de requêtes :

* par IP
* par token
* par route

Techniques possibles :

* Token bucket
* Sliding window
* Fixed window

---

## [DOC] `logging.py` — *Request/Response Logging*

Enregistre :

* méthode
* endpoint
* latence
* code HTTP
* user-agent

Peut envoyer vers :

* fichiers
* Elastic
* Loki
* OpenTelemetry

---

## [RAPIDE] `circuit_breaker.py` — *Circuit breaker*

Protège contre les services défaillants.

États :

* Closed
* Open
* Half-open

Empêche :

* timeouts répétitifs
* cascades de pannes

---

## [PACKAGE] `cache.py` — *Caching*

Stocke les réponses si autorisé :

* cache mémoire
* redis
* expiration TTL
* cache des GET

---

## [MONDE] `cors.py` — *CORS*

Gère :

* Access-Control-Allow-Origin
* Access-Control-Allow-Methods
* Preflight OPTIONS

---

# [DOSSIER] **app/plugins/**

Plugins optionnels que tu peux activer/désactiver (comme Kong).

## [OUTIL] `request_transform.py`

Transforme la requête **avant** envoi au service :

* ajouter/enlever headers
* normalisation JSON
* validation
* rewriting d’URL

---

## [OUTIL] `response_transform.py`

Modifie la réponse du service avant qu’elle ne revienne au client :

* filtrage de données
* ajout de metadata
* rewriting du body

---

## [SCALES] `load_balancer.py`

Algorithmes supportés :

* Round robin
* Weighted RR
* Least connections
* Random
* IP hash

---

# [DOSSIER] **app/utils/**

## [BAISSE] `metrics.py`

Expose les métriques de monitoring :

* via Prometheus
* ou OpenTelemetry métriques

Exemples :

* temps de latence
* nombre de requêtes
* statut par route
* nombre d’erreurs

---

## [HEAVY_BLACK_HEART] `health.py`

Endpoints pour vérifier la santé :

* `/health`
* `/ready`
* `/live`
* health checks des microservices

---

# [DOSSIER] **app/routes/**

## [OUTILS] `admin.py`

Routes d’administration (comme Kong Admin API) :

* ajouter un service
* supprimer un service
* activer un plugin
* voir les logs
* voir les stats
* recharger la configuration

---

## [GRAPHIQUE] `monitoring.py`

Endpoints internes :

* `/metrics` (Prometheus)
* `/traces` (si profil debug)
* `/status`
* `/uptime`

---

# [DOSSIER] **config/**

## [FICHIER] `gateway.yaml`

Fichier central où tu définis :

* routes -> services
* règles de sécurité
* paramètres du proxy
* plugins activés
* stratégies de load balancing
* limites de rate limiting

Exemple :

```yaml
routes:
  - path: /users/*
    service: users-service
    upstreams:
      - http://localhost:7001
      - http://localhost:7002
    plugins:
      - auth
      - rate_limit
      - logging
```

---

# [DOCKER] **Dockerfile**

Pour construire l’image de ta Gateway.

---

# [PACKAGE] **requirements.txt**

Toutes les dépendances Python.

---

# [BLACK_RIGHT-POINTING_TRIANGLE] **run.py**

Point d’entrée principal :

* démarre FastAPI / Flask / Starlette
* charge la config
* initialise les middlewares
* enregistre les routes
* lance le serveur

---


                              ┌──────────────────────────────────┐
                              │              CLIENT               │
                              └──────────────────┬────────────────┘
                                                 │  HTTP Request
                                                 [BLACK_DOWN-POINTING_TRIANGLE]
         ┌──────────────────────────────────────────────────────────────────────────┐
         │                               API GATEWAY                                │
         │──────────────────────────────────────────────────────────────────────────│
         │                                                                          │
         │   ┌────────────────────────── CORE ───────────────────────────────────┐  │
         │   │                                                                   │  │
         │   │   ┌───────────────────┐      ┌──────────────────────────┐         │  │
         │   │   │   router.py       │ ---> │    service_registry.py   │         │  │
         │   │   └───────────────────┘      └──────────────────────────┘         │  │
         │   │              │                          [BLACK_UP-POINTING_TRIANGLE]                         │  │
         │   │              [BLACK_DOWN-POINTING_TRIANGLE]                          │                         │  │
         │   │        ┌──────────┐           ┌───────────────────┐               │  │
         │   │        │ proxy.py │ <-------- │ Registered Services │             │  │
         │   │        └──────────┘           └───────────────────┘               │  │
         │   │                                                                   │  │
         │   └───────────────────────────────────────────────────────────────────┘  │
         │                                                                          │
         │   ┌──────────────────────── MIDDLEWARE PIPELINE ───────────────────────┐ │
         │   │                                                                    │ │
         │   │   ┌───────────────┐  -> Vérification identité                       │ │
         │   │   │  auth.py      │                                                │ │
         │   │   └───────────────┘                                                │ │
         │   │   ┌───────────────┐  -> Vérification permissions                    │ │
         │   │   │ authorization?│  (si intégré dans auth)                        │ │
         │   │   └───────────────┘                                                │ │
         │   │   ┌───────────────┐  -> Limite requêtes / IP / Token                │ │
         │   │   │ rate_limit.py │                                                │ │
         │   │   └───────────────┘                                                │ │
         │   │   ┌───────────────┐  -> Cache local / Redis                         │ │
         │   │   │  cache.py     │                                                │ │
         │   │   └───────────────┘                                                │ │
         │   │   ┌───────────────┐  -> CORS Policies                               │ │
         │   │   │  cors.py      │                                                │ │
         │   │   └───────────────┘                                                │ │
         │   │   ┌───────────────┐  -> Circuit breaker par service                 │ │
         │   │   │ circuit_breaker│                                               │ │
         │   │   └───────────────┘                                                │ │
         │   │   ┌───────────────┐  -> Logging global / audit                      │ │
         │   │   │ logging.py    │                                                │ │
         │   │   └───────────────┘                                                │ │
         │   └────────────────────────────────────────────────────────────────────┘ │
         │                                                                          │
         │   ┌───────────────────────────── PLUGINS ──────────────────────────────┐ │
         │   │                                                                    │ │
         │   │   ┌──────────────────────────┐  -> Modifier requêtes avant proxy    │ │
         │   │   │ request_transform.py     │                                     │ │
         │   │   └──────────────────────────┘                                     │ │
         │   │   ┌──────────────────────────┐  -> Modifier réponses avant client   │ │
         │   │   │ response_transform.py    │                                     │ │
         │   │   └──────────────────────────┘                                     │ │
         │   │   ┌──────────────────────────┐  -> Round Robin / Weighted LB        │ │
         │   │   │ load_balancer.py         │                                     │ │
         │   │   └──────────────────────────┘                                     │ │
         │   └────────────────────────────────────────────────────────────────────┘ │
         │                                                                          │
         │   ┌────────────────────────────── MONITORING ──────────────────────────┐ │
         │   │                                                                    │ │
         │   │   ┌──────────────┐ -> Expose /metrics                               │ │
         │   │   │ metrics.py   │                                                 │ │
         │   │   └──────────────┘                                                 │ │
         │   │   ┌──────────────┐ -> Liveness / Readiness                          │ │
         │   │   │ health.py    │                                                 │ │
         │   │   └──────────────┘                                                 │ │
         │   └────────────────────────────────────────────────────────────────────┘ │
         │                                                                          │
         │   ┌────────────────────────────── ROUTES ─────────────────────────────┐  │
         │   │                                                                   │  │
         │   │   ┌──────────────┐ -> Web UI Admin (list services, logs…)          │  │
         │   │   │ admin.py     │                                                │  │
         │   │   └──────────────┘                                                │  │
         │   │   ┌──────────────┐ -> /metrics, /health, /status                   │  │
         │   │   │ monitoring.py│                                                │  │
         │   │   └──────────────┘                                                │  │
         │   └───────────────────────────────────────────────────────────────────┘  │
         └───────────────────────┬──────────────────────────────────────────────────┘
                                 │
                                 [BLACK_DOWN-POINTING_TRIANGLE]  (via proxy + LB (Load Balancing) + CB (Circuit Breaker))
 ┌───────────────────────────────────────────────────────────────────────────────────────────┐
 │                                          MICRO SERVICES                                   │
 │────────────────────────────────────────────────────────────────────────────────────────── │
 │                                                                                           │
 │    ┌────────────────────────┐   ┌────────────────────────┐    ┌────────────────────────┐  │
 │    │     SERVICE A          │   │     SERVICE B          │    │     SERVICE C          │  │
 │    │  (Users API)           │   │ (Orders API)           │    │ (Payments API)         │  │
 │    └────────────────────────┘   └────────────────────────┘    └────────────────────────┘  │
 │                                                                                           │
 └───────────────────────────────────────────────────────────────────────────────────────────┘



# === CONFIGURATION ===

# config/gateway.yaml

services:
  auth-service:
    upstream: "http://auth-service:5000"
    routes:
      - path: "/api/auth/*"
        methods: ["GET", "POST"]
        strip_path: false
        rate_limit:
          requests: 10
          window: 60
        timeout: 5
    health_check:
      path: "/health"
      interval: 10
      timeout: 5

  user-service:
    upstream: "http://user-service:5001"
    routes:
      - path: "/api/users/*"
        methods: ["GET", "POST", "PUT", "DELETE"]
        strip_path: false
        auth_required: true
        rate_limit:
          requests: 100
          window: 60
        cache:
          enabled: true
          ttl: 300
          methods: ["GET"]
    health_check:
      path: "/health"
      interval: 10

  product-service:
    upstream: "http://product-service:5002"
    routes:
      - path: "/api/products/*"
        methods: ["GET", "POST", "PUT", "DELETE"]
        strip_path: false
        rate_limit:
          requests: 200
          window: 60
        cache:
          enabled: true
          ttl: 600
          methods: ["GET"]
    load_balancing:
      strategy: "round_robin"
      instances:
        - "http://product-service-1:5002"
        - "http://product-service-2:5002"
        - "http://product-service-3:5002"

  order-service:
    upstream: "http://order-service:5003"
    routes:
      - path: "/api/orders/*"
        methods: ["GET", "POST", "PUT", "DELETE"]
        strip_path: false
        auth_required: true
        rate_limit:
          requests: 50
          window: 60
        circuit_breaker:
          enabled: true
          threshold: 5
          timeout: 60

global:
  cors:
    allowed_origins: ["*"]
    allowed_methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"]
    allowed_headers: ["Authorization", "Content-Type"]
    max_age: 3600
  
  logging:
    level: "INFO"
    format: "json"
  
  metrics:
    enabled: true
    port: 9090


# === CONFIGURATION PYTHON ===

# app/config.py

import yaml
import os
from pathlib import Path

class Config:
    def __init__(self):
        self.config_path = os.getenv(
            'GATEWAY_CONFIG',
            'config/gateway.yaml'
        )
        self.load_config()
    
    def load_config(self):
        """Charge la configuration depuis YAML"""
        with open(self.config_path, 'r') as f:
            self.data = yaml.safe_load(f)
        
        self.services = self.data.get('services', {})
        self.global_config = self.data.get('global', {})
    
    def get_service(self, service_name):
        """Récupère configuration d'un service"""
        return self.services.get(service_name)
    
    def get_route_for_path(self, path, method):
        """Trouve la route correspondant au path"""
        for service_name, service_config in self.services.items():
            for route in service_config.get('routes', []):
                if self._match_route(path, method, route):
                    return service_name, service_config, route
        
        return None, None, None
    
    def _match_route(self, path, method, route):
        """Vérifie si path/method match la route"""
        import re
        
        # Vérifier méthode HTTP
        if method not in route.get('methods', []):
            return False
        
        # Convertir pattern en regex
        pattern = route['path'].replace('*', '.*')
        pattern = f"^{pattern}$"
        
        return re.match(pattern, path) is not None

config = Config()


# === SERVICE REGISTRY ===

# app/core/service_registry.py

import time
import threading
import requests
from typing import Dict, List, Optional
from dataclasses import dataclass, field

@dataclass
class ServiceInstance:
    """Instance d'un service"""
    host: str
    port: int
    protocol: str = "http"
    healthy: bool = True
    last_check: float = field(default_factory=time.time)
    
    @property
    def url(self):
        return f"{self.protocol}://{self.host}:{self.port}"

@dataclass
class Service:
    """Service avec ses instances"""
    name: str
    instances: List[ServiceInstance] = field(default_factory=list)
    health_check_path: str = "/health"
    health_check_interval: int = 10
    health_check_timeout: int = 5
    
    def add_instance(self, instance: ServiceInstance):
        """Ajoute une instance"""
        self.instances.append(instance)
    
    def get_healthy_instances(self) -> List[ServiceInstance]:
        """Retourne instances en bonne santé"""
        return [i for i in self.instances if i.healthy]
    
    def get_instance(self, strategy='round_robin') -> Optional[ServiceInstance]:
        """Récupère une instance selon stratégie"""
        healthy = self.get_healthy_instances()
        
        if not healthy:
            return None
        
        if strategy == 'round_robin':
            # Simple round-robin
            instance = healthy[0]
            # Déplacer à la fin pour prochain appel
            self.instances.remove(instance)
            self.instances.append(instance)
            return instance
        
        elif strategy == 'random':
            import random
            return random.choice(healthy)
        
        elif strategy == 'least_connections':
            # Simplification: retourne première instance
            return healthy[0]
        
        return healthy[0]

class ServiceRegistry:
    """Registry centralisé des services"""
    
    def __init__(self):
        self.services: Dict[str, Service] = {}
        self._lock = threading.RLock()
        self._health_check_thread = None
        self._running = False
    
    def register_service(
        self,
        name: str,
        upstream: str,
        health_check_path: str = "/health",
        health_check_interval: int = 10
    ):
        """Enregistre un service"""
        with self._lock:
            if name not in self.services:
                self.services[name] = Service(
                    name=name,
                    health_check_path=health_check_path,
                    health_check_interval=health_check_interval
                )
            
            # Parser l'upstream
            from urllib.parse import urlparse
            parsed = urlparse(upstream)
            
            instance = ServiceInstance(
                host=parsed.hostname,
                port=parsed.port or 80,
                protocol=parsed.scheme
            )
            
            self.services[name].add_instance(instance)
    
    def get_service(self, name: str) -> Optional[Service]:
        """Récupère un service"""
        with self._lock:
            return self.services.get(name)
    
    def get_service_url(
        self,
        name: str,
        strategy: str = 'round_robin'
    ) -> Optional[str]:
        """Récupère URL d'une instance du service"""
        service = self.get_service(name)
        
        if not service:
            return None
        
        instance = service.get_instance(strategy)
        
        if not instance:
            return None
        
        return instance.url
    
    def start_health_checks(self):
        """Démarre les health checks"""
        if self._running:
            return
        
        self._running = True
        self._health_check_thread = threading.Thread(
            target=self._health_check_loop,
            daemon=True
        )
        self._health_check_thread.start()
    
    def stop_health_checks(self):
        """Arrête les health checks"""
        self._running = False
        if self._health_check_thread:
            self._health_check_thread.join(timeout=5)
    
    def _health_check_loop(self):
        """Boucle de health checks"""
        while self._running:
            with self._lock:
                for service in self.services.values():
                    for instance in service.instances:
                        self._check_instance_health(service, instance)
            
            time.sleep(5)  # Check toutes les 5 secondes
    
    def _check_instance_health(self, service: Service, instance: ServiceInstance):
        """Vérifie la santé d'une instance"""
        now = time.time()
        
        # Vérifier si besoin de check
        if now - instance.last_check < service.health_check_interval:
            return
        
        instance.last_check = now
        
        try:
            url = f"{instance.url}{service.health_check_path}"
            response = requests.get(
                url,
                timeout=service.health_check_timeout
            )
            
            instance.healthy = response.status_code == 200
            
            if not instance.healthy:
                print(f"[ATTENTION]  Instance {instance.url} unhealthy: {response.status_code}")
        
        except Exception as e:
            instance.healthy = False
            print(f"[X] Health check failed for {instance.url}: {e}")

# Instance globale
registry = ServiceRegistry()


# === PROXY HTTP ===

# app/core/proxy.py

import requests
from flask import request, Response
from typing import Optional, Dict, Any
import time

class HTTPProxy:
    """Proxy HTTP pour forwarding des requêtes"""
    
    def __init__(self):
        self.session = requests.Session()
        # Pool de connexions
        adapter = requests.adapters.HTTPAdapter(
            pool_connections=100,
            pool_maxsize=100
        )
        self.session.mount('http://', adapter)
        self.session.mount('https://', adapter)
    
    def forward_request(
        self,
        target_url: str,
        method: str,
        path: str,
        headers: Dict[str, str],
        body: Optional[bytes] = None,
        params: Optional[Dict] = None,
        timeout: int = 30,
        strip_path: bool = False
    ) -> Response:
        """
        Forward une requête vers le service cible
        
        Args:
            target_url: URL du service (ex: http://user-service:5001)
            method: Méthode HTTP (GET, POST, etc.)
            path: Path de la requête (ex: /api/users/123)
            headers: Headers à forwarder
            body: Corps de la requête
            params: Query parameters
            timeout: Timeout en secondes
            strip_path: Si True, retire le prefix du path
        
        Returns:
            Response Flask
        """
        
        # Construire URL complète
        if strip_path:
            # Retirer prefix (ex: /api/users/123 -> /123)
            path = self._strip_prefix(path)
        
        full_url = f"{target_url.rstrip('/')}{path}"
        
        # Nettoyer headers
        forwarded_headers = self._prepare_headers(headers)
        
        # Timer pour métriques
        start_time = time.time()
        
        try:
            # Faire la requête
            response = self.session.request(
                method=method,
                url=full_url,
                headers=forwarded_headers,
                data=body,
                params=params,
                timeout=timeout,
                allow_redirects=False,
                stream=True  # Pour supporter grandes réponses
            )
            
            # Mesurer latence
            latency = time.time() - start_time
            
            # Créer réponse Flask
            flask_response = self._create_response(response)
            
            # Ajouter headers custom
            flask_response.headers['X-Gateway-Latency'] = f"{latency:.3f}s"
            flask_response.headers['X-Upstream-Service'] = target_url
            
            return flask_response
        
        except requests.exceptions.Timeout:
            return Response(
                '{"error": "Service timeout"}',
                status=504,
                mimetype='application/json'
            )
        
        except requests.exceptions.ConnectionError:
            return Response(
                '{"error": "Service unavailable"}',
                status=503,
                mimetype='application/json'
            )
        
        except Exception as e:
            print(f"Proxy error: {e}")
            return Response(
                f'{{"error": "Gateway error: {str(e)}"}}',
                status=500,
                mimetype='application/json'
            )
    
    def _prepare_headers(self, headers: Dict[str, str]) -> Dict[str, str]:
        """Prépare les headers à forwarder"""
        # Headers à ne pas forwarder
        skip_headers = {
            'host',
            'connection',
            'keep-alive',
            'proxy-authenticate',
            'proxy-authorization',
            'te',
            'trailers',
            'transfer-encoding',
            'upgrade'
        }
        
        forwarded = {}
        
        for key, value in headers.items():
            if key.lower() not in skip_headers:
                forwarded[key] = value
        
        # Ajouter X-Forwarded headers
        forwarded['X-Forwarded-For'] = request.remote_addr
        forwarded['X-Forwarded-Proto'] = request.scheme
        forwarded['X-Forwarded-Host'] = request.host
        
        return forwarded
    
    def _strip_prefix(self, path: str) -> str:
        """Retire le prefix du path"""
        # Ex: /api/users/123 -> /123
        parts = path.split('/', 3)
        if len(parts) >= 4:
            return '/' + parts[3]
        return '/'
    
    def _create_response(self, upstream_response: requests.Response) -> Response:
        """Crée une Response Flask depuis requests.Response"""
        
        # Headers à exclure
        excluded_headers = {
            'content-encoding',
            'content-length',
            'transfer-encoding',
            'connection'
        }
        
        headers = [
            (name, value)
            for name, value in upstream_response.raw.headers.items()
            if name.lower() not in excluded_headers
        ]
        
        # Créer réponse
        response = Response(
            upstream_response.content,
            status=upstream_response.status_code,
            headers=headers
        )
        
        return response

# Instance globale
proxy = HTTPProxy()


# === ROUTER PRINCIPAL ===

# app/core/router.py

from flask import request, jsonify
from app.config import config
from app.core.proxy import proxy
from app.core.service_registry import registry

class Router:
    """Routeur principal du gateway"""
    
    def __init__(self):
        self.config = config
    
    def route_request(self):
        """Route la requête vers le service approprié"""
        
        path = request.path
        method = request.method
        
        # Trouver la route correspondante
        service_name, service_config, route_config = \
            self.config.get_route_for_path(path, method)
        
        if not service_name:
            return jsonify({
                'error': 'Route not found',
                'path': path,
                'method': method
            }), 404
        
        # Récupérer URL du service
        strategy = service_config.get('load_balancing', {}).get(
            'strategy',
            'round_robin'
        )
        
        service_url = registry.get_service_url(service_name, strategy)
        
        if not service_url:
            return jsonify({
                'error': 'Service unavailable',
                'service': service_name
            }), 503
        
        # Forwarder la requête
        return proxy.forward_request(
            target_url=service_url,
            method=method,
            path=path,
            headers=dict(request.headers),
            body=request.get_data(),
            params=request.args,
            timeout=route_config.get('timeout', 30),
            strip_path=route_config.get('strip_path', False)
        )

router = Router()


# === MIDDLEWARE: AUTHENTICATION ===

# app/middleware/auth.py

from flask import request, jsonify, g
from functools import wraps
import jwt
import requests

class AuthMiddleware:
    """Middleware d'authentification"""
    
    def __init__(self, auth_service_url, jwt_secret=None):
        self.auth_service_url = auth_service_url
        self.jwt_secret = jwt_secret
    
    def __call__(self, f):
        """Décorateur pour protéger les routes"""
        @wraps(f)
        def decorated_function(*args, **kwargs):
            # Récupérer token
            auth_header = request.headers.get('Authorization')
            
            if not auth_header:
                return jsonify({'error': 'Missing authorization header'}), 401
            
            try:
                # Format: "Bearer <token>"
                token = auth_header.split(' ')[1]
                
                # Valider token
                user = self.validate_token(token)
                
                if not user:
                    return jsonify({'error': 'Invalid token'}), 401
                
                # Stocker user dans context
                g.user = user
                
                return f(*args, **kwargs)
            
            except Exception as e:
                return jsonify({'error': f'Authentication failed: {str(e)}'}), 401
        
        return decorated_function
    
    def validate_token(self, token):
        """Valide un JWT token"""
        
        if self.jwt_secret:
            # Validation locale
            try:
                payload = jwt.decode(
                    token,
                    self.jwt_secret,
                    algorithms=['HS256']
                )
                return payload
            except jwt.ExpiredSignatureError:
                return None
            except jwt.InvalidTokenError:
                return None
        
        else:
            # Validation via auth service
            try:
                response = requests.post(
                    f"{self.auth_service_url}/api/auth/verify",
                    headers={'Authorization': f'Bearer {token}'},
                    timeout=5
                )
                
                if response.status_code == 200:
                    return response.json()
                
                return None
            
            except:
                return None

# Instance globale
auth_middleware = AuthMiddleware(
    auth_service_url='http://auth-service:5000',
    jwt_secret='your-jwt-secret-key'  # Ou None pour validation via service
)


# === MIDDLEWARE: RATE LIMITING ===

# app/middleware/rate_limit.py

import time
import redis
from flask import request, jsonify
from functools import wraps
from typing import Optional

class RateLimiter:
    """Rate limiter basé sur Redis"""
    
    def __init__(self, redis_url='redis://localhost:6379'):
        self.redis_client = redis.from_url(redis_url)
    
    def limit(
        self,
        requests_per_window: int,
        window_seconds: int,
        key_func=None
    ):
        """
        Décorateur de rate limiting
        
        Args:
            requests_per_window: Nombre de requêtes autorisées
            window_seconds: Fenêtre de temps en secondes
            key_func: Fonction pour générer la clé (défaut: IP)
        """
        def decorator(f):
            @wraps(f)
            def decorated_function(*args, **kwargs):
                # Générer clé
                if key_func:
                    key = key_func()
                else:
                    key = self._get_ip_key()
                
                # Vérifier limite
                allowed, remaining, reset_time = self._check_limit(
                    key,
                    requests_per_window,
                    window_seconds
                )
                
                if not allowed:
                    response = jsonify({
                        'error': 'Rate limit exceeded',
                        'retry_after': reset_time
                    })
                    response.status_code = 429
                    response.headers['X-RateLimit-Limit'] = str(requests_per_window)
                    response.headers['X-RateLimit-Remaining'] = '0'
                    response.headers['X-RateLimit-Reset'] = str(reset_time)
                    response.headers['Retry-After'] = str(reset_time)
                    return response
                
                # Exécuter fonction
                response = f(*args, **kwargs)
                
                # Ajouter headers rate limit
                if hasattr(response, 'headers'):
                    response.headers['X-RateLimit-Limit'] = str(requests_per_window)
                    response.headers['X-RateLimit-Remaining'] = str(remaining)
                    response.headers['X-RateLimit-Reset'] = str(reset_time)
                
                return response
            
            return decorated_function
        return decorator
    
    def _get_ip_key(self) -> str:
        """Génère clé basée sur IP"""
        ip = request.remote_addr
        path = request.path
        return f"ratelimit:{ip}:{path}"
    
    def _check_limit(
        self,
        key: str,
        limit: int,
        window: int
    ) -> tuple[bool, int, int]:
        """
        Vérifie la limite
        
        Returns:
            (allowed, remaining, reset_time)
        """
        now = int(time.time())
        window_start = now - window
        
        # Utiliser sorted set Redis
        pipe = self.redis_client.pipeline()
        
        # Retirer anciennes entrées
        pipe.zremrangebyscore(key, 0, window_start)
        
        # Compter entrées dans fenêtre
        pipe.zcard(key)
        
        # Ajouter nouvelle entrée
        pipe.zadd(key, {str(now): now})
        
        # Définir expiration
        pipe.expire(key, window)
        
        results = pipe.execute()
        
        current_count = results[1]
        
        allowed = current_count < limit
        remaining = max(0, limit - current_count - 1)
        reset_time = now + window
        
        return allowed, remaining, reset_time

# Instance globale
rate_limiter = RateLimiter()


# === MIDDLEWARE: CACHING ===

# app/middleware/cache.py

import hashlib
import json
import redis
from flask import request, Response
from functools import wraps

class CacheMiddleware:
    """Middleware de caching"""
    
    def __init__(self, redis_url='redis://localhost:6379'):
        self.redis_client = redis.from_url(redis_url)
    
    def cache(self, ttl=300, methods=None):
        """
        Décorateur de cache
        
        Args:
            ttl: Time to live en secondes
            methods: Méthodes HTTP à cacher (défaut: ['GET'])
        """
        if methods is None:
            methods = ['GET']
        
        def decorator(f):
            @wraps(f)
            def decorated_function(*args, **kwargs):
                # Vérifier si méthode cacheable
                if request.method not in methods:
                    return f(*args, **kwargs)
                
                # Générer clé de cache
                cache_key = self._generate_cache_key()
                
                # Vérifier cache
                cached = self.redis_client.get(cache_key)
                
                if cached:
                    # Cache hit
                    data = json.loads(cached)
                    response = Response(
                        data['body'],
                        status=data['status'],
                        headers=data['headers']
                    )
                    response.headers['X-Cache'] = 'HIT'
                    return response
                
                # Cache miss - exécuter fonction
                response = f(*args, **kwargs)
                
                # Mettre en cache si succès
                if hasattr(response, 'status_code') and 200 <= response.status_code < 300:
                    cache_data = {
                        'body': response.get_data(as_text=True),
                        'status': response.status_code,
                        'headers': dict(response.headers)
                    }
                    
                    self.redis_client.setex(
                        cache_key,
                        ttl,
                        json.dumps(cache_data)
                    )
                    
                    response.headers['X-Cache'] = 'MISS'
                
                return response
            
            return decorated_function
        return decorator
    
    def _generate_cache_key(self) -> str:
        """Génère clé de cache unique"""
        # Inclure: method, path, query params, headers pertinents
                key_parts = [
            request.method,
            request.path,
            request.query_string.decode('utf-8'),
            request.headers.get('Accept', ''),
        ]
        
        key_string = '|'.join(key_parts)
        
        # Hash pour clé courte
        hash_key = hashlib.sha256(key_string.encode()).hexdigest()
        
        return f"cache:{hash_key}"
    
    def invalidate(self, pattern='*'):
        """Invalide le cache selon pattern"""
        keys = self.redis_client.keys(f"cache:{pattern}")
        if keys:
            self.redis_client.delete(*keys)

# Instance globale
cache_middleware = CacheMiddleware()


# === MIDDLEWARE: CIRCUIT BREAKER ===

# app/middleware/circuit_breaker.py

import time
import threading
from enum import Enum
from typing import Dict, Callable
from functools import wraps
from flask import jsonify

class CircuitState(Enum):
    """États du circuit breaker"""
    CLOSED = "closed"       # Normal, requêtes passent
    OPEN = "open"           # Circuit ouvert, rejette requêtes
    HALF_OPEN = "half_open" # Test si service récupéré

class CircuitBreaker:
    """
    Circuit Breaker pour protéger contre services défaillants
    
    États:
    - CLOSED: Tout va bien, requêtes passent
    - OPEN: Trop d'échecs, requêtes rejetées
    - HALF_OPEN: Test après timeout pour voir si service récupéré
    """
    
    def __init__(
        self,
        failure_threshold: int = 5,
        timeout: int = 60,
        expected_exception=Exception
    ):
        """
        Args:
            failure_threshold: Nombre d'échecs avant ouverture
            timeout: Temps avant test de récupération (secondes)
            expected_exception: Type d'exception à compter
        """
        self.failure_threshold = failure_threshold
        self.timeout = timeout
        self.expected_exception = expected_exception
        
        self._failure_count = 0
        self._last_failure_time = None
        self._state = CircuitState.CLOSED
        self._lock = threading.RLock()
    
    def __call__(self, func: Callable):
        """Décorateur"""
        @wraps(func)
        def wrapper(*args, **kwargs):
            with self._lock:
                # Vérifier état
                if self._state == CircuitState.OPEN:
                    # Vérifier si timeout écoulé
                    if self._should_attempt_reset():
                        self._state = CircuitState.HALF_OPEN
                        print(f"- Circuit HALF_OPEN for {func.__name__}")
                    else:
                        # Rejeter requête
                        print(f"[HEAVY_LARGE_CIRCLE] Circuit OPEN for {func.__name__}, rejecting request")
                        return jsonify({
                            'error': 'Service temporarily unavailable',
                            'circuit_state': 'open',
                            'retry_after': self._get_retry_after()
                        }), 503
            
            # Exécuter fonction
            try:
                result = func(*args, **kwargs)
                
                # Succès
                with self._lock:
                    self._on_success()
                
                return result
            
            except self.expected_exception as e:
                # Échec
                with self._lock:
                    self._on_failure()
                
                raise
    
        return wrapper
    
    def _on_success(self):
        """Appelé lors d'un succès"""
        if self._state == CircuitState.HALF_OPEN:
            # Service récupéré
            self._state = CircuitState.CLOSED
            self._failure_count = 0
            print(f"[OK] Circuit CLOSED, service recovered")
        
        # Reset compteur en état CLOSED
        if self._state == CircuitState.CLOSED:
            self._failure_count = 0
    
    def _on_failure(self):
        """Appelé lors d'un échec"""
        self._failure_count += 1
        self._last_failure_time = time.time()
        
        if self._failure_count >= self.failure_threshold:
            self._state = CircuitState.OPEN
            print(f"[ROUGE] Circuit OPEN after {self._failure_count} failures")
    
    def _should_attempt_reset(self) -> bool:
        """Vérifie si assez de temps écoulé pour tester"""
        if not self._last_failure_time:
            return True
        
        return (time.time() - self._last_failure_time) >= self.timeout
    
    def _get_retry_after(self) -> int:
        """Retourne temps avant prochain essai"""
        if not self._last_failure_time:
            return 0
        
        elapsed = time.time() - self._last_failure_time
        return max(0, int(self.timeout - elapsed))
    
    @property
    def state(self) -> CircuitState:
        """État actuel du circuit"""
        return self._state

class CircuitBreakerManager:
    """Gestionnaire de circuit breakers par service"""
    
    def __init__(self):
        self.breakers: Dict[str, CircuitBreaker] = {}
    
    def get_breaker(
        self,
        service_name: str,
        failure_threshold: int = 5,
        timeout: int = 60
    ) -> CircuitBreaker:
        """Récupère ou crée un circuit breaker"""
        if service_name not in self.breakers:
            self.breakers[service_name] = CircuitBreaker(
                failure_threshold=failure_threshold,
                timeout=timeout
            )
        
        return self.breakers[service_name]
    
    def get_status(self) -> Dict:
        """Statut de tous les circuit breakers"""
        return {
            name: breaker.state.value
            for name, breaker in self.breakers.items()
        }

# Instance globale
circuit_breaker_manager = CircuitBreakerManager()


# === MIDDLEWARE: CORS ===

# app/middleware/cors.py

from flask import request, make_response

class CORSMiddleware:
    """Middleware CORS"""
    
    def __init__(
        self,
        allowed_origins=None,
        allowed_methods=None,
        allowed_headers=None,
        max_age=3600
    ):
        self.allowed_origins = allowed_origins or ['*']
        self.allowed_methods = allowed_methods or ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS']
        self.allowed_headers = allowed_headers or ['*']
        self.max_age = max_age
    
    def add_cors_headers(self, response):
        """Ajoute headers CORS à la réponse"""
        
        origin = request.headers.get('Origin')
        
        # Vérifier origin autorisée
        if self._is_origin_allowed(origin):
            response.headers['Access-Control-Allow-Origin'] = origin or '*'
        
        response.headers['Access-Control-Allow-Methods'] = ', '.join(self.allowed_methods)
        response.headers['Access-Control-Allow-Headers'] = ', '.join(self.allowed_headers)
        response.headers['Access-Control-Max-Age'] = str(self.max_age)
        response.headers['Access-Control-Allow-Credentials'] = 'true'
        
        return response
    
    def handle_preflight(self):
        """Gère les requêtes OPTIONS (preflight)"""
        response = make_response('', 204)
        return self.add_cors_headers(response)
    
    def _is_origin_allowed(self, origin):
        """Vérifie si origin autorisée"""
        if '*' in self.allowed_origins:
            return True
        
        return origin in self.allowed_origins

# Instance globale
cors_middleware = CORSMiddleware()


# === MIDDLEWARE: LOGGING ===

# app/middleware/logging.py

import time
import json
import logging
from flask import request, g
from functools import wraps

class RequestLogger:
    """Logger de requêtes"""
    
    def __init__(self):
        self.logger = logging.getLogger('gateway.requests')
        self.logger.setLevel(logging.INFO)
        
        # Format JSON
        handler = logging.StreamHandler()
        handler.setFormatter(
            logging.Formatter('%(message)s')
        )
        self.logger.addHandler(handler)
    
    def __call__(self, f):
        """Décorateur pour logger les requêtes"""
        @wraps(f)
        def decorated_function(*args, **kwargs):
            # Timestamp début
            g.start_time = time.time()
            
            # Exécuter fonction
            response = f(*args, **kwargs)
            
            # Logger après exécution
            self._log_request(response)
            
            return response
        
        return decorated_function
    
    def _log_request(self, response):
        """Enregistre la requête"""
        duration = time.time() - g.start_time
        
        log_data = {
            'timestamp': time.time(),
            'method': request.method,
            'path': request.path,
            'query_string': request.query_string.decode('utf-8'),
            'remote_addr': request.remote_addr,
            'user_agent': request.headers.get('User-Agent', ''),
            'status_code': response.status_code if hasattr(response, 'status_code') else 0,
            'duration_ms': round(duration * 1000, 2),
            'request_size': request.content_length or 0,
            'response_size': len(response.get_data()) if hasattr(response, 'get_data') else 0,
        }
        
        # Ajouter user si authentifié
        if hasattr(g, 'user'):
            log_data['user_id'] = g.user.get('id')
        
        self.logger.info(json.dumps(log_data))

# Instance globale
request_logger = RequestLogger()


# === PLUGINS: REQUEST TRANSFORMATION ===

# app/plugins/request_transform.py

from flask import request
import json

class RequestTransformer:
    """Transforme les requêtes avant forwarding"""
    
    def __init__(self):
        self.transformers = {}
    
    def register(self, name, transformer_func):
        """Enregistre un transformer"""
        self.transformers[name] = transformer_func
    
    def transform(self, name, request_data):
        """Applique une transformation"""
        if name in self.transformers:
            return self.transformers[name](request_data)
        return request_data

# Transformers prédéfinis

def add_api_key(request_data):
    """Ajoute une API key aux headers"""
    headers = request_data.get('headers', {})
    headers['X-API-Key'] = 'your-api-key'
    request_data['headers'] = headers
    return request_data

def rename_fields(field_mapping):
    """Renomme des champs du body"""
    def transformer(request_data):
        try:
            body = json.loads(request_data.get('body', '{}'))
            
            for old_name, new_name in field_mapping.items():
                if old_name in body:
                    body[new_name] = body.pop(old_name)
            
            request_data['body'] = json.dumps(body)
        except:
            pass
        
        return request_data
    
    return transformer

def add_timestamp(request_data):
    """Ajoute timestamp à la requête"""
    try:
        body = json.loads(request_data.get('body', '{}'))
        body['_timestamp'] = time.time()
        request_data['body'] = json.dumps(body)
    except:
        pass
    
    return request_data

# Instance globale
request_transformer = RequestTransformer()

# Enregistrer transformers
request_transformer.register('add_api_key', add_api_key)
request_transformer.register('add_timestamp', add_timestamp)


# === PLUGINS: RESPONSE TRANSFORMATION ===

# app/plugins/response_transform.py

import json

class ResponseTransformer:
    """Transforme les réponses après forwarding"""
    
    def __init__(self):
        self.transformers = {}
    
    def register(self, name, transformer_func):
        """Enregistre un transformer"""
        self.transformers[name] = transformer_func
    
    def transform(self, name, response):
        """Applique une transformation"""
        if name in self.transformers:
            return self.transformers[name](response)
        return response

# Transformers prédéfinis

def add_metadata(response):
    """Ajoute metadata à la réponse"""
    try:
        data = json.loads(response.get_data(as_text=True))
        
        if isinstance(data, dict):
            data['_meta'] = {
                'gateway_version': '1.0.0',
                'timestamp': time.time()
            }
            
            response.set_data(json.dumps(data))
    except:
        pass
    
    return response

def wrap_response(response):
    """Enveloppe la réponse dans un format standard"""
    try:
        data = json.loads(response.get_data(as_text=True))
        
        wrapped = {
            'success': 200 <= response.status_code < 300,
            'status_code': response.status_code,
            'data': data,
            'timestamp': time.time()
        }
        
        response.set_data(json.dumps(wrapped))
    except:
        pass
    
    return response

def remove_sensitive_fields(fields):
    """Retire des champs sensibles de la réponse"""
    def transformer(response):
        try:
            data = json.loads(response.get_data(as_text=True))
            
            for field in fields:
                if field in data:
                    del data[field]
            
            response.set_data(json.dumps(data))
        except:
            pass
        
        return response
    
    return transformer

# Instance globale
response_transformer = ResponseTransformer()

# Enregistrer transformers
response_transformer.register('add_metadata', add_metadata)
response_transformer.register('wrap_response', wrap_response)
response_transformer.register(
    'remove_passwords',
    remove_sensitive_fields(['password', 'password_hash'])
)


# === PLUGINS: LOAD BALANCER ===

# app/plugins/load_balancer.py

import random
from typing import List, Optional
from dataclasses import dataclass
import time

@dataclass
class Backend:
    """Backend server"""
    url: str
    weight: int = 1
    current_connections: int = 0
    total_requests: int = 0
    healthy: bool = True

class LoadBalancer:
    """Load balancer avec plusieurs stratégies"""
    
    def __init__(self, backends: List[str]):
        self.backends = [Backend(url=url) for url in backends]
        self.current_index = 0
    
    def get_backend(self, strategy='round_robin') -> Optional[str]:
        """Sélectionne un backend selon stratégie"""
        
        healthy_backends = [b for b in self.backends if b.healthy]
        
        if not healthy_backends:
            return None
        
        if strategy == 'round_robin':
            return self._round_robin(healthy_backends)
        
        elif strategy == 'random':
            return self._random(healthy_backends)
        
        elif strategy == 'least_connections':
            return self._least_connections(healthy_backends)
        
        elif strategy == 'weighted_round_robin':
            return self._weighted_round_robin(healthy_backends)
        
        elif strategy == 'ip_hash':
            return self._ip_hash(healthy_backends)
        
        return healthy_backends[0].url
    
    def _round_robin(self, backends: List[Backend]) -> str:
        """Round-robin simple"""
        backend = backends[self.current_index % len(backends)]
        self.current_index += 1
        return backend.url
    
    def _random(self, backends: List[Backend]) -> str:
        """Sélection aléatoire"""
        return random.choice(backends).url
    
    def _least_connections(self, backends: List[Backend]) -> str:
        """Moins de connexions actives"""
        backend = min(backends, key=lambda b: b.current_connections)
        return backend.url
    
    def _weighted_round_robin(self, backends: List[Backend]) -> str:
        """Round-robin pondéré"""
        # Créer liste avec répétitions selon poids
        weighted_list = []
        for backend in backends:
            weighted_list.extend([backend] * backend.weight)
        
        backend = weighted_list[self.current_index % len(weighted_list)]
        self.current_index += 1
        return backend.url
    
    def _ip_hash(self, backends: List[Backend]) -> str:
        """Hash de l'IP client pour sticky sessions"""
        from flask import request
        
        ip = request.remote_addr
        hash_value = hash(ip)
        index = hash_value % len(backends)
        
        return backends[index].url
    
    def mark_backend_down(self, url: str):
        """Marque un backend comme down"""
        for backend in self.backends:
            if backend.url == url:
                backend.healthy = False
                break
    
    def mark_backend_up(self, url: str):
        """Marque un backend comme up"""
        for backend in self.backends:
            if backend.url == url:
                backend.healthy = True
                break
    
    def increment_connections(self, url: str):
        """Incrémente compteur connexions"""
        for backend in self.backends:
            if backend.url == url:
                backend.current_connections += 1
                backend.total_requests += 1
                break
    
    def decrement_connections(self, url: str):
        """Décrémente compteur connexions"""
        for backend in self.backends:
            if backend.url == url:
                backend.current_connections = max(0, backend.current_connections - 1)
                break
    
    def get_stats(self) -> dict:
        """Statistiques des backends"""
        return {
            'backends': [
                {
                    'url': b.url,
                    'healthy': b.healthy,
                    'current_connections': b.current_connections,
                    'total_requests': b.total_requests,
                    'weight': b.weight
                }
                for b in self.backends
            ]
        }


# === UTILS: METRICS (PROMETHEUS) ===

# app/utils/metrics.py

from prometheus_client import Counter, Histogram, Gauge, generate_latest, CONTENT_TYPE_LATEST
from flask import Response
import time

# Métriques

# Compteur de requêtes
requests_total = Counter(
    'gateway_requests_total',
    'Total requests through gateway',
    ['method', 'path', 'status']
)

# Latence des requêtes
request_duration = Histogram(
    'gateway_request_duration_seconds',
    'Request duration in seconds',
    ['method', 'path', 'service']
)

# Taille des requêtes/réponses
request_size = Histogram(
    'gateway_request_size_bytes',
    'Request size in bytes',
    ['method', 'path']
)

response_size = Histogram(
    'gateway_response_size_bytes',
    'Response size in bytes',
    ['method', 'path', 'status']
)

# Services actifs
active_services = Gauge(
    'gateway_active_services',
    'Number of active services',
    ['service']
)

# Circuit breakers
circuit_breaker_state = Gauge(
    'gateway_circuit_breaker_state',
    'Circuit breaker state (0=closed, 1=open, 2=half-open)',
    ['service']
)

class MetricsCollector:
    """Collecteur de métriques"""
    
    @staticmethod
    def record_request(method, path, status, duration, req_size, resp_size):
        """Enregistre une requête"""
        requests_total.labels(method=method, path=path, status=status).inc()
        request_size.labels(method=method, path=path).observe(req_size)
        response_size.labels(method=method, path=path, status=status).observe(resp_size)
    
    @staticmethod
    def record_duration(method, path, service, duration):
        """Enregistre la durée"""
        request_duration.labels(method=method, path=path, service=service).observe(duration)
    
    @staticmethod
    def set_active_services(service, count):
        """Définit nombre de services actifs"""
        active_services.labels(service=service).set(count)
    
    @staticmethod
    def set_circuit_breaker_state(service, state):
        """Définit état circuit breaker"""
        # 0=closed, 1=open, 2=half-open
        state_map = {'closed': 0, 'open': 1, 'half_open': 2}
        circuit_breaker_state.labels(service=service).set(state_map.get(state, 0))
    
    @staticmethod
    def get_metrics():
        """Retourne métriques Prometheus"""
        return Response(generate_latest(), mimetype=CONTENT_TYPE_LATEST)

metrics_collector = MetricsCollector()


# === UTILS: HEALTH CHECKS ===

# app/utils/health.py

import requests
from typing import Dict, List
from dataclasses import dataclass
import time

@dataclass
class HealthStatus:
    """Statut de santé d'un service"""
    service_name: str
    healthy: bool
    response_time: float
    last_check: float
    error: str = None

class HealthChecker:
    """Vérificateur de santé des services"""
    
    def __init__(self):
        self.statuses: Dict[str, HealthStatus] = {}
    
    def check_service(
        self,
        service_name: str,
        url: str,
        timeout: int = 5
    ) -> HealthStatus:
        """Vérifie la santé d'un service"""
        
        start_time = time.time()
        
        try:
            response = requests.get(url, timeout=timeout)
            response_time = time.time() - start_time
            
            status = HealthStatus(
                service_name=service_name,
                healthy=response.status_code == 200,
                response_time=response_time,
                last_check=time.time(),
                error=None if response.status_code == 200 else f"Status {response.status_code}"
            )
        
        except requests.exceptions.Timeout:
            status = HealthStatus(
                service_name=service_name,
                healthy=False,
                response_time=timeout,
                last_check=time.time(),
                error="Timeout"
            )
        
        except Exception as e:
            status = HealthStatus(
                service_name=service_name,
                healthy=False,
                response_time=time.time() - start_time,
                last_check=time.time(),
                error=str(e)
            )
        
        self.statuses[service_name] = status
        return status
    
    def check_all_services(self, services: Dict[str, str]) -> List[HealthStatus]:
        """Vérifie tous les services"""
        return [
            self.check_service(name, url)
            for name, url in services.items()
        ]
    
    def get_overall_health(self) -> Dict:
        """Santé globale du gateway"""
        if not self.statuses:
            return {'status': 'unknown', 'services': {}}
        
        all_healthy = all(s.healthy for s in self.statuses.values())
        
        return {
            'status': 'healthy' if all_healthy else 'unhealthy',
            'timestamp': time.time(),
            'services': {
                name: {
                    'healthy': status.healthy,
                    'response_time': status.response_time,
                    'last_check': status.last_check,
                    'error': status.error
                }
                for name, status in self.statuses.items()
            }
        }

health_checker = HealthChecker()


# === APPLICATION FLASK PRINCIPALE ===

# app/__init__.py

from flask import Flask, request, jsonify, g
import yaml
import time

from app.config import config
from app.core.router import router
from app.core.service_registry import registry
from app.middleware.auth import auth_middleware
from app.middleware.rate_limit import rate_limiter
from app.middleware.cache import cache_middleware
from app.middleware.cors import cors_middleware
from app.middleware.logging import request_logger
from app.middleware.circuit_breaker import circuit_breaker_manager
from app.utils.metrics import metrics_collector
from app.utils.health import health_checker

def create_app():
    """Factory pour créer l'application"""
    
    app = Flask(__name__)
    
    # Charger configuration
    app.config.from_object(config)
    
    # Initialiser service registry
    _init_service_registry()
    
    # CORS - Traiter OPTIONS en premier
    @app.before_request
    def handle_preflight():
        if request.method == 'OPTIONS':
            return cors_middleware.handle_preflight()
    
    # Ajouter headers CORS à toutes les réponses
    @app.after_request
    def add_cors_headers(response):
        return cors_middleware.add_cors_headers(response)
    
    # === ROUTES PRINCIPALES ===
    
    @app.route('/<path:path>', methods=['GET', 'POST', 'PUT', 'DELETE', 'PATCH'])
    @request_logger
    def proxy_request(path):
        """Route principale - proxy vers services"""
        
        # Récupérer config de route
        service_name, service_config, route_config = \
            config.get_route_for_path(f'/{path}', request.method)
        
        if not service_name:
            return jsonify({'error': 'Route not found'}), 404
        
        # Vérifier authentication si requis
        if route_config.get('auth_required', False):
            token = request.headers.get('Authorization', '').replace('Bearer ', '')
            if not token:
                return jsonify({'error': 'Authentication required'}), 401
            
            user = auth_middleware.validate_token(token)
            if not user:
                return jsonify({'error': 'Invalid token'}), 401
            
            g.user = user
        
        # Appliquer rate limiting
        rate_limit_config = route_config.get('rate_limit', {})
        if rate_limit_config:
            limiter = rate_limiter.limit(
                requests_per_window=rate_limit_config.get('requests', 100),
                window_seconds=rate_limit_config.get('window', 60)
            )
            
            # Vérifier limite
            check_result = limiter(lambda: None)
            if check_result:
                return check_result
        
        # Vérifier cache
        cache_config = route_config.get('cache', {})
        if cache_config.get('enabled') and request.method in cache_config.get('methods', ['GET']):
            cache_key = cache_middleware._generate_cache_key()
            cached = cache_middleware.redis_client.get(cache_key)
            
            if cached:
                import json
                data = json.loads(cached)
                from flask import Response
                response = Response(
                    data['body'],
                    status=data['status'],
                    headers=data['headers']
                )
                response.headers['X-Cache'] = 'HIT'
                return response
        
        # Circuit breaker
        cb_config = route_config.get('circuit_breaker', {})
        if cb_config.get('enabled'):
            breaker = circuit_breaker_manager.get_breaker(
                service_name,
                failure_threshold=cb_config.get('threshold', 5),
                timeout=cb_config.get('timeout', 60)
            )
            
            if breaker.state.value == 'open':
                return jsonify({
                    'error': 'Service temporarily unavailable',
                    'circuit_state': 'open'
                }), 503
        
        # Router la requête
        start_time = time.time()
        
        try:
            response = router.route_request()
            
            # Métriques
            duration = time.time() - start_time
            metrics_collector.record_request(
                method=request.method,
                path=f'/{path}',
                status=response.status_code if hasattr(response, 'status_code') else 0,
                duration=duration,
                req_size=request.content_length or 0,
                resp_size=len(response.get_data()) if hasattr(response, 'get_data') else 0
            )
            
            # Cache la réponse si configuré
            if cache_config.get('enabled') and hasattr(response, 'status_code'):
                if 200 <= response.status_code < 300:
                    cache_middleware.redis_client.setex(
                        cache_key,
                        cache_config.get('ttl', 300),
                        json.dumps({
                            'body': response.get_data(as_text=True),
                            'status': response.status_code,
                            'headers': dict(response.headers)
                        })
                    )
            
            return response
        
        except Exception as e:
            print(f"Error routing request: {e}")
            return jsonify({'error': 'Internal gateway error'}), 500
    
    # === ROUTES ADMIN ===
    
    @app.route('/admin/health', methods=['GET'])
    def admin_health():
        """Health check du gateway"""
        return jsonify(health_checker.get_overall_health())
    
    @app.route('/admin/services', methods=['GET'])
    def admin_services():
        """Liste des services enregistrés"""
        services = {}
        for name, service in registry.services.items():
            services[name] = {
                'instances': [
                    {
                        'url': inst.url,
                        'healthy': inst.healthy,
                        'last_check': inst.last_check
                    }
                    for inst in service.instances
                ]
            }
        
        return jsonify(services)
    
    @app.route('/admin/circuit-breakers', methods=['GET'])
    def admin_circuit_breakers():
        """Statut des circuit breakers"""
        return jsonify(circuit_breaker_manager.get_status())
    
    @app.route('/admin/metrics', methods=['GET'])
    def admin_metrics():
        """Métriques Prometheus"""
        return metrics_collector.get_metrics()
    
    @app.route('/admin/cache/clear', methods=['POST'])
    def admin_clear_cache():
        """Vide le cache"""
        cache_middleware.invalidate()
        return jsonify({'message': 'Cache cleared successfully'})
    
    @app.route('/admin/config', methods=['GET'])
    def admin_config():
        """Configuration actuelle"""
        return jsonify(config.data)
    
    @app.route('/admin/stats', methods=['GET'])
    def admin_stats():
        """Statistiques globales"""
        return jsonify({
            'uptime': time.time() - app.start_time,
            'services': len(registry.services),
            'circuit_breakers': circuit_breaker_manager.get_status(),
            'health': health_checker.get_overall_health()
        })
    
    # Sauvegarder temps de démarrage
    app.start_time = time.time()
    
    return app

def _init_service_registry():
    """Initialise le registry des services depuis config"""
    
    for service_name, service_config in config.services.items():
        upstream = service_config.get('upstream')
        health_check = service_config.get('health_check', {})
        
        registry.register_service(
            name=service_name,
            upstream=upstream,
            health_check_path=health_check.get('path', '/health'),
            health_check_interval=health_check.get('interval', 10)
        )
    
    # Démarrer health checks
    registry.start_health_checks()


# === POINT D'ENTRÉE ===

# run.py

import os
from app import create_app

app = create_app()

if __name__ == '__main__':
    port = int(os.getenv('GATEWAY_PORT', 8080))
    app.run(
        host='0.0.0.0',
        port=port,
        debug=os.getenv('DEBUG', 'False') == 'True'
    )


# === REQUIREMENTS.TXT ===

# requirements.txt

Flask==3.0.0
Flask-CORS==4.0.0
PyYAML==6.0.1
redis==5.0.1
requests==2.31.0
PyJWT==2.8.0
prometheus-client==0.19.0
gunicorn==21.2.0


# === DOCKERFILE ===

# Dockerfile

FROM python:3.11-slim

WORKDIR /app

# Dépendances système
RUN apt-get update && apt-get install -y \
    gcc \
    && rm -rf /var/lib/apt/lists/*

# Dépendances Python
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Code application
COPY . .

# Port
EXPOSE 8080

# Variables d'environnement
ENV GATEWAY_PORT=8080
ENV GATEWAY_CONFIG=/app/config/gateway.yaml

# Healthcheck
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
    CMD curl -f http://localhost:8080/admin/health || exit 1

# Lancer avec Gunicorn
CMD ["gunicorn", "-w", "4", "-b", "0.0.0.0:8080", "--timeout", "120", "run:app"]


# === DOCKER-COMPOSE ===

# docker-compose.yml

version: '3.8'

services:
  # Redis pour cache et rate limiting
  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    networks:
      - gateway-network
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 3s
      retries: 3

  # API Gateway
  api-gateway:
    build: .
    ports:
      - "8080:8080"
      - "9090:9090"  # Prometheus metrics
    environment:
      - GATEWAY_CONFIG=/app/config/gateway.yaml
      - REDIS_URL=redis://redis:6379
    volumes:
      - ./config:/app/config
      - ./logs:/app/logs
    depends_on:
      - redis
    networks:
      - gateway-network
    restart: unless-stopped

  # Prometheus pour monitoring
  prometheus:
    image: prom/prometheus:latest
    ports:
      - "9091:9090"
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
      - prometheus_data:/prometheus
    command:
      - '--config.file=/etc/prometheus/prometheus.yml'
      - '--storage.tsdb.path=/prometheus'
    networks:
      - gateway-network

  # Grafana pour visualisation
  grafana:
    image: grafana/grafana:latest
    ports:
      - "3000:3000"
    environment:
      - GF_SECURITY_ADMIN_PASSWORD=admin
    volumes:
      - grafana_data:/var/lib/grafana
    depends_on:
      - prometheus
    networks:
      - gateway-network

volumes:
  prometheus_data:
  grafana_data:

networks:
  gateway-network:
    driver: bridge


# === CONFIGURATION PROMETHEUS ===

# prometheus.yml

global:
  scrape_interval: 15s
  evaluation_interval: 15s

scrape_configs:
  - job_name: 'api-gateway'
    static_configs:
      - targets: ['api-gateway:9090']
    metrics_path: '/admin/metrics'


# === TESTS ===

# tests/test_gateway.py

import pytest
import requests
from unittest.mock import Mock, patch
from app import create_app

@pytest.fixture
def app():
    """Fixture Flask app"""
    app = create_app()
    app.config['TESTING'] = True
    return app

@pytest.fixture
def client(app):
    """Fixture test client"""
    return app.test_client()

def test_health_check(client):
    """Test health check endpoint"""
    response = client.get('/admin/health')
    assert response.status_code == 200
    data = response.get_json()
    assert 'status' in data

def test_route_not_found(client):
    """Test route inexistante"""
    response = client.get('/api/nonexistent')
    assert response.status_code == 404
    data = response.get_json()
    assert 'error' in data

def test_cors_headers(client):
    """Test headers CORS"""
    response = client.options('/admin/health')
    assert response.status_code == 204
    assert 'Access-Control-Allow-Origin' in response.headers

@patch('app.core.proxy.requests.Session.request')
def test_proxy_request(mock_request, client):
    """Test proxy vers service"""
    # Mock réponse du service
    mock_response = Mock()
    mock_response.status_code = 200
    mock_response.content = b'{"success": true}'
    mock_response.headers = {'Content-Type': 'application/json'}
    mock_request.return_value = mock_response
    
    response = client.get('/api/products')
    assert response.status_code == 200

def test_rate_limiting(client):
    """Test rate limiting"""
    # Faire plusieurs requêtes rapidement
    for i in range(15):
        response = client.get('/api/products')
        
        if response.status_code == 429:
            # Rate limit atteint
            assert 'X-RateLimit-Limit' in response.headers
            break


# === EXEMPLES D'UTILISATION ===


# === EXEMPLE 1: Configuration minimale ===

# config/gateway-minimal.yaml

services:
  backend:
    upstream: "http://backend:5000"
    routes:
      - path: "/api/*"
        methods: ["GET", "POST", "PUT", "DELETE"]

global:
  cors:
    allowed_origins: ["*"]


# === EXEMPLE 2: Configuration avec auth et rate limiting ===

# config/gateway-auth.yaml

services:
  api:
    upstream: "http://api-service:5000"
    routes:
      - path: "/api/public/*"
        methods: ["GET"]
        rate_limit:
          requests: 100
          window: 60
      
      - path: "/api/private/*"
        methods: ["GET", "POST", "PUT", "DELETE"]
        auth_required: true
        rate_limit:
          requests: 50
          window: 60

global:
  cors:
    allowed_origins: ["https://myapp.com"]


# === EXEMPLE 3: Configuration avec load balancing ===

# config/gateway-lb.yaml

services:
  web-api:
    routes:
      - path: "/api/*"
        methods: ["GET", "POST"]
        cache:
          enabled: true
          ttl: 300
          methods: ["GET"]
    
    load_balancing:
      strategy: "round_robin"
      instances:
        - "http://api-1:5000"
        - "http://api-2:5000"
        - "http://api-3:5000"
    
    health_check:
      path: "/health"
      interval: 10
      timeout: 5


# === EXEMPLE 4: Configuration avec circuit breaker ===

# config/gateway-cb.yaml

services:
  unstable-service:
    upstream: "http://unstable:5000"
    routes:
      - path: "/api/unstable/*"
        methods: ["GET"]
        circuit_breaker:
          enabled: true
          threshold: 5
          timeout: 60
        timeout: 10


# === UTILISATION AVEC CURL ===

# Requête simple
curl http://localhost:8080/api/products

# Avec authentification
curl -H "Authorization: Bearer <token>" \
     http://localhost:8080/api/users/me

# Avec body JSON
curl -X POST \
     -H "Content-Type: application/json" \
     -d '{"name":"Product","price":99.99}' \
     http://localhost:8080/api/products

# Vérifier health
curl http://localhost:8080/admin/health

# Voir métriques
curl http://localhost:8080/admin/metrics

# Vider cache
curl -X POST http://localhost:8080/admin/cache/clear

# === CONFIGURATION AVANCÉE ===

# config/gateway_advanced.yaml

services:
  auth-service:
    upstream: "http://auth-service:5000"
    routes:
      - path: "/api/auth/login"
        methods: ["POST"]
        rate_limit:
          requests: 5
          window: 60
        transforms:
          request:
            - add_timestamp
          response:
            - remove_passwords
        retry:
          attempts: 3
          backoff: exponential
          
      - path: "/api/auth/register"
        methods: ["POST"]
        rate_limit:
          requests: 3
          window: 300
        validation:
          schema: "user_registration"
    
    timeout: 10
    circuit_breaker:
      threshold: 5
      timeout: 60
      half_open_requests: 3

  product-service:
    load_balancing:
      strategy: "least_connections"
      instances:
        - url: "http://product-1:5002"
          weight: 2
        - url: "http://product-2:5002"
          weight: 1
        - url: "http://product-3:5002"
          weight: 1
      
      health_check:
        path: "/health"
        interval: 10
        timeout: 5
        unhealthy_threshold: 3
        healthy_threshold: 2
    
    routes:
      - path: "/api/products"
        methods: ["GET"]
        cache:
          enabled: true
          ttl: 600
          vary_by: ["Accept-Language"]
        
      - path: "/api/products/*"
        methods: ["POST", "PUT", "DELETE"]
        auth_required: true
        permissions: ["products:write"]
        rate_limit:
          strategy: "user"
          requests: 100
          window: 3600

global:
  ssl:
    enabled: true
    cert_file: "/certs/gateway.crt"
    key_file: "/certs/gateway.key"
  
  security:
    ip_whitelist:
      - "10.0.0.0/8"
      - "172.16.0.0/12"
    
    ip_blacklist: []
    
    request_size_limit: 10485760  # 10MB
    
    headers:
      remove:
        - "Server"
        - "X-Powered-By"
      add:
        - name: "X-Gateway-Version"
          value: "1.0.0"
        - name: "X-Content-Type-Options"
          value: "nosniff"
  
  logging:
    level: "INFO"
    format: "json"
    destinations:
      - type: "stdout"
      - type: "file"
        path: "/var/log/gateway/access.log"
      - type: "elasticsearch"
        url: "http://elasticsearch:9200"
  
  monitoring:
    prometheus:
      enabled: true
      port: 9090
    
    tracing:
      enabled: true
      backend: "jaeger"
      endpoint: "http://jaeger:14268/api/traces"
      sample_rate: 0.1


# === FONCTIONNALITÉS AVANCÉES ===

# 1. REQUEST VALIDATION

# app/plugins/validation.py

from marshmallow import Schema, fields, ValidationError
from flask import request, jsonify
from functools import wraps

class UserRegistrationSchema(Schema):
    """Schéma de validation pour inscription"""
    username = fields.Str(required=True, validate=lambda x: len(x) >= 3)
    email = fields.Email(required=True)
    password = fields.Str(required=True, validate=lambda x: len(x) >= 8)

class RequestValidator:
    """Validateur de requêtes"""
    
    def __init__(self):
        self.schemas = {
            'user_registration': UserRegistrationSchema()
        }
    
    def validate(self, schema_name):
        """Décorateur de validation"""
        def decorator(f):
            @wraps(f)
            def decorated_function(*args, **kwargs):
                if schema_name not in self.schemas:
                    return jsonify({'error': 'Unknown validation schema'}), 500
                
                try:
                    data = request.get_json()
                    validated = self.schemas[schema_name].load(data)
                    request.validated_data = validated
                    return f(*args, **kwargs)
                
                except ValidationError as e:
                    return jsonify({'error': 'Validation failed', 'details': e.messages}), 400
            
            return decorated_function
        return decorator

request_validator = RequestValidator()


# 2. RETRY LOGIC

# app/plugins/retry.py

import time
from functools import wraps

def retry_request(attempts=3, backoff='exponential', delay=1):
    """Retry logic pour requêtes"""
    def decorator(f):
        @wraps(f)
        def wrapper(*args, **kwargs):
            last_exception = None
            
            for attempt in range(attempts):
                try:
                    return f(*args, **kwargs)
                
                except Exception as e:
                    last_exception = e
                    
                    if attempt < attempts - 1:
                        # Calculer délai
                        if backoff == 'exponential':
                            wait_time = delay * (2 ** attempt)
                        elif backoff == 'linear':
                            wait_time = delay * (attempt + 1)
                        else:
                            wait_time = delay
                        
                        print(f"Retry {attempt + 1}/{attempts} after {wait_time}s")
                        time.sleep(wait_time)
            
            # Tous les essais échoués
            raise last_exception
        
        return wrapper
    return decorator


# 3. IP FILTERING

# app/middleware/ip_filter.py

from flask import request, jsonify
from ipaddress import ip_address, ip_network
from functools import wraps

class IPFilter:
    """Filtrage par IP"""
    
    def __init__(self, whitelist=None, blacklist=None):
        self.whitelist = [ip_network(ip) for ip in (whitelist or [])]
        self.blacklist = [ip_network(ip) for ip in (blacklist or [])]
    
    def __call__(self, f):
        @wraps(f)
        def decorated_function(*args, **kwargs):
            client_ip = ip_address(request.remote_addr)
            
            # Vérifier blacklist
            if self._is_blacklisted(client_ip):
                return jsonify({'error': 'Access denied'}), 403
            
            # Vérifier whitelist (si définie)
            if self.whitelist and not self._is_whitelisted(client_ip):
                return jsonify({'error': 'Access denied'}), 403
            
            return f(*args, **kwargs)
        
        return decorated_function
    
    def _is_blacklisted(self, ip):
        return any(ip in network for network in self.blacklist)
    
    def _is_whitelisted(self, ip):
        return any(ip in network for network in self.whitelist)


# 4. REQUEST SIZE LIMITING

# app/middleware/size_limit.py

from flask import request, jsonify
from functools import wraps

def limit_request_size(max_size_bytes=10485760):
    """Limite la taille des requêtes"""
    def decorator(f):
        @wraps(f)
        def decorated_function(*args, **kwargs):
            content_length = request.content_length
            
            if content_length and content_length > max_size_bytes:
                return jsonify({
                    'error': 'Request too large',
                    'max_size': max_size_bytes,
                    'received': content_length
                }), 413
            
            return f(*args, **kwargs)
        
        return decorated_function
    return decorator


# 5. DISTRIBUTED TRACING

# app/utils/tracing.py

from opentelemetry import trace
from opentelemetry.sdk.trace import TracerProvider
from opentelemetry.sdk.trace.export import BatchSpanProcessor
from opentelemetry.exporter.jaeger.thrift import JaegerExporter
from opentelemetry.instrumentation.flask import FlaskInstrumentor
from opentelemetry.instrumentation.requests import RequestsInstrumentor

def setup_tracing(app, service_name='api-gateway'):
    """Configure distributed tracing"""
    
    # Provider
    trace.set_tracer_provider(TracerProvider())
    
    # Jaeger exporter
    jaeger_exporter = JaegerExporter(
        agent_host_name='jaeger',
        agent_port=6831,
    )
    
    # Span processor
    span_processor = BatchSpanProcessor(jaeger_exporter)
    trace.get_tracer_provider().add_span_processor(span_processor)
    
    # Auto-instrumentation
    FlaskInstrumentor().instrument_app(app)
    RequestsInstrumentor().instrument()
    
    return trace.get_tracer(__name__)


# === DÉPLOIEMENT ===


# === DÉPLOIEMENT LOCAL ===

# 1. Installer dépendances
pip install -r requirements.txt

# 2. Configurer Redis
docker run -d -p 6379:6379 redis:7-alpine

# 3. Créer configuration
# Éditer config/gateway.yaml

# 4. Lancer gateway
python run.py

# Ou avec Gunicorn
gunicorn -w 4 -b 0.0.0.0:8080 run:app


# === DÉPLOIEMENT DOCKER ===

# Build image
docker build -t api-gateway:latest .

# Run container
docker run -d \
  -p 8080:8080 \
  -v $(pwd)/config:/app/config \
  -e REDIS_URL=redis://redis:6379 \
  --name api-gateway \
  api-gateway:latest

# Avec docker-compose
docker-compose up -d


# === DÉPLOIEMENT KUBERNETES ===

# k8s/deployment.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: api-gateway
  labels:
    app: api-gateway
spec:
  replicas: 3
  selector:
    matchLabels:
      app: api-gateway
  template:
    metadata:
      labels:
        app: api-gateway
    spec:
      containers:
      - name: api-gateway
        image: api-gateway:latest
        ports:
        - containerPort: 8080
          name: http
        - containerPort: 9090
          name: metrics
        env:
        - name: GATEWAY_CONFIG
          value: /app/config/gateway.yaml
        - name: REDIS_URL
          value: redis://redis-service:6379
        volumeMounts:
        - name: config
          mountPath: /app/config
        livenessProbe:
          httpGet:
            path: /admin/health
            port: 8080
          initialDelaySeconds: 30
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /admin/health
            port: 8080
          initialDelaySeconds: 5
          periodSeconds: 5
        resources:
          requests:
            memory: "256Mi"
            cpu: "250m"
          limits:
            memory: "512Mi"
            cpu: "500m"
      volumes:
      - name: config
        configMap:
          name: gateway-config
---
apiVersion: v1
kind: Service
metadata:
  name: api-gateway
spec:
  selector:
    app: api-gateway
  ports:
  - name: http
    port: 80
    targetPort: 8080
  - name: metrics
    port: 9090
    targetPort: 9090
  type: LoadBalancer
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: gateway-config
data:
  gateway.yaml: |
    services:
      api:
        upstream: "http://api-service:5000"
        routes:
          - path: "/api/*"
            methods: ["GET", "POST", "PUT", "DELETE"]
    global:
      cors:
        allowed_origins: ["*"]


# k8s/redis.yaml

apiVersion: apps/v1
kind: Deployment
metadata:
  name: redis
spec:
  replicas: 1
  selector:
    matchLabels:
      app: redis
  template:
    metadata:
      labels:
        app: redis
    spec:
      containers:
      - name: redis
        image: redis:7-alpine
        ports:
        - containerPort: 6379
---
apiVersion: v1
kind: Service
metadata:
  name: redis-service
spec:
  selector:
    app: redis
  ports:
  - port: 6379
    targetPort: 6379


# Déployer sur Kubernetes
kubectl apply -f k8s/


# === MONITORING ET OBSERVABILITÉ ===


# === DASHBOARDS GRAFANA ===

# Importer dashboard Grafana pour visualiser:
# - Nombre de requêtes par endpoint
# - Latence moyenne/p95/p99
# - Taux d'erreur
# - Circuit breakers état
# - Cache hit rate
# - Services health


# === ALERTES PROMETHEUS ===

# prometheus-alerts.yml

groups:
  - name: gateway_alerts
    interval: 30s
    rules:
      # Alert si gateway down
      - alert: GatewayDown
        expr: up{job="api-gateway"} == 0
        for: 1m
        labels:
          severity: critical
        annotations:
          summary: "API Gateway is down"
          description: "Gateway {{ $labels.instance }} has been down for more than 1 minute"
      
      # Alert si taux d'erreur élevé
      - alert: HighErrorRate
        expr: rate(gateway_requests_total{status=~"5.."}[5m]) > 0.1
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "High error rate on gateway"
          description: "Error rate is {{ $value }} for {{ $labels.path }}"
      
      # Alert si latence élevée
      - alert: HighLatency
        expr: histogram_quantile(0.95, rate(gateway_request_duration_seconds_bucket[5m])) > 1
        for: 5m
        labels:
          severity: warning
        annotations:
          summary: "High latency on gateway"
          description: "P95 latency is {{ $value }}s"
      
      # Alert si circuit breaker ouvert
      - alert: CircuitBreakerOpen
        expr: gateway_circuit_breaker_state == 1
        for: 1m
        labels:
          severity: warning
        annotations:
          summary: "Circuit breaker open"
          description: "Circuit breaker for {{ $labels.service }} is open"


# === LOGGING ===

# Configuration logging avec ELK

# filebeat.yml - Envoyer logs vers Elasticsearch

filebeat.inputs:
  - type: log
    enabled: true
    paths:
      - /app/logs/*.log
    json.keys_under_root: true
    json.add_error_key: true

output.elasticsearch:
  hosts: ["elasticsearch:9200"]
  index: "gateway-logs-%{+yyyy.MM.dd}"

setup.kibana:
  host: "kibana:5601"


# === SÉCURITÉ ===


# === SSL/TLS TERMINATION ===

# Le gateway peut gérer SSL/TLS

# Avec Gunicorn + certificats
gunicorn \
  --certfile=/path/to/cert.pem \
  --keyfile=/path/to/key.pem \
  -w 4 -b 0.0.0.0:443 run:app


# === AUTHENTIFICATION AVANCÉE ===

# app/middleware/auth_advanced.py

import jwt
import time
from functools import wraps
from flask import request, jsonify

class OAuth2Middleware:
    """Middleware OAuth2/OpenID Connect"""
    
    def __init__(self, jwks_url, issuer, audience):
        self.jwks_url = jwks_url
        self.issuer = issuer
        self.audience = audience
        self._jwks = None
        self._jwks_last_fetch = 0
    
    def validate_token(self, token):
        """Valide un token OAuth2"""
        try:
            # Récupérer JWKS si nécessaire
            if not self._jwks or time.time() - self._jwks_last_fetch > 3600:
                self._fetch_jwks()
            
            # Décoder token
            header = jwt.get_unverified_header(token)
            key = self._get_signing_key(header['kid'])
            
            payload = jwt.decode(
                token,
                key=key,
                algorithms=['RS256'],
                audience=self.audience,
                issuer=self.issuer
            )
            
            return payload
        
        except jwt.ExpiredSignatureError:
            return None
        except jwt.InvalidTokenError:
            return None
    
    def _fetch_jwks(self):
        """Récupère les clés JWKS"""
        import requests
        response = requests.get(self.jwks_url)
        self._jwks = response.json()
        self._jwks_last_fetch = time.time()
    
    def _get_signing_key(self, kid):
        """Récupère la clé de signature"""
        for key in self._jwks['keys']:
            if key['kid'] == kid:
                return jwt.algorithms.RSAAlgorithm.from_jwk(json.dumps(key))
        raise Exception('Key not found')


# === IP WHITELISTING ===

# app/middleware/ip_whitelist.py

from flask import request, jsonify
from functools import wraps

class IPWhitelistMiddleware:
    """Middleware pour whitelist d'IPs"""
    
    def __init__(self, allowed_ips):
        self.allowed_ips = set(allowed_ips)
    
    def __call__(self, f):
        @wraps(f)
        def decorated_function(*args, **kwargs):
            client_ip = request.remote_addr
            
            if client_ip not in self.allowed_ips:
                return jsonify({
                    'error': 'Access denied',
                    'ip': client_ip
                }), 403
            
            return f(*args, **kwargs)
        
        return decorated_function


# === REQUEST SIGNING ===

# app/middleware/request_signing.py

import hmac
import hashlib
from flask import request, jsonify
from functools import wraps

class RequestSigningMiddleware:
    """Middleware pour vérifier signature des requêtes"""
    
    def __init__(self, secret_key):
        self.secret_key = secret_key
    
    def __call__(self, f):
        @wraps(f)
        def decorated_function(*args, **kwargs):
            # Récupérer signature du header
            signature = request.headers.get('X-Signature')
            
            if not signature:
                return jsonify({'error': 'Missing signature'}), 401
            
            # Calculer signature attendue
            body = request.get_data()
            expected_signature = hmac.new(
                self.secret_key.encode(),
                body,
                hashlib.sha256
            ).hexdigest()
            
            # Vérifier
            if not hmac.compare_digest(signature, expected_signature):
                return jsonify({'error': 'Invalid signature'}), 401
            
            return f(*args, **kwargs)
        
        return decorated_function


# === OPTIMISATIONS PERFORMANCE ===


# === COMPRESSION DES RÉPONSES ===

# app/middleware/compression.py

from flask import request
import gzip
import zlib

class CompressionMiddleware:
    """Middleware de compression des réponses"""
    
    def compress_response(self, response):
        """Compresse la réponse si client supporte"""
        
        accept_encoding = request.headers.get('Accept-Encoding', '')
        
        # Vérifier si compression déjà faite
        if response.headers.get('Content-Encoding'):
            return response
        
        # Vérifier taille minimum
        if len(response.get_data()) < 1024:
            return response
        
        # Gzip
        if 'gzip' in accept_encoding:
            response.set_data(gzip.compress(response.get_data()))
            response.headers['Content-Encoding'] = 'gzip'
            response.headers['Content-Length'] = len(response.get_data())
        
        # Deflate
        elif 'deflate' in accept_encoding:
            response.set_data(zlib.compress(response.get_data()))
            response.headers['Content-Encoding'] = 'deflate'
            response.headers['Content-Length'] = len(response.get_data())
        
        return response


# === CONNECTION POOLING ===

# Le HTTPProxy utilise déjà un pool de connexions
# Mais on peut l'optimiser davantage

# app/core/proxy_optimized.py

import requests
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry

class OptimizedHTTPProxy:
    """Proxy HTTP optimisé"""
    
    def __init__(self):
        self.session = requests.Session()
        
        # Retry strategy
        retry_strategy = Retry(
            total=3,
            backoff_factor=0.3,
            status_forcelist=[429, 500, 502, 503, 504],
            method_whitelist=["HEAD", "GET", "OPTIONS"]
        )
        
        # Adapter avec pooling et retry
        adapter = HTTPAdapter(
            pool_connections=100,
            pool_maxsize=100,
            max_retries=retry_strategy,
            pool_block=False
        )
        
        self.session.mount('http://', adapter)
        self.session.mount('https://', adapter)
        
        # Keep-alive
        self.session.headers['Connection'] = 'keep-alive'


# === CACHING AVANCÉ ===

# Cache avec stratégies multiples

# app/middleware/cache_advanced.py

import redis
from functools import wraps

class AdvancedCache:
    """Cache avancé avec plusieurs stratégies"""
    
    def __init__(self, redis_url):
        self.redis = redis.from_url(redis_url)
    
    def cache_aside(self, key_func, ttl=300):
        """Pattern Cache-Aside"""
        def decorator(f):
            @wraps(f)
            def wrapper(*args, **kwargs):
                key = key_func(*args, **kwargs)
                
                # Vérifier cache
                cached = self.redis.get(key)
                if cached:
                    return json.loads(cached)
                
                # Exécuter et cacher
                result = f(*args, **kwargs)
                self.redis.setex(key, ttl, json.dumps(result))
                
                return result
            return wrapper
        return decorator
    
    def write_through(self, key_func):
        """Pattern Write-Through"""
        def decorator(f):
            @wraps(f)
            def wrapper(*args, **kwargs):
                # Écrire en DB et cache simultanément
                result = f(*args, **kwargs)
                
                key = key_func(*args, **kwargs)
                self.redis.set(key, json.dumps(result))
                
                return result
            return wrapper
        return decorator
    
    def write_behind(self, key_func, buffer_size=100):
        """Pattern Write-Behind (async)"""
        # Implémenter buffer et flush périodique
        pass


# === EXEMPLES COMPLETS ===


# === EXEMPLE: Gateway pour E-commerce ===

# config/ecommerce-gateway.yaml

services:
  catalog-service:
    upstream: "http://catalog:5000"
    routes:
      - path: "/api/products/*"
        methods: ["GET"]
        cache:
          enabled: true
          ttl: 600
        rate_limit:
          requests: 200
          window: 60
    load_balancing:
      strategy: "least_connections"
      instances:
        - "http://catalog-1:5000"
        - "http://catalog-2:5000"
  
  cart-service:
    upstream: "http://cart:5001"
    routes:
      - path: "/api/cart/*"
        methods: ["GET", "POST", "PUT", "DELETE"]
        auth_required: true
        rate_limit:
          requests: 50
          window: 60
  
  checkout-service:
    upstream: "http://checkout:5002"
    routes:
      - path: "/api/checkout/*"
        methods: ["POST"]
        auth_required: true
        rate_limit:
          requests: 10
          window: 60
        circuit_breaker:
          enabled: true
          threshold: 3
          timeout: 30
  
  payment-service:
    upstream: "http://payment:5003"
    routes:
      - path: "/api/payments/*"
        methods: ["POST"]
        auth_required: true
        timeout: 15
        circuit_breaker:
          enabled: true
          threshold: 2
          timeout: 60

global:
  cors:
    allowed_origins: ["https://shop.com"]
    allowed_methods: ["GET", "POST", "PUT", "DELETE"]
  
  logging:
    level: "INFO"
    format: "json"
  
  metrics:
    enabled: true


# === EXEMPLE: Gateway pour API publique ===

# config/public-api-gateway.yaml

services:
  public-api:
    upstream: "http://api:5000"
    routes:
      # Endpoints publics
      - path: "/api/v1/public/*"
        methods: ["GET"]
        rate_limit:
          requests: 1000
          window: 3600
        cache:
          enabled: true
          ttl: 300
      
      # Endpoints authentifiés
      - path: "/api/v1/users/*"
        methods: ["GET", "POST", "PUT", "DELETE"]
        auth_required: true
        rate_limit:
          requests: 100
          window: 60
      
      # Webhooks
      - path: "/api/v1/webhooks/*"
        methods: ["POST"]
        auth_required: true
        timeout: 30

global:
  cors:
    allowed_origins: ["*"]
  
  rate_limiting:
    default_requests: 100
    default_window: 60


# === COMPARAISON: Custom Gateway vs Solutions Existantes ===

┌──────────────────┬──────────────┬──────────────┬──────────────┐
│    CRITÈRE       │   CUSTOM     │     KONG     │     NGINX    │
├──────────────────┼──────────────┼──────────────┼──────────────┤
│ Flexibilité      │ *****      │ *****      │ *****       │
│ Facilité setup   │ *****      │ *****      │ *****       │
│ Performance      │ *****      │ *****      │ *****       │
│ Features         │ *****      │ *****      │ *****       │
│ Communauté       │ *****      │ *****      │ *****       │
│ Coût             │ Gratuit      │Enterprise $  │  Gratuit/Pro │
│ Maintenance      │ *****      │ *****      │ *****       │
│ Documentation    │ À créer      │ *****      │ *****       │
│ Plugins          │ Sur mesure   │ Nombreux     │ Modules      │
│ Support          │ Aucun        │ Commercial   │ Commercial   │
└──────────────────┴──────────────┴──────────────┴──────────────┘

# Recommandation:
# - Petit projet / apprentissage -> Custom Gateway
# - Production / entreprise -> Kong ou NGINX
# - Besoins très spécifiques -> Custom Gateway
# - Équipe expérimentée -> Custom Gateway
# - Démarrage rapide -> Kong/NGINX


# === RÉSUMÉ: API GATEWAY CUSTOM ===

[OK] CE QUI A ÉTÉ COUVERT

1. **Architecture complète**
   - Service Registry
   - HTTP Proxy
   - Router dynamique

2. **Middlewares**
   - Authentication (JWT, OAuth2)
   - Rate Limiting (Redis)
   - Caching (Redis)
   - Circuit Breaker
   - CORS
   - Logging
   - Compression

3. **Plugins**
   - Request/Response transformation
   - Load Balancing (5 stratégies)

4. **Observabilité**
   - Métriques Prometheus
   - Health Checks
   - Logging structuré

5. **Sécurité**
   - SSL/TLS
   - IP Whitelisting
   - Request Signing

6. **Déploiement**
   - Docker & Docker Compose
   - Kubernetes
   - Configuration YAML
   - Monitoring & Alerting

7. **Performance**
   - Connection Pooling
   - Compression
   - Cache avancé
   - Retry strategy


# === PATTERNS & BEST PRACTICES ===


# === PATTERN: API VERSIONING ===

# app/middleware/versioning.py

from flask import request, jsonify
from functools import wraps

class APIVersioning:
    """Gestion du versioning d'API"""
    
    def __init__(self, default_version='v1'):
        self.default_version = default_version
    
    def get_version(self):
        """Récupère la version depuis header ou path"""
        
        # Depuis header
        version = request.headers.get('X-API-Version')
        if version:
            return version
        
        # Depuis path
        path_parts = request.path.split('/')
        if len(path_parts) > 2 and path_parts[2].startswith('v'):
            return path_parts[2]
        
        return self.default_version
    
    def route_by_version(self, versions_map):
        """Route vers service selon version"""
        def decorator(f):
            @wraps(f)
            def wrapper(*args, **kwargs):
                version = self.get_version()
                
                if version not in versions_map:
                    return jsonify({
                        'error': f'API version {version} not supported',
                        'supported_versions': list(versions_map.keys())
                    }), 400
                
                # Modifier l'upstream selon version
                request.upstream_url = versions_map[version]
                
                return f(*args, **kwargs)
            return wrapper
        return decorator

# Configuration
api_versioning = APIVersioning()

# Usage dans routes
@app.route('/api/<version>/products')
@api_versioning.route_by_version({
    'v1': 'http://api-v1:5000',
    'v2': 'http://api-v2:5001',
    'v3': 'http://api-v3:5002'
})
def products_route():
    return proxy.forward_request(
        target_url=request.upstream_url,
        method=request.method,
        path=request.path,
        headers=dict(request.headers),
        body=request.get_data()
    )


# === PATTERN: REQUEST AGGREGATION ===

# app/plugins/request_aggregation.py

import asyncio
import aiohttp
from typing import List, Dict

class RequestAggregator:
    """Agrège plusieurs requêtes en une seule réponse"""
    
    async def aggregate(self, requests_config: List[Dict]) -> Dict:
        """
        Exécute plusieurs requêtes en parallèle
        
        Args:
            requests_config: Liste de configs de requêtes
            [
                {'name': 'user', 'url': 'http://user-service/users/1'},
                {'name': 'orders', 'url': 'http://order-service/orders?user=1'}
            ]
        
        Returns:
            Dict avec toutes les réponses
        """
        
        async with aiohttp.ClientSession() as session:
            tasks = []
            
            for req_config in requests_config:
                task = self._fetch(
                    session,
                    req_config['name'],
                    req_config['url'],
                    req_config.get('method', 'GET'),
                    req_config.get('headers', {}),
                    req_config.get('body')
                )
                tasks.append(task)
            
            results = await asyncio.gather(*tasks, return_exceptions=True)
            
            # Construire réponse agrégée
            response = {}
            for i, result in enumerate(results):
                name = requests_config[i]['name']
                
                if isinstance(result, Exception):
                    response[name] = {
                        'error': str(result),
                        'status': 'failed'
                    }
                else:
                    response[name] = result
            
            return response
    
    async def _fetch(self, session, name, url, method, headers, body):
        """Fetch une requête"""
        async with session.request(
            method,
            url,
            headers=headers,
            json=body
        ) as response:
            return {
                'status': response.status,
                'data': await response.json()
            }

# Usage
aggregator = RequestAggregator()

@app.route('/api/aggregate/user-dashboard/<int:user_id>')
async def user_dashboard(user_id):
    """Agrège toutes les données du dashboard utilisateur"""
    
    requests = [
        {
            'name': 'user',
            'url': f'http://user-service:5001/api/users/{user_id}'
        },
        {
            'name': 'orders',
            'url': f'http://order-service:5003/api/orders/user/{user_id}'
        },
        {
            'name': 'cart',
            'url': f'http://cart-service:5002/api/cart/{user_id}'
        },
        {
            'name': 'recommendations',
            'url': f'http://recommendation-service:5004/api/recommendations/{user_id}'
        }
    ]
    
    result = await aggregator.aggregate(requests)
    return jsonify(result)


# === PATTERN: GRACEFUL DEGRADATION ===

# app/plugins/graceful_degradation.py

from functools import wraps
from flask import jsonify

class GracefulDegradation:
    """Dégradation gracieuse en cas d'erreur"""
    
    def __init__(self):
        self.fallbacks = {}
    
    def register_fallback(self, service_name, fallback_func):
        """Enregistre une fonction de fallback"""
        self.fallbacks[service_name] = fallback_func
    
    def with_fallback(self, service_name):
        """Décorateur pour ajouter fallback"""
        def decorator(f):
            @wraps(f)
            def wrapper(*args, **kwargs):
                try:
                    return f(*args, **kwargs)
                except Exception as e:
                    print(f"Service {service_name} failed: {e}")
                    
                    # Utiliser fallback
                    if service_name in self.fallbacks:
                        return self.fallbacks[service_name](*args, **kwargs)
                    
                    # Fallback par défaut
                    return jsonify({
                        'error': 'Service temporarily unavailable',
                        'fallback': True
                    }), 503
            
            return wrapper
        return decorator

# Configuration
degradation = GracefulDegradation()

# Enregistrer fallbacks
degradation.register_fallback(
    'recommendation-service',
    lambda user_id: jsonify({'recommendations': []})  # Liste vide
)

degradation.register_fallback(
    'user-service',
    lambda user_id: jsonify({  # Données en cache
        'id': user_id,
        'name': 'Guest User',
        'cached': True
    })
)

# Usage
@app.route('/api/recommendations/<int:user_id>')
@degradation.with_fallback('recommendation-service')
def get_recommendations(user_id):
    # Appeler service de recommandation
    return proxy.forward_request(
        target_url='http://recommendation-service:5004',
        method='GET',
        path=f'/api/recommendations/{user_id}',
        headers=dict(request.headers)
    )


# === PATTERN: REQUEST BATCHING ===

# app/plugins/request_batching.py

import asyncio
from collections import defaultdict
import time

class RequestBatcher:
    """Batch plusieurs requêtes similaires"""
    
    def __init__(self, batch_window=0.1, max_batch_size=100):
        self.batch_window = batch_window  # 100ms
        self.max_batch_size = max_batch_size
        self.batches = defaultdict(list)
        self._lock = asyncio.Lock()
    
    async def batch_request(self, batch_key, request_id, request_func):
        """
        Ajoute une requête au batch
        
        Args:
            batch_key: Clé pour grouper les requêtes (ex: 'get_user')
            request_id: ID unique de la requête
            request_func: Fonction à exécuter
        """
        
        async with self._lock:
            # Ajouter au batch
            self.batches[batch_key].append({
                'id': request_id,
                'func': request_func,
                'timestamp': time.time()
            })
            
            # Exécuter si batch plein
            if len(self.batches[batch_key]) >= self.max_batch_size:
                return await self._execute_batch(batch_key)
        
        # Attendre fin de fenêtre
        await asyncio.sleep(self.batch_window)
        
        async with self._lock:
            if self.batches[batch_key]:
                return await self._execute_batch(batch_key)
    
    async def _execute_batch(self, batch_key):
        """Exécute un batch de requêtes"""
        batch = self.batches[batch_key]
        self.batches[batch_key] = []
        
        # Exécuter toutes les requêtes en parallèle
        tasks = [item['func']() for item in batch]
        results = await asyncio.gather(*tasks)
        
        # Mapper résultats aux IDs
        return {
            batch[i]['id']: results[i]
            for i in range(len(batch))
        }


# === PATTERN: RESPONSE CACHING INTELLIGENT ===

# app/middleware/intelligent_cache.py

import hashlib
import json
from flask import request

class IntelligentCache:
    """Cache intelligent avec invalidation automatique"""
    
    def __init__(self, redis_client):
        self.redis = redis_client
    
    def cache_with_tags(self, tags, ttl=300):
        """Cache avec tags pour invalidation sélective"""
        def decorator(f):
            @wraps(f)
            def wrapper(*args, **kwargs):
                # Générer clé
                cache_key = self._generate_key(request)
                
                # Vérifier cache
                cached = self.redis.get(cache_key)
                if cached:
                    return json.loads(cached)
                
                # Exécuter
                result = f(*args, **kwargs)
                
                # Cacher avec tags
                self.redis.setex(cache_key, ttl, json.dumps(result))
                
                # Associer tags
                for tag in tags:
                    self.redis.sadd(f'tag:{tag}', cache_key)
                    self.redis.expire(f'tag:{tag}', ttl)
                
                return result
            return wrapper
        return decorator
    
    def invalidate_by_tag(self, tag):
        """Invalide tous les caches avec ce tag"""
        keys = self.redis.smembers(f'tag:{tag}')
        if keys:
            self.redis.delete(*keys)
            self.redis.delete(f'tag:{tag}')
    
    def _generate_key(self, request):
        """Génère clé unique"""
        key_data = f"{request.method}:{request.path}:{request.query_string}"
        return f"cache:{hashlib.md5(key_data.encode()).hexdigest()}"

# Usage
cache = IntelligentCache(redis_client)

@app.route('/api/products')
@cache.cache_with_tags(['products', 'catalog'], ttl=600)
def list_products():
    return proxy.forward_request(...)

@app.route('/api/products', methods=['POST'])
def create_product():
    result = proxy.forward_request(...)
    
    # Invalider cache des produits
    cache.invalidate_by_tag('products')
    
    return result


# === PATTERN: SERVICE MESH INTEGRATION ===

# app/plugins/service_mesh.py

class ServiceMeshIntegration:
    """Intégration avec service mesh (Istio, Linkerd)"""
    
    def inject_mesh_headers(self, headers):
        """Injecte headers pour tracing distribué"""
        
        # Propagation de contexte
        mesh_headers = {
            'x-request-id': headers.get('x-request-id', self._generate_request_id()),
            'x-b3-traceid': headers.get('x-b3-traceid', self._generate_trace_id()),
            'x-b3-spanid': headers.get('x-b3-spanid', self._generate_span_id()),
            'x-b3-parentspanid': headers.get('x-b3-parentspanid'),
            'x-b3-sampled': '1',
        }
        
        headers.update(mesh_headers)
        return headers
    
    def _generate_request_id(self):
        import uuid
        return str(uuid.uuid4())
    
    def _generate_trace_id(self):
        import secrets
        return secrets.token_hex(16)
    
    def _generate_span_id(self):
        import secrets
        return secrets.token_hex(8)


# === ADVANCED FEATURES ===


# === FEATURE: DYNAMIC ROUTING ===

# app/plugins/dynamic_routing.py

class DynamicRouter:
    """Routage dynamique basé sur des règles"""
    
    def __init__(self):
        self.rules = []
    
    def add_rule(self, condition_func, target_url):
        """
        Ajoute une règle de routage
        
        Args:
            condition_func: Fonction qui retourne True si règle applicable
            target_url: URL cible si condition vraie
        """
        self.rules.append({
            'condition': condition_func,
            'target': target_url
        })
    
    def route(self, request):
        """Route selon les règles"""
        for rule in self.rules:
            if rule['condition'](request):
                return rule['target']
        
        return None

# Configuration
router = DynamicRouter()

# Règle: Utilisateurs premium -> service optimisé
router.add_rule(
    lambda req: req.headers.get('X-User-Tier') == 'premium',
    'http://premium-api:5000'
)

# Règle: Beta testers -> nouvelle version
router.add_rule(
    lambda req: req.headers.get('X-Beta-Tester') == 'true',
    'http://api-beta:5001'
)

# Règle: Traffic de certains pays -> serveur régional
router.add_rule(
    lambda req: req.headers.get('X-Country') in ['FR', 'DE', 'IT'],
    'http://api-eu:5002'
)

# Usage
@app.route('/api/<path:path>')
def dynamic_route(path):
    target = router.route(request)
    
    if not target:
        target = 'http://api-default:5000'
    
    return proxy.forward_request(
        target_url=target,
        method=request.method,
        path=f'/api/{path}',
        headers=dict(request.headers),
        body=request.get_data()
    )


# === FEATURE: A/B TESTING ===

# app/plugins/ab_testing.py

import random
import hashlib

class ABTesting:
    """A/B Testing dans le gateway"""
    
    def __init__(self):
        self.experiments = {}
    
    def create_experiment(
        self,
        name,
        variants,
        allocation=None
    ):
        """
        Crée une expérience A/B
        
        Args:
            name: Nom de l'expérience
            variants: Dict {variant_name: target_url}
            allocation: Dict {variant_name: percentage} ou None pour égal
        """
        
        if not allocation:
            # Allocation égale
            n = len(variants)
            allocation = {v: 100/n for v in variants}
        
        self.experiments[name] = {
            'variants': variants,
            'allocation': allocation
        }
    
    def get_variant(self, experiment_name, user_id=None):
        """
        Détermine la variante pour un utilisateur
        
        Args:
            experiment_name: Nom de l'expérience
            user_id: ID utilisateur (pour sticky assignment)
        
        Returns:
            URL de la variante
        """
        
        if experiment_name not in self.experiments:
            return None
        
        experiment = self.experiments[experiment_name]
        
        # Sticky assignment basé sur user_id
        if user_id:
            hash_value = int(hashlib.md5(
                f"{experiment_name}:{user_id}".encode()
            ).hexdigest(), 16)
            
            percentage = (hash_value % 100)
        else:
            # Assignment aléatoire
            percentage = random.randint(0, 99)
        
        # Déterminer variante selon allocation
        cumulative = 0
        for variant, allocation in experiment['allocation'].items():
            cumulative += allocation
            if percentage < cumulative:
                return experiment['variants'][variant]
        
        # Fallback
        return list(experiment['variants'].values())[0]

# Configuration
ab_testing = ABTesting()

# Expérience: Nouvelle API vs Ancienne API
ab_testing.create_experiment(
    name='api_v2_rollout',
    variants={
        'control': 'http://api-v1:5000',
        'treatment': 'http://api-v2:5001'
    },
    allocation={
        'control': 90,    # 90% sur v1
        'treatment': 10   # 10% sur v2
    }
)

# Usage
@app.route('/api/products')
def products_ab():
    user_id = request.headers.get('X-User-Id')
    
    target = ab_testing.get_variant('api_v2_rollout', user_id)
    
    response = proxy.forward_request(
        target_url=target,
        method=request.method,
        path='/api/products',
        headers=dict(request.headers)
    )
    
    # Ajouter header pour tracking
    response.headers['X-AB-Variant'] = 'treatment' if 'v2' in target else 'control'
    
    return response


# === FEATURE: CANARY DEPLOYMENTS ===

# app/plugins/canary.py

class CanaryDeployment:
    """Déploiement canary progressif"""
    
    def __init__(self):
        self.canaries = {}
    
    def register_canary(
        self,
        service_name,
        stable_url,
        canary_url,
        canary_percentage=10
    ):
        """Enregistre un déploiement canary"""
        self.canaries[service_name] = {
            'stable': stable_url,
            'canary': canary_url,
            'percentage': canary_percentage
        }
    
    def get_target(self, service_name):
        """Détermine si requête va vers canary ou stable"""
        
        if service_name not in self.canaries:
            return None
        
        canary = self.canaries[service_name]
        
        # Décision aléatoire selon pourcentage
        if random.randint(1, 100) <= canary['percentage']:
            return canary['canary']
        else:
            return canary['stable']
    
    def increase_canary(self, service_name, step=10):
        """Augmente le trafic canary"""
        if service_name in self.canaries:
            current = self.canaries[service_name]['percentage']
            self.canaries[service_name]['percentage'] = min(100, current + step)
    
    def promote_canary(self, service_name):
        """Promouvoir canary en stable"""
        if service_name in self.canaries:
            canary_url = self.canaries[service_name]['canary']
            self.canaries[service_name]['stable'] = canary_url
            self.canaries[service_name]['percentage'] = 0

# Configuration
canary = CanaryDeployment()

canary.register_canary(
    service_name='product-service',
    stable_url='http://product-v1.2.3:5000',
    canary_url='http://product-v1.3.0:5000',
    canary_percentage=5  # Commencer avec 5%
)

# Usage
@app.route('/api/products')
def products_canary():
    target = canary.get_target('product-service')
    
    response = proxy.forward_request(
        target_url=target,
        method=request.method,
        path='/api/products',
        headers=dict(request.headers)
    )
    
    # Tracker version
    response.headers['X-Service-Version'] = 'canary' if 'v1.3.0' in target else 'stable'
    
    return response

# Route admin pour contrôler canary
@app.route('/admin/canary/<service>/increase', methods=['POST'])
def increase_canary_traffic(service):
    canary.increase_canary(service, step=10)
    return jsonify({
        'service': service,
        'new_percentage': canary.canaries[service]['percentage']
    })


# === FEATURE: API DOCUMENTATION AUTO ===

# app/plugins/api_docs.py

class APIDocumentation:
    """Génère documentation API automatiquement"""
    
    def __init__(self):
        self.endpoints = []
    
    def document_route(self, path, methods, description, params=None):
        """Documente un endpoint"""
        self.endpoints.append({
            'path': path,
            'methods': methods,
            'description': description,
            'parameters': params or []
        })
    
    def generate_openapi(self):
        """Génère spécification OpenAPI"""
        return {
            'openapi': '3.0.0',
            'info': {
                'title': 'API Gateway',
                'version': '1.0.0',
                'description': 'API Gateway Documentation'
            },
            'paths': self._generate_paths()
        }
    
    def _generate_paths(self):
        """Génère section paths OpenAPI"""
        paths = {}
        
        for endpoint in self.endpoints:
            path = endpoint['path']
            paths[path] = {}
            
            for method in endpoint['methods']:
                paths[path][method.lower()] = {
                    'summary': endpoint['description'],
                    'parameters': endpoint['parameters']
                }
        
        return paths

# Configuration
api_docs = APIDocumentation()

# Documenter routes
api_docs.document_route(
    path='/api/products',
    methods=['GET'],
    description='Liste tous les produits',
    params=[
        {'name': 'page', 'in': 'query', 'type': 'integer'},
        {'name': 'per_page', 'in': 'query', 'type': 'integer'}
    ]
)

# Route pour accéder à la doc
@app.route('/api/docs')
def api_documentation():
    return jsonify(api_docs.generate_openapi())


# === DEBUGGING & TROUBLESHOOTING ===


# === DEBUG MODE ===

# app/middleware/debug.py

class DebugMiddleware:
    """Middleware de debug avec informations détaillées"""
    
    def __init__(self, enabled=False):
        self.enabled = enabled
    
    def __call__(self, f):
        @wraps(f)
        def wrapper(*args, **kwargs):
            if not self.enabled:
                return f(*args, **kwargs)
            
            import time
            start = time.time()
            
            # Capturer info requête
            debug_info = {
                'request': {
                    'method': request.method,
                    'path': request.path,
                    'headers': dict(request.headers),
                    'body': request.get_data(as_text=True)[:500]  # Max 500 chars
                }
            }
            
            # Exécuter
            try:
                response = f(*args, **kwargs)
                
                # Capturer info réponse
                debug_info['response'] = {
                    'status': response.status_code if hasattr(response, 'status_code') else 0,
                    'headers': dict(response.headers) if hasattr(response, 'headers') else {},
                    'body': response.get_data(as_text=True)[:500] if hasattr(response, 'get_data') else ''
                }
                
                debug_info['duration_ms'] = (time.time() - start) * 1000
                
                # Ajouter dans header
                if hasattr(response, 'headers'):
                    response.headers['X-Debug-Info'] = json.dumps(debug_info)
                
                return response
            
            except Exception as e:
                debug_info['error'] = str(e)
                debug_info['duration_ms'] = (time.time() - start) * 1000
                
                print(json.dumps(debug_info, indent=2))
                raise
        
        return wrapper


# === REQUEST TRACING ===

# app/middleware/tracing.py

import uuid

class RequestTracing:
    """Traçage de requêtes à travers les services"""
    
    def trace_request(self, f):
        @wraps(f)
        def wrapper(*args, **kwargs):
            # Générer ou récupérer trace ID
            trace_id = request.headers.get('X-Trace-ID')
            if not trace_id:
                trace_id = str(uuid.uuid4())
            
            # Stocker dans context
            g.trace_id = trace_id
            
            # Logger début
            self._log_trace_start(trace_id)
            
            # Exécuter
            try:
                response = f(*args, **kwargs)
                
                # Logger fin
                self._log_trace_end(trace_id, response)
                
                # Ajouter trace ID dans réponse
                if hasattr(response, 'headers'):
                    response.headers['X-Trace-ID'] = trace_id
                
                return response
            
            except Exception as e:
                self._log_trace_error(trace_id, e)
                raise
        
        return wrapper
    
    def _log_trace_start(self, trace_id):
        print(f"[TRACE:{trace_id}] Request started: {request.method} {request.path}")
    
    def _log_trace_end(self, trace_id, response):
        status = response.status_code if hasattr(response, 'status_code') else 0
        print(f"[TRACE:{trace_id}] Request completed: {status}")
    
    def _log_trace_error(self, trace_id, error):
        print(f"[TRACE:{trace_id}] Request failed: {error}")


# === PERFORMANCE PROFILING ===

# app/utils/profiling.py

import cProfile
import pstats
import io

class PerformanceProfiler:
    """Profile les performances du gateway"""
    
    def profile(self, f):
        @wraps(f)
        def wrapper(*args, **kwargs):
            profiler = cProfile.Profile()
            profiler.enable()
            
            result = f(*args, **kwargs)
            
            profiler.disable()
            
            # Générer rapport
            s = io.StringIO()
            ps = pstats.Stats(profiler, stream=s).sort_stats('cumulative')
            ps.print_stats(20)  # Top 20
            
            print("=== Performance Profile ===")
            print(s.getvalue())
            
            return result
        
        return wrapper


# === CONCLUSION ===

[OK] **API GATEWAY CUSTOM COMPLET**

Vous disposez maintenant d'un API Gateway entièrement fonctionnel avec:

**Fonctionnalités Core:**
- [OK] Routing dynamique
- [OK] Service Registry
- [OK] Health Checks
- [OK] Load Balancing (5 stratégies)
- [OK] HTTP Proxy optimisé

**Sécurité:**
- [OK] Authentication (JWT, OAuth2)
- [OK] Authorization
- [OK] Rate Limiting
- [OK] IP Whitelisting
- [OK] Request Signing
- [OK] CORS

**Performance:**
- [OK] Caching (intelligent)
- [OK] Compression
- [OK] Connection Pooling
- [OK] Request Batching
- [OK] Circuit Breaker

**Observabilité:**
- [OK] Metrics (Prometheus)
- [OK] Logging structuré
- [OK] Distributed Tracing
- [OK] Health Monitoring
- [OK] Alerting

**Features Avancées:**
- [OK] API Versioning
- [OK] Request Aggregation
- [OK] Graceful Degradation
- [OK] A/B Testing
- [OK] Canary Deployments
- [OK] Dynamic Routing
- [OK] Request/Response Transformation

**Déploiement:**
- [OK] Docker
- [OK] Kubernetes
- [OK] Configuration YAML
- [OK] CI/CD ready

**Documentation:**
- [OK] Code commenté
- [OK] Exemples d'utilisation
- [OK] Tests
- [OK] OpenAPI auto-génération

[DOCS] **Ressources pour aller plus loin:**
- Kong documentation: https://docs.konghq.com/
- NGINX Plus: https://www.nginx.com/products/nginx/
- AWS API Gateway: https://aws.amazon.com/api-gateway/
- Envoy Proxy: https://www.envoyproxy.io/
- Traefik: https://doc.traefik.io/traefik/

[OBJECTIF] **Prochaines étapes:**
1. Tester en local
2. Adapter à vos besoins
3. Ajouter tests unitaires
4. Déployer en staging
5. Monitorer et optimiser
6. Migrer vers production

Bon développement! [RAPIDE]