# Fichier: python_cheats/cheatsheets/twisted.txt
# Cheatsheet Twisted - Framework Asynchrone Python - Guide Complet


[OK] INTRODUCTION À TWISTED

# === QU'EST-CE QUE TWISTED? ===

# Twisted est un framework réseau événementiel (event-driven) pour Python
# Créé en 2002, c'est l'un des frameworks asynchrones les plus matures
# 
# Caractéristiques principales:
# - Programmation asynchrone non-bloquante
# - Architecture event-driven (réagit aux événements réseau)
# - Support de nombreux protocoles réseau (TCP, UDP, SSL/TLS, HTTP, SSH, DNS, etc.)
# - Cross-platform (Linux, Windows, macOS)
# - Production-ready avec des milliers de déploiements

# === CONCEPTS FONDAMENTAUX ===

# 1. REACTOR (Boucle d'événements)
#    Le cœur de Twisted - gère tous les événements I/O
#    Une seule boucle qui surveille les sockets, timers, callbacks
#    Pattern: "Ne m'appelez pas, je vous appellerai"

# 2. PROTOCOL (Gestion des connexions)
#    Classe qui gère UNE connexion réseau
#    Méthodes appelées automatiquement lors d'événements:
#    - connectionMade() : connexion établie
#    - dataReceived() : données reçues
#    - connectionLost() : connexion fermée

# 3. FACTORY (Créateur de Protocols)
#    Crée des instances de Protocol pour chaque connexion
#    Permet de partager l'état entre connexions
#    Exemple: liste de clients connectés dans un chat

# 4. TRANSPORT (Canal de communication)
#    Interface pour envoyer/recevoir données
#    Abstraction sur socket/fichier/autre
#    Fourni automatiquement au Protocol

# 5. DEFERRED (Promesse de résultat futur)
#    Comme les Promises en JavaScript
#    Permet de chaîner des opérations asynchrones
#    Gère succès (callback) et erreurs (errback)

# === POURQUOI UTILISER TWISTED? ===

# Avantages:
# [OK] Performance: gère des milliers de connexions simultanées
# [OK] Complet: protocoles réseau déjà implémentés
# [OK] Mature: 20+ ans de développement, très stable
# [OK] Testé: excellent framework de test intégré (Trial)
# [OK] Documentation: très bien documenté avec exemples
# [OK] Production: utilisé par Scrapy, Buildbot, Apple, Lucasfilm

# Cas d'usage idéaux:
# - Serveurs TCP/UDP personnalisés
# - Protocoles réseau personnalisés
# - Applications temps-réel (chat, gaming)
# - Web scraping à grande échelle (Scrapy)
# - Proxies et load balancers
# - Serveurs de jeu multi-joueurs
# - IoT et systèmes embarqués

# === INSTALLATION ===

# Installation de base
pip install twisted

# Avec support TLS/SSL (recommandé)
pip install twisted[tls]
# Installe aussi: pyOpenSSL, service-identity

# Avec support HTTP/2
pip install twisted[http2]
# Installe aussi: h2, priority

# Installation complète (tous les extras)
pip install twisted[all]
# Inclut: tls, http2, conch (SSH), soap, serial

# Installation pour développement
pip install twisted[dev]
# Inclut outils de test et documentation

# Vérifier installation
python -c "import twisted; print(twisted.__version__)"
twist --version  # Outil en ligne de commande

# Version minimale Python requise: 3.7+
# (Twisted 20.3+ pour Python 3.7+, versions anciennes supportent Python 2.7)

# === STRUCTURE D'UNE APPLICATION TWISTED ===

# Architecture en 3 couches:

# 1. PROTOCOL LAYER (Logique métier)
#    Définit comment traiter les données reçues
#    Exemple: parser des commandes, formatter des réponses

from twisted.internet.protocol import Protocol

class MyProtocol(Protocol):
    def dataReceived(self, data):
        # Logique métier ici
        self.transport.write(b"Response")

# 2. FACTORY LAYER (Gestion des connexions)
#    Crée les Protocol, gère l'état partagé

from twisted.internet.protocol import Factory

class MyFactory(Factory):
    protocol = MyProtocol
    
    def __init__(self):
        self.clients = []  # État partagé

# 3. REACTOR LAYER (Moteur d'événements)
#    Gère la boucle d'événements, écoute sur les ports

from twisted.internet import reactor

factory = MyFactory()
reactor.listenTCP(8000, factory)  # Écouter sur port 8000
reactor.run()  # Démarrer la boucle (bloque jusqu'à arrêt)

# === MODÈLE D'EXÉCUTION ===

# Twisted utilise un modèle MONO-THREAD par défaut
# Avantages:
# - Pas de problèmes de concurrence
# - Pas de locks/mutex nécessaires
# - Performances excellentes (pas de context switching)

# Important: NE JAMAIS bloquer dans le reactor thread!
# [X] Mauvais:
def dataReceived(self, data):
    time.sleep(5)  # BLOQUE TOUT LE REACTOR!
    
# [OK] Bon:
def dataReceived(self, data):
    reactor.callLater(5, self.process_data, data)  # Non-bloquant

# Pour opérations bloquantes, utiliser threads:
from twisted.internet import threads
d = threads.deferToThread(blocking_function)

# === PHILOSOPHIE "CALLBACK-DRIVEN" ===

# Twisted n'attend jamais - il enregistre des callbacks
# Au lieu de:
result = wait_for_data()  # Bloquant
process(result)

# Twisted fait:
def handle_data(result):
    process(result)

d = get_data_async()  # Retourne immédiatement un Deferred
d.addCallback(handle_data)  # Appelé quand données prêtes

# Avantage: le reactor peut gérer d'autres événements pendant l'attente

# === EXEMPLE MINIMAL COMPLET ===

from twisted.internet import reactor, protocol

# Protocol: que faire avec les données
class Echo(protocol.Protocol):
    def dataReceived(self, data):
        print(f"Reçu: {data}")
        self.transport.write(data)  # Renvoyer les données

# Factory: créer des Protocols
class EchoFactory(protocol.Factory):
    def buildProtocol(self, addr):
        print(f"Connexion depuis {addr}")
        return Echo()

# Reactor: démarrer le serveur
if __name__ == '__main__':
    reactor.listenTCP(8000, EchoFactory())
    print("Serveur echo sur port 8000")
    reactor.run()

# Tester avec: telnet localhost 8000

# === DIFFÉRENCE AVEC D'AUTRES FRAMEWORKS ===

# TWISTED vs ASYNCIO (standard library):
# - Twisted: plus complet, plus de protocoles built-in
# - asyncio: syntaxe moderne async/await, standard Python
# - Twisted peut s'intégrer avec asyncio si nécessaire

# TWISTED vs TORNADO:
# - Twisted: framework général tous protocoles
# - Tornado: focus sur HTTP/WebSocket

# TWISTED vs GEVENT:
# - Twisted: asynchrone explicite (callbacks)
# - Gevent: asynchrone implicite (greenlets)


[OK] REACTOR - CŒUR DE TWISTED

# === CONCEPT DU REACTOR ===

# Le Reactor est la BOUCLE D'ÉVÉNEMENTS centrale de Twisted
# C'est un singleton (une seule instance par processus)
# 
# Fonctionnement:
# 1. Surveille les file descriptors (sockets, fichiers)
# 2. Détecte les événements (données disponibles, connexion, timeout)
# 3. Appelle les callbacks appropriés
# 4. Recommence indéfiniment jusqu'à reactor.stop()

# Analogie: Le reactor est comme un chef d'orchestre
# - Les sockets/timers sont les musiciens
# - Les callbacks sont les partitions
# - Le reactor coordonne tout sans bloquer

# === IMPORTER LE REACTOR ===

from twisted.internet import reactor

# IMPORTANT: Le reactor est importé, pas instancié
# Twisted crée automatiquement l'instance unique
# C'est un singleton global

# Différents types de reactors disponibles:
# - select (défaut, cross-platform)
# - poll (Linux)
# - epoll (Linux, haute performance)
# - kqueue (BSD, macOS)
# - iocp (Windows, haute performance)
# - asyncio (intégration avec asyncio)

# Pour changer de reactor, installer AVANT d'importer reactor:
from twisted.internet import epollreactor
epollreactor.install()
from twisted.internet import reactor  # Utilise maintenant epoll

# === DÉMARRER LE REACTOR ===

# Démarrer la boucle d'événements (BLOQUANT)
reactor.run()
# [ATTENTION] Cette ligne BLOQUE jusqu'à reactor.stop()
# Tout le code après reactor.run() n'est exécuté qu'après l'arrêt

# Code exécuté immédiatement au démarrage:
reactor.callWhenRunning(my_startup_function)
# Utile pour initialisation sans bloquer avant reactor.run()

# Arrêter le reactor (depuis un callback)
reactor.stop()
# [ATTENTION] L'arrêt n'est pas immédiat, il se fait proprement
# Termine les opérations en cours, ferme les connexions

# Vérifier si reactor est en cours d'exécution:
if reactor.running:
    print("Reactor actif")

# === PLANIFIER DES APPELS (SCHEDULING) ===

# 1. APPEL UNIQUE DIFFÉRÉ (comme setTimeout en JavaScript)

def my_function(arg1, arg2):
    print(f"Appelé avec {arg1}, {arg2}")

# Appeler après 2.5 secondes
delayed_call = reactor.callLater(2.5, my_function, "hello", "world")

# delayed_call est un IDelayedCall - permet d'annuler
if not delayed_call.called:  # Pas encore appelé?
    delayed_call.cancel()  # Annuler l'appel
    
# Vérifier temps restant:
remaining = delayed_call.getTime() - reactor.seconds()

# Réinitialiser le délai (repousse l'appel):
delayed_call.reset(5.0)  # Repousse de 5 secondes

# 2. APPEL IMMÉDIAT (prochain cycle du reactor)

reactor.callLater(0, my_function)
# Équivalent à setImmediate en Node.js
# Permet de céder le contrôle au reactor
# Utile pour éviter de bloquer pendant longues opérations

# 3. APPELS RÉPÉTÉS (comme setInterval)

from twisted.internet import task

def periodic_function():
    print("Appelé toutes les 5 secondes")
    # Retourner peut arrêter la boucle:
    # return False  # Arrête la boucle
    # return True   # Continue (par défaut)

# Créer LoopingCall
loop = task.LoopingCall(periodic_function)

# Démarrer (interval en secondes)
loop.start(5.0)
# Par défaut, première exécution immédiate

# Démarrer avec délai avant première exécution:
loop.start(5.0, now=False)
# Première exécution après 5 secondes

# Arrêter la boucle:
loop.stop()

# LoopingCall avec gestion d'erreurs:
def on_error(failure):
    print(f"Erreur dans boucle: {failure}")
    # Par défaut, erreur arrête la boucle
    # Retourner quelque chose pour continuer

loop = task.LoopingCall(risky_function)
loop.start(5.0).addErrback(on_error)

# 4. COOPÉRATIVE TASK (pour longues opérations)

from twisted.internet import task

def long_operation():
    # Opération qui prend du temps
    for i in range(1000000):
        # Faire un peu de travail
        result = i * i
        
        # Céder le contrôle périodiquement
        if i % 1000 == 0:
            yield  # Permet au reactor de gérer autres événements

# Exécuter de manière coopérative:
cooperator = task.Cooperator()
cooperator.coiterate(long_operation())

# Ou avec react.cooperate:
task.cooperate(long_operation())

# === GESTION DU TEMPS ===

# Obtenir le temps actuel (secondes depuis epoch, float)
current_time = reactor.seconds()
# Plus précis et rapide que time.time()
# Utilise l'horloge interne du reactor

# Différence avec time.time():
# - reactor.seconds() est monotone (ne recule jamais)
# - Résistant aux changements d'heure système
# - Plus performant (mis en cache)

# Mesurer durée d'opération:
start = reactor.seconds()
# ... opération ...
duration = reactor.seconds() - start

# === THREADS AVEC REACTOR ===

# [ATTENTION] RÈGLE D'OR: Le reactor est mono-thread
# Ne JAMAIS faire d'opérations bloquantes dans le reactor thread

# 1. EXÉCUTER FONCTION BLOQUANTE DANS THREAD SÉPARÉ

from twisted.internet import threads

def blocking_function(x, y):
    import time
    time.sleep(2)  # Opération bloquante
    return x + y

# Exécuter dans thread pool
d = threads.deferToThread(blocking_function, 5, 3)

# d est un Deferred qui sera résolu dans le reactor thread
d.addCallback(lambda result: print(f"Résultat: {result}"))

# 2. APPELER DEPUIS THREAD VERS REACTOR THREAD

import threading

def worker_thread():
    # Ce code tourne dans un thread séparé
    time.sleep(5)
    result = "Travail terminé"
    
    # Pour exécuter code dans reactor thread:
    reactor.callFromThread(update_ui, result)
    # [ATTENTION] NE JAMAIS manipuler objets Twisted depuis autre thread
    # TOUJOURS utiliser callFromThread

def update_ui(result):
    # Ce code tourne dans le reactor thread
    print(f"Update: {result}")

# Démarrer thread
t = threading.Thread(target=worker_thread)
t.start()

# 3. THREAD POOL DU REACTOR

# Twisted a un thread pool intégré pour opérations bloquantes
thread_pool = reactor.getThreadPool()

# Configurer taille du pool:
reactor.suggestThreadPoolSize(10)  # 10 threads max

# === ÉVÉNEMENTS SYSTÈME ===

# Hooks pour événements du cycle de vie du reactor

# 1. AVANT DÉMARRAGE
def on_startup():
    print("Reactor démarre")

reactor.addSystemEventTrigger('before', 'startup', on_startup)

# 2. APRÈS DÉMARRAGE
def on_running():
    print("Reactor en cours d'exécution")

reactor.addSystemEventTrigger('after', 'startup', on_running)

# 3. AVANT ARRÊT (cleanup)
def on_shutdown():
    print("Nettoyage avant arrêt...")
    # Fermer connexions, sauvegarder état, etc.

reactor.addSystemEventTrigger('before', 'shutdown', on_shutdown)

# 4. APRÈS ARRÊT
def on_stopped():
    print("Reactor arrêté")

reactor.addSystemEventTrigger('after', 'shutdown', on_stopped)

# Phases disponibles: 'startup', 'shutdown'
# Moments: 'before', 'during', 'after'

# === SIGNAUX SYSTÈME ===

# Gérer signaux Unix (SIGTERM, SIGINT, etc.)

import signal

def handle_sigterm(signum, frame):
    print("SIGTERM reçu, arrêt gracieux...")
    reactor.stop()

# Enregistrer handler
reactor.addSystemEventTrigger('before', 'startup',
    signal.signal, signal.SIGTERM, handle_sigterm)

# Par défaut, Twisted gère déjà SIGINT (Ctrl+C)
# Pour personnaliser:
def custom_sigint():
    print("Ctrl+C pressé, arrêt...")
    reactor.stop()

reactor.addSystemEventTrigger('before', 'startup',
    signal.signal, signal.SIGINT, 
    lambda s, f: reactor.callFromThread(custom_sigint))

# === DÉLAI AVEC DEFERRED (alternative à callLater) ===

from twisted.internet import task

# Créer Deferred qui se résout après délai
d = task.deferLater(reactor, 3.0, lambda: "Résultat après 3s")

# Avec fonction et arguments:
d = task.deferLater(reactor, 3.0, my_function, arg1, arg2)

# Chaîner avec autres opérations:
d.addCallback(lambda r: print(f"Reçu: {r}"))

# === EXEMPLE COMPLET: TIMER AVEC COMPTE À REBOURS ===

from twisted.internet import reactor, task

class CountdownTimer:
    def __init__(self, seconds):
        self.remaining = seconds
        self.loop = task.LoopingCall(self.tick)
    
    def start(self):
        print(f"Timer: {self.remaining} secondes")
        self.loop.start(1.0)  # Toutes les secondes
    
    def tick(self):
        self.remaining -= 1
        print(f"Reste: {self.remaining}")
        
        if self.remaining <= 0:
            print("Timer terminé!")
            self.loop.stop()
            reactor.stop()

# Utilisation:
timer = CountdownTimer(10)
reactor.callWhenRunning(timer.start)
reactor.run()

# === BONNES PRATIQUES ===

# [OK] Toujours utiliser callLater/deferLater pour délais
#   Ne JAMAIS utiliser time.sleep()

# [OK] Pour opérations longues, utiliser:
#   - threads.deferToThread pour bloquantes
#   - task.cooperate pour CPU-intensives

# [OK] Un seul reactor.run() par processus
#   Généralement à la fin du script principal

# [OK] Cleanup dans shutdown triggers
#   Fermer fichiers, connexions, sauvegarder état

# [X] Ne jamais faire:
#   - time.sleep() dans reactor thread
#   - Boucles infinies sans yield
#   - Opérations I/O bloquantes
#   - Calculs CPU-intensifs sans pause

# === DEBUGGING REACTOR ===

# Activer mode debug:
import sys
from twisted.python import log
log.startLogging(sys.stdout)

# Voir tous les événements:
reactor.addSystemEventTrigger('before', 'startup', 
    lambda: log.msg("=== REACTOR START ==="))

# Mesurer performance:
from twisted.internet import task

def measure_reactor_responsiveness():
    last_time = reactor.seconds()
    
    def check():
        nonlocal last_time
        current = reactor.seconds()
        delay = current - last_time - 1.0
        if delay > 0.1:  # Plus de 100ms de retard
            print(f"[ATTENTION] Reactor lent: +{delay:.3f}s")
        last_time = current
    
    task.LoopingCall(check).start(1.0)

reactor.callWhenRunning(measure_reactor_responsiveness)


[OK] DEFERRED - GESTION ASYNCHRONE

# === CONCEPT DU DEFERRED ===

# Deferred = "Promesse" d'un résultat futur (comme Promise en JavaScript)
# Permet de gérer des opérations asynchrones sans bloquer
#
# Problème résolu:
# Sans Deferred (code bloquant):
result = wait_for_network()  # [X] Bloque pendant l'attente
process(result)

# Avec Deferred (code non-bloquant):
d = fetch_from_network()  # [OK] Retourne immédiatement
d.addCallback(process)     # [OK] process() appelé quand prêt

# Analogie: Commander au restaurant
# - Vous passez commande (créer Deferred)
# - Vous donnez votre numéro (addCallback)
# - Vous faites autre chose pendant la préparation
# - On vous appelle quand c'est prêt (callback exécuté)

# === CRÉER UN DEFERRED ===

from twisted.internet.defer import Deferred

# Créer un Deferred vide
d = Deferred()

# État initial: en attente (pending)
# Deux issues possibles:
# 1. Succès -> callback avec un résultat
# 2. Échec -> errback avec une erreur

# === AJOUTER CALLBACKS (succès) ===

def handle_success(result):
    print(f"Succès: {result}")
    return result  # Passe au callback suivant (si existe)

d = Deferred()
d.addCallback(handle_success)

# Chaîner plusieurs callbacks:
d.addCallback(handle_success)
d.addCallback(lambda r: r * 2)  # Transforme le résultat
d.addCallback(lambda r: print(f"Final: {r}"))

# Chaque callback reçoit le résultat du précédent
# Si un callback retourne une valeur, elle devient le résultat
# Si un callback ne retourne rien (None), None est passé

# === AJOUTER ERRBACKS (erreurs) ===

def handle_error(failure):
    # failure est un objet twisted.python.failure.Failure
    print(f"Erreur: {failure.value}")  # L'exception
    print(f"Type: {failure.type}")     # Classe de l'exception
    print(f"Traceback: {failure.getTraceback()}")
    
    # Options de gestion:
    # 1. Retourner une valeur -> convertit en succès
    return "valeur_par_défaut"
    
    # 2. Raise ou retourner Failure -> propage l'erreur
    # return failure
    
    # 3. Ne rien retourner -> propage None (succès)

d = Deferred()
d.addErrback(handle_error)

# === AJOUTER LES DEUX (callback ET errback) ===

# addBoth: appelé dans tous les cas (succès ou erreur)
def handle_both(result):
    if isinstance(result, Failure):
        print("C'était une erreur")
    else:
        print("C'était un succès")
    return result

d = Deferred()
d.addBoth(handle_both)

# Ou ajouter séparément:
d.addCallbacks(success_handler, error_handler)

# === DÉCLENCHER LE DEFERRED ===

# 1. Déclencher avec SUCCÈS
d = Deferred()
d.addCallback(lambda r: print(f"Résultat: {r}"))
d.callback(42)  # Déclenche avec résultat 42
# Affiche: Résultat: 42

# [ATTENTION] Un Deferred ne peut être déclenché qu'UNE SEULE FOIS!
# d.callback(43)  # [X] AlreadyCalledError!

# 2. Déclencher avec ERREUR
d = Deferred()
d.addErrback(lambda f: print(f"Erreur: {f.value}"))
d.errback(Exception("Quelque chose a échoué"))
# Affiche: Erreur: Quelque chose a échoué

# Ou déclencher avec une exception existante:
try:
    1 / 0
except:
    import sys
    d.errback(sys.exc_info())

# === CHAÎNE DE CALLBACKS ===

# Les callbacks sont exécutés en SÉQUENCE

from twisted.internet.defer import Deferred

def step1(result):
    print(f"Step 1: {result}")
    return result + 10

def step2(result):
    print(f"Step 2: {result}")
    return result * 2

def step3(result):
    print(f"Step 3: {result}")
    return result

d = Deferred()
d.addCallback(step1)   # 5 -> 15
d.addCallback(step2)   # 15 -> 30
d.addCallback(step3)   # 30 -> 30
d.callback(5)
# Affiche:
# Step 1: 5
# Step 2: 15
# Step 3: 30

# === GESTION D'ERREURS DANS LA CHAÎNE ===

# Si un callback lève une exception, on passe aux errbacks

def may_fail(result):
    if result < 0:
        raise ValueError("Négatif!")
    return result * 2

def handle_error(failure):
    print(f"Erreur capturée: {failure.value}")
    return 0  # Valeur par défaut -> continue en succès

def continue_processing(result):
    print(f"Continue avec: {result}")

d = Deferred()
d.addCallback(may_fail)
d.addErrback(handle_error)  # Capture l'erreur
d.addCallback(continue_processing)  # Continue normalement
d.callback(-5)
# Affiche:
# Erreur capturée: Négatif!
# Continue avec: 0

# === FAILURE OBJECT ===

# Failure encapsule les exceptions pour Twisted

from twisted.python.failure import Failure

# Créer Failure:
f = Failure(ValueError("Erreur"))

# Accéder à l'exception:
exception = f.value  # L'objet exception
exc_type = f.type    # La classe (ValueError)

# Obtenir traceback:
traceback = f.getTraceback()

# Vérifier type d'exception:
f.check(ValueError)  # Retourne ValueError si c'est le type
f.check(ValueError, TypeError)  # Vérifie plusieurs types

# trap: lève l'exception si ce n'est pas le bon type
def handle_specific_error(failure):
    failure.trap(ValueError, TypeError)  # Lève si autre type
    print("Erreur ValueError ou TypeError")

# === DEFERRED PRÊTS (RESOLVED) ===

from twisted.internet.defer import succeed, fail

# Deferred déjà résolu en succès:
d = succeed(42)
d.addCallback(lambda r: print(r))  # Affiche immédiatement: 42

# Deferred déjà résolu en erreur:
d = fail(Exception("Erreur"))
d.addErrback(lambda f: print(f.value))

# Utile pour:
# - Valeurs en cache (retour immédiat)
# - Tests
# - Conditions où résultat est déjà connu

def get_data(use_cache=True):
    if use_cache and data_in_cache:
        return succeed(cached_data)  # Retour immédiat
    else:
        return fetch_from_network()  # Asynchrone

# === ATTENDRE PLUSIEURS DEFERRED ===

from twisted.internet.defer import DeferredList, gatherResults

# 1. DEFERREDLIST - Attend tous (capture succès ET erreurs)

d1 = operation1()  # Retourne Deferred
d2 = operation2()
d3 = operation3()

dl = DeferredList([d1, d2, d3])

def handle_results(results):
    # results = [(success, result), (success, result), ...]
    # success = True/False
    for i, (success, result) in enumerate(results):
        if success:
            print(f"Op {i} réussie: {result}")
        else:
            print(f"Op {i} échouée: {result}")  # result est Failure

dl.addCallback(handle_results)

# Options de DeferredList:

# fireOnOneCallback: se résout au premier succès
dl = DeferredList([d1, d2, d3], fireOnOneCallback=True)
# Retourne (index, result) du premier qui réussit

# fireOnOneErrback: échoue à la première erreur
dl = DeferredList([d1, d2, d3], fireOnOneErrback=True)
# Retourne (index, failure) du premier qui échoue

# consumeErrors: évite "Unhandled error" si erreurs
dl = DeferredList([d1, d2, d3], consumeErrors=True)

# 2. GATHERRESULTS - Échoue si UN seul échoue

d1 = operation1()
d2 = operation2()
d3 = operation3()

d = gatherResults([d1, d2, d3])

def all_succeeded(results):
    # results = [result1, result2, result3]
    # Tous ont réussi
    print(f"Tous réussis: {results}")

def one_failed(failure):
    # Un (ou plus) a échoué
    print(f"Au moins un échec: {failure}")

d.addCallback(all_succeeded)
d.addErrback(one_failed)

# === INLINECALLBACKS - Style async/await ===

# inlineCallbacks permet d'écrire du code asynchrone
# qui RESSEMBLE à du code synchrone
# Comme async/await mais avec yield

from twisted.internet.defer import inlineCallbacks, returnValue

@inlineCallbacks
def async_operation():
    """
    Fonction asynchrone qui attend plusieurs Deferred
    """
    # yield "attend" le Deferred
    result1 = yield fetch_data()
    print(f"Data: {result1}")
    
    # On peut utiliser le résultat immédiatement
    result2 = yield process_data(result1)
    
    # Conditions, boucles, etc. marchent normalement
    if result2 > 100:
        result3 = yield fetch_more()
    else:
        result3 = result2
    
    # Retourner le résultat final
    returnValue(result3)
    # Ou en Python 3.3+: return result3

# Utilisation:
d = async_operation()  # Retourne un Deferred
d.addCallback(lambda r: print(f"Résultat final: {r}"))

# Gestion d'erreurs avec try/except:
@inlineCallbacks
def safe_operation():
    try:
        result = yield risky_operation()
        returnValue(result)
    except ValueError as e:
        print(f"Erreur capturée: {e}")
        returnValue("valeur_par_défaut")

# Avantages:
# [OK] Code plus lisible (style séquentiel)
# [OK] Gestion d'erreurs familière (try/except)
# [OK] Facile de suivre le flux logique

# Inconvénients:
# [X] Moins de contrôle fin que callbacks
# [X] Peut masquer la nature asynchrone

# === EXEMPLE COMPLET: APPEL API ===

from twisted.internet import reactor, defer
from twisted.web.client import Agent, readBody

@defer.inlineCallbacks
def fetch_user_data(user_id):
    """Récupère données utilisateur depuis API"""
    
    # 1. Faire requête HTTP
    agent = Agent(reactor)
    url = f"https://api.example.com/users/{user_id}".encode()
    
    print(f"Fetching user {user_id}...")
    response = yield agent.request(b'GET', url)
    
    # 2. Lire le corps de la réponse
    body = yield readBody(response)
    
    # 3. Parser JSON
    import json
    data = json.loads(body)
    
    # 4. Retourner résultat
    defer.returnValue(data)

@defer.inlineCallbacks
def get_multiple_users(user_ids):
    """Récupère plusieurs utilisateurs en parallèle"""
    
    # Créer liste de Deferred
    deferreds = [fetch_user_data(uid) for uid in user_ids]
    
    # Attendre tous
    results = yield defer.gatherResults(deferreds)
    
    defer.returnValue(results)

# Utilisation:
def main():
    d = get_multiple_users([1, 2, 3, 4, 5])
    d.addCallback(lambda users: print(f"Users: {users}"))
    d.addErrback(lambda f: print(f"Erreur: {f}"))
    d.addBoth(lambda _: reactor.stop())

reactor.callWhenRunning(main)
reactor.run()

# === TIMEOUT SUR DEFERRED ===

from twisted.internet import reactor, defer

# Méthode 1: timeout manuel avec callLater
def with_timeout(deferred, timeout_seconds):
    """Ajoute timeout à un Deferred"""
    
    timeout_deferred = defer.Deferred()
    
    def on_timeout():
        if not deferred.called:
            deferred.errback(defer.TimeoutError("Opération timeout"))
    
    # Planifier timeout
    timeout_call = reactor.callLater(timeout_seconds, on_timeout)
    
    # Annuler timeout si opération réussit
    def cancel_timeout(result):
        if timeout_call.active():
            timeout_call.cancel()
        return result
    
    deferred.addBoth(cancel_timeout)
    return deferred

# Utilisation:
d = slow_operation()
d = with_timeout(d, 5.0)  # Timeout après 5 secondes

# Méthode 2: avec inlineCallbacks
@defer.inlineCallbacks
def operation_with_timeout():
    try:
        # Créer Deferred qui timeout après 5s
        result = yield defer.race([
            slow_operation(),
            defer.deferLater(reactor, 5.0, lambda: None)
        ])
        defer.returnValue(result)
    except defer.TimeoutError:
        print("Timeout!")
        defer.returnValue(None)

# === DEFER TO THREAD - Exécution dans thread ===

from twisted.internet import threads, defer

# Pour fonctions bloquantes (I/O, calculs lourds)

def blocking_operation(x, y):
    """Fonction bloquante (ex: requête DB, calcul lourd)"""
    import time
    time.sleep(2)  # Simule opération lente
    return x + y

# Exécuter dans thread pool
d = threads.deferToThread(blocking_operation, 5, 3)

# d est un Deferred qui se résout dans le reactor thread
d.addCallback(lambda result: print(f"Résultat: {result}"))

# Avec inlineCallbacks:
@defer.inlineCallbacks
def use_blocking_function():
    result = yield threads.deferToThread(blocking_operation, 10, 20)
    print(f"10 + 20 = {result}")
    defer.returnValue(result)

# [ATTENTION] ATTENTION:
# - La fonction s'exécute dans un thread séparé
# - Ne PAS manipuler objets Twisted depuis ce thread
# - Le résultat est retourné au reactor thread

# === BONNES PRATIQUES ===

# [OK] Toujours gérer les erreurs (addErrback)
d = operation()
d.addErrback(log.err)  # Au minimum: logger

# [OK] Préférer inlineCallbacks pour code complexe
@defer.inlineCallbacks
def complex_operation():
    r1 = yield step1()
    r2 = yield step2(r1)
    defer.returnValue(r2)

# [OK] Retourner Deferred depuis fonctions async
def async_function():
    d = Deferred()
    # ... logique ...
    return d  # [OK] Permet au appelant de chaîner

# [X] Ne pas oublier returnValue dans inlineCallbacks
@defer.inlineCallbacks
def wrong():
    result = yield operation()
    # [X] Oubli de returnValue!

@defer.inlineCallbacks
def correct():
    result = yield operation()
    defer.returnValue(result)  # [OK]

# [X] Ne pas mélanger return et callbacks
def wrong():
    d = operation()
    d.addCallback(process)
    return d.result  # [X] Deferred n'a pas .result!

def correct():
    d = operation()
    d.addCallback(process)
    return d  # [OK] Retourner le Deferred


[OK] PROTOCOLS - GESTION DES CONNEXIONS

# === CONCEPT DU PROTOCOL ===

# Protocol = Classe qui gère UNE connexion réseau
# Chaque connexion cliente crée une NOUVELLE instance de Protocol
# 
# Rôle du Protocol:
# 1. Recevoir et traiter les données
# 2. Envoyer des réponses
# 3. Gérer l'état de la connexion
# 4. Réagir aux événements (connexion, déconnexion, erreurs)

# Analogie: Un serveur de restaurant
# - Protocol = un serveur (gère une table)
# - Factory = le restaurant (gère tous les serveurs)
# - Transport = le plateau (moyen de transport)
# - Reactor = le chef (coordonne tout)

# === PROTOCOL DE BASE ===

from twisted.internet.protocol import Protocol

class MyProtocol(Protocol):
    """Protocol minimal pour connexion TCP"""
    
    def connectionMade(self):
        """
        [RAPIDE] Appelé automatiquement quand connexion établie
        
        C'est ici qu'on fait:
        - Initialisation de l'état
        - Enregistrement du client
        - Envoi d'un message de bienvenue
        - Logging de la connexion
        """
        # Obtenir adresse du peer (client)
        peer = self.transport.getPeer()
        print(f"[OK] Nouvelle connexion depuis {peer.host}:{peer.port}")
        
        # Envoyer message de bienvenue
        self.transport.write(b"Bienvenue sur le serveur!\n")
        
        # Initialiser état spécifique à cette connexion
        self.buffer = b""  # Buffer pour données incomplètes
        self.authenticated = False
        self.username = None
    
    def dataReceived(self, data):
        """
        [RAPIDE] Appelé automatiquement quand données reçues
        
        Arguments:
        - data: bytes reçus (peut être incomplet!)
        
        [ATTENTION] IMPORTANT:
        - data peut être n'importe quelle taille (1 byte à plusieurs MB)
        - Peut contenir plusieurs messages
        - Peut contenir message partiel (fragmenté par TCP)
        - TOUJOURS traiter comme flux continu
        """
        print(f"[ENTREE] Reçu {len(data)} bytes: {data}")
        
        # Echo: renvoyer les données
        self.transport.write(b"Echo: " + data)
        
        # Pour traitement plus complexe, utiliser un buffer:
        self.buffer += data
        while b"\n" in self.buffer:
            line, self.buffer = self.buffer.split(b"\n", 1)
            self.processLine(line)
    
    def processLine(self, line):
        """Traiter une ligne complète"""
        print(f"[NOTE] Ligne: {line.decode()}")
    
    def connectionLost(self, reason):
        """
        [RAPIDE] Appelé automatiquement quand connexion perdue
        
        Arguments:
        - reason: twisted.python.failure.Failure
                  Contient la raison de la déconnexion
        
        Causes possibles:
        - Client a fermé la connexion (normal)
        - Timeout réseau
        - Erreur de connexion
        - Serveur a fermé (via loseConnection)
        """
        print(f"[X] Connexion fermée: {reason.value}")
        
        # Cleanup: libérer ressources
        self.buffer = None
        
        # Désenregistrer de la factory si nécessaire
        if hasattr(self.factory, 'unregister'):
            self.factory.unregister(self)

# === ATTRIBUTS IMPORTANTS DU PROTOCOL ===

class ExampleProtocol(Protocol):
    
    def connectionMade(self):
        # 1. TRANSPORT - Interface pour envoyer/recevoir
        self.transport
        # Méthodes principales:
        # - write(data)           : envoyer données
        # - loseConnection()      : fermer connexion proprement
        # - abortConnection()     : fermer brutalement
        # - getPeer()             : adresse distante
        # - getHost()             : adresse locale
        
        # 2. FACTORY - La factory qui a créé ce protocol
        self.factory
        # Permet d'accéder à l'état partagé entre connexions
        # Exemple: self.factory.clients pour broadcast
        
        # Exemple d'utilisation:
        peer = self.transport.getPeer()  # IAddress
        print(f"IP: {peer.host}, Port: {peer.port}")
        
        # Ajouter ce client à la liste de la factory
        self.factory.clients.append(self)

# === MÉTHODES DU TRANSPORT ===

class TransportDemo(Protocol):
    
    def demonstrate_transport(self):
        # ENVOYER DONNÉES
        
        # 1. Envoyer bytes
        self.transport.write(b"Hello, World!\n")
        
        # 2. Envoyer plusieurs morceaux (efficace)
        self.transport.writeSequence([
            b"Ligne 1\n",
            b"Ligne 2\n",
            b"Ligne 3\n"
        ])
        # Plus efficace que 3 appels write() séparés
        
        # FERMER CONNEXION
        
        # 3. Fermeture propre (graceful)
        self.transport.loseConnection()
        # - Envoie les données buffered en attente
        # - Ferme après que tout soit envoyé
        # - Appelle connectionLost() après
        
        # 4. Fermeture brutale (ungraceful)
        self.transport.abortConnection()
        # - Ferme immédiatement
        # - Données buffered sont perdues
        # - À utiliser en cas d'erreur grave
        
        # CONTRÔLE DE FLUX (flow control)
        
        # 5. Pause de réception (backpressure)
        self.transport.pauseProducing()
        # Arrête temporairement de recevoir des données
        # Utile si traitement est trop lent
        
        # 6. Reprise de réception
        self.transport.resumeProducing()
        
        # INFORMATIONS
        
        # 7. Adresse du peer (distant)
        peer = self.transport.getPeer()
        # peer.host = adresse IP (str)
        # peer.port = port (int)
        # peer.type = 'TCP' ou 'UDP'
        
        # 8. Adresse locale (notre serveur)
        host = self.transport.getHost()
        
        # 9. Vérifier si connecté
        if hasattr(self.transport, 'connected'):
            is_connected = self.transport.connected

# === PROTOCOL AVEC ÉTAT ===

class StatefulProtocol(Protocol):
    """
    Protocol qui maintient un état pendant la connexion
    Exemple: authentification, session, conversation
    """
    
    def __init__(self):
        # État initial
        self.state = "WAITING_USERNAME"
        self.username = None
        self.authenticated = False
        self.buffer = b""
        self.message_count = 0
    
    def connectionMade(self):
        self.send_prompt("Entrez votre nom d'utilisateur:")
    
    def dataReceived(self, data):
        # Accumuler dans buffer
        self.buffer += data
        
        # Traiter ligne par ligne
        while b"\n" in self.buffer:
            line, self.buffer = self.buffer.split(b"\n", 1)
            line = line.strip()
            
            # Dispatcher selon l'état
            if self.state == "WAITING_USERNAME":
                self.handle_username(line)
            elif self.state == "WAITING_PASSWORD":
                self.handle_password(line)
            elif self.state == "AUTHENTICATED":
                self.handle_command(line)
    
    def handle_username(self, username):
        """Phase 1: demander username"""
        self.username = username.decode()
        self.state = "WAITING_PASSWORD"
        self.send_prompt("Mot de passe:")
    
    def handle_password(self, password):
        """Phase 2: vérifier password"""
        if self.check_auth(self.username, password.decode()):
            self.authenticated = True
            self.state = "AUTHENTICATED"
            self.send_message(f"Bienvenue {self.username}!")
        else:
            self.send_message("Authentification échouée")
            self.transport.loseConnection()
    
    def handle_command(self, command):
        """Phase 3: traiter commandes"""
        cmd = command.decode().lower()
        
        if cmd == "quit":
            self.send_message("Au revoir!")
            self.transport.loseConnection()
        elif cmd == "help":
            self.send_message("Commandes: help, stats, quit")
        elif cmd == "stats":
            self.send_message(f"Messages envoyés: {self.message_count}")
        else:
            self.send_message("Commande inconnue")
    
    def send_message(self, msg):
        """Helper pour envoyer message"""
        self.transport.write(f"{msg}\n".encode())
        self.message_count += 1
    
    def send_prompt(self, prompt):
        """Helper pour envoyer prompt"""
        self.transport.write(f"{prompt} ".encode())
    
    def check_auth(self, username, password):
        """Vérifier authentification (simplifié)"""
        return username == "admin" and password == "secret"

# === LINERECIEVER - PROTOCOL LIGNE PAR LIGNE ===

from twisted.protocols.basic import LineReceiver

class MyLineProtocol(LineReceiver):
    """
    LineReceiver gère automatiquement le découpage en lignes
    Plus besoin de gérer le buffer manuellement!
    
    Avantages:
    - Gère le buffering automatiquement
    - Split sur délimiteur configurable
    - Protection contre lignes trop longues
    - Mode "raw" pour données binaires
    """
    
    # Configuration
    delimiter = b"\n"  # Séparateur de ligne (défaut: b"\r\n")
    MAX_LENGTH = 16384  # Taille max d'une ligne (16 KB)
    
    def connectionMade(self):
        self.sendLine(b"Bienvenue! Tapez 'help' pour aide")
    
    def lineReceived(self, line):
        """
        [RAPIDE] Appelé pour chaque ligne COMPLÈTE reçue
        
        Arguments:
        - line: bytes sans le délimiteur
        
        Avantages vs dataReceived:
        - Toujours une ligne complète
        - Pas de fragmentation à gérer
        - Pas de buffer manuel
        """
        print(f"Ligne complète: {line}")
        
        # Traiter la ligne
        if line == b"hello":
            self.sendLine(b"Bonjour!")
        elif line == b"quit":
            self.sendLine(b"Au revoir!")
            self.transport.loseConnection()
        else:
            # Echo
            self.sendLine(b"Echo: " + line)
    
    def sendLine(self, line):
        """
        Envoyer ligne (ajoute automatiquement le délimiteur)
        """
        # LineReceiver.sendLine ajoute self.delimiter
        LineReceiver.sendLine(self, line)
    
    def lineLengthExceeded(self, line):
        """
        [RAPIDE] Appelé si ligne dépasse MAX_LENGTH
        
        Par défaut, cette méthode ferme la connexion
        Peut être overridé pour gérer différemment
        """
        print(f"[ATTENTION] Ligne trop longue ({len(line)} bytes)")
        self.sendLine(b"ERROR: Line too long")
        self.transport.loseConnection()
    
    def rawDataReceived(self, data):
        """
        [RAPIDE] Appelé en mode raw (données binaires)
        
        Utiliser setRawMode() pour passer en mode raw
        Utile pour recevoir fichiers, images, etc.
        """
        print(f"Données raw: {len(data)} bytes")
        # Traiter données binaires
        
        # Retourner en mode ligne:
        self.setLineMode()
    
    # Changer de mode
    def setRawMode(self):
        """Passer en mode données brutes"""
        LineReceiver.setRawMode(self)
    
    def setLineMode(self, extra=b""):
        """Retourner en mode ligne par ligne"""
        LineReceiver.setLineMode(self, extra)

# Exemple d'utilisation de mode raw:
class FileReceiver(LineReceiver):
    """Recevoir un fichier après ligne de commande"""
    
    def lineReceived(self, line):
        if line.startswith(b"UPLOAD "):
            # Format: UPLOAD <filename> <size>
            parts = line.split()
            self.filename = parts[1].decode()
            self.file_size = int(parts[2])
            self.received = 0
            self.file_data = b""
            
            # Passer en mode raw pour recevoir fichier
            self.setRawMode()
            self.sendLine(b"OK, send file")
    
    def rawDataReceived(self, data):
        self.file_data += data
        self.received += len(data)
        
        if self.received >= self.file_size:
            # Fichier complet
            with open(self.filename, 'wb') as f:
                f.write(self.file_data[:self.file_size])
            
            # Retourner en mode ligne
            extra = self.file_data[self.file_size:]
            self.setLineMode(extra)
            
            self.sendLine(f"File {self.filename} saved".encode())

# === AUTRES PROTOCOLS DE BASE ===

# 1. INT N STRING RECEIVER - Messages préfixés par longueur

from twisted.protocols.basic import IntNStringReceiver

class MyIntProtocol(IntNStringReceiver):
    """
    Protocol pour messages binaires avec longueur
    Format: [longueur sur 4 bytes][données]
    
    Avantages:
    - Gère messages de taille variable
    - Pas de délimiteur nécessaire
    - Bon pour données binaires
    - Protection contre débordement
    """
    
    # Configuration du format
    structFormat = "!I"  # 4 bytes, unsigned int, big-endian
    prefixLength = 4      # Longueur du préfixe
    MAX_LENGTH = 99999    # Taille max message
    
    def stringReceived(self, string):
        """
        [RAPIDE] Appelé quand message complet reçu
        
        Arguments:
        - string: bytes du message (sans préfixe de longueur)
        """
        print(f"Message reçu ({len(string)} bytes): {string}")
        
        # Répondre
        response = b"OK: " + string
        self.sendString(response)
    
    def sendString(self, data):
        """
        Envoyer message (ajoute automatiquement préfixe longueur)
        """
        IntNStringReceiver.sendString(self, data)
    
    def lengthLimitExceeded(self, length):
        """[RAPIDE] Appelé si message trop grand"""
        print(f"[ATTENTION] Message trop grand: {length} bytes")
        self.transport.loseConnection()

# Formats disponibles:
# Int8String   - 1 byte  (max 255 bytes)
# Int16String  - 2 bytes (max 64 KB)
# Int32String  - 4 bytes (max 4 GB)

from twisted.protocols.basic import Int16StringReceiver

class CompactProtocol(Int16StringReceiver):
    """Pour petits messages (< 64 KB)"""
    MAX_LENGTH = 65535

# 2. NETSTRING RECEIVER - Format netstring

from twisted.protocols.basic import NetstringReceiver

class MyNetstringProtocol(NetstringReceiver):
    """
    Protocol utilisant format netstring
    Format: <longueur>:<données>,
    Exemple: "12:Hello World!,"
    
    Avantages:
    - Format texte lisible
    - Auto-délimitant
    - Simple à débugger
    """
    
    MAX_LENGTH = 99999
    
    def stringReceived(self, string):
        """[RAPIDE] Appelé pour chaque netstring reçu"""
        print(f"Netstring: {string}")
        self.sendString(b"Response: " + string)

# === PROTOCOL STATELESS (UDP) ===

from twisted.internet.protocol import DatagramProtocol

class UDPProtocol(DatagramProtocol):
    """
    Protocol pour UDP (sans connexion)
    
    Différences avec TCP Protocol:
    - Pas de connectionMade/connectionLost
    - Pas de garantie de livraison
    - Pas d'ordre garanti
    - Messages atomiques (pas de fragmentation)
    """
    
    def startProtocol(self):
        """
        [RAPIDE] Appelé quand protocol démarre
        Équivalent de connectionMade pour UDP
        """
        print("UDP Protocol démarré")
        
        # Configurer socket
        self.transport.setBroadcastAllowed(True)
    
    def stopProtocol(self):
        """
        [RAPIDE] Appelé quand protocol s'arrête
        """
        print("UDP Protocol arrêté")
    
    def datagramReceived(self, datagram, address):
        """
        [RAPIDE] Appelé pour chaque datagramme UDP reçu
        
        Arguments:
        - datagram: bytes du message (toujours complet!)
        - address: tuple (host, port) de l'expéditeur
        
        [ATTENTION] Différence importante avec TCP:
        - datagram est TOUJOURS le message COMPLET
        - Pas de fragmentation (contrairement à TCP)
        - Taille max ~65 KB (limite UDP)
        """
        host, port = address
        print(f"[PACKAGE] Datagram de {host}:{port}: {datagram}")
        
        # Répondre
        self.transport.write(b"Response: " + datagram, address)
    
    # Méthodes du transport UDP:
    def demonstrate_udp_transport(self):
        # Envoyer à une adresse spécifique
        self.transport.write(b"Hello", ("192.168.1.100", 9999))
        
        # Broadcast (nécessite setBroadcastAllowed(True))
        self.transport.write(b"Broadcast", ("<broadcast>", 9999))
        
        # Obtenir port local
        host = self.transport.getHost()
        print(f"Écoute sur {host.host}:{host.port}")
        
        # Connecter à une adresse (optionnel, filtre les autres)
        self.transport.connect("192.168.1.100", 9999)
        # Après connect, write() n'a plus besoin d'adresse
        self.transport.write(b"Hello")

# === BONNES PRATIQUES PROTOCOLS ===

class BestPracticesProtocol(Protocol):
    """Exemples de bonnes pratiques"""
    
    def connectionMade(self):
        # [OK] Initialiser TOUT l'état ici
        self.buffer = b""
        self.authenticated = False
        self.last_activity = time.time()
        
        # [OK] Enregistrer dans factory si nécessaire
        if hasattr(self.factory, 'register'):
            self.factory.register(self)
        
        # [OK] Logger les connexions
        peer = self.transport.getPeer()
        log.msg(f"New connection from {peer.host}")
    
    def dataReceived(self, data):
        # [OK] Toujours valider les données
        if not self.validate_data(data):
            self.transport.loseConnection()
            return
        
        # [OK] Mettre à jour timestamp activité
        self.last_activity = time.time()
        
        # [OK] Gérer les erreurs
        try:
            self.process_data(data)
        except Exception as e:
            log.err(e)
            self.send_error("Internal error")
    
    def connectionLost(self, reason):
        # [OK] Toujours cleanup
        self.buffer = None
        
        # [OK] Désenregistrer de factory
        if hasattr(self.factory, 'unregister'):
            self.factory.unregister(self)
        
        # [OK] Logger la déconnexion
        log.msg(f"Connection lost: {reason.value}")
    
    def validate_data(self, data):
        """[OK] Toujours valider input utilisateur"""
        # Vérifier taille
        if len(data) > self.MAX_DATA_SIZE:
            return False
        
        # Vérifier caractères dangereux
        if b'\x00' in data:
            return False
        
        return True
    
    # [X] À ÉVITER:
    def bad_practices(self):
        # [X] Ne JAMAIS bloquer
        time.sleep(5)  # BLOQUE TOUT LE REACTOR!
        
        # [X] Ne pas faire d'I/O synchrone
        with open('file.txt') as f:  # BLOQUANT!
            data = f.read()
        
        # [X] Ne pas oublier de gérer erreurs
        result = risky_operation()  # Peut crasher!
        
        # [X] Ne pas fuir de mémoire
        self.all_data_ever.append(data)  # Croissance infinie!


[OK] FACTORIES - CRÉATION DE PROTOCOLS

# === CONCEPT DE LA FACTORY ===

# Factory = Usine à Protocols
# Rôle principal: créer des instances de Protocol pour chaque connexion
#
# Pourquoi une Factory?
# 1. Partager état entre TOUTES les connexions
# 2. Configurer les Protocols avant utilisation
# 3. Gérer le cycle de vie des connexions (logs, stats)
# 4. Implémenter logique de reconnexion (client)
#
# Analogie: Un restaurant
# - Factory = le restaurant (infrastructure permanente)
# - Protocol = un serveur (gère une table/client)
# - Transport = le plateau (outil de communication)
# 
# Le restaurant (Factory) embauche des serveurs (Protocols)
# pour gérer chaque table (connexion)

# === FACTORY DE BASE (SERVEUR) ===

from twisted.internet.protocol import Factory, Protocol

class MyProtocol(Protocol):
    def connectionMade(self):
        # Accès à la factory
        print(f"Connecté! Factory: {self.factory}")
        self.factory.num_connections += 1
    
    def dataReceived(self, data):
        self.transport.write(b"Echo: " + data)

class MyFactory(Factory):
    """
    Factory simple pour serveur
    """
    
    # 1. Spécifier la CLASSE Protocol (pas une instance!)
    protocol = MyProtocol
    
    def __init__(self):
        """
        Constructeur: initialiser état partagé
        Appelé UNE SEULE FOIS à la création de la factory
        """
        # État partagé entre TOUTES les connexions
        self.num_connections = 0
        self.clients = []  # Liste de tous les clients connectés
        self.start_time = time.time()
    
    def buildProtocol(self, addr):
        """
        [RAPIDE] Appelé pour CHAQUE nouvelle connexion
        
        Arguments:
        - addr: IAddress (adresse du client)
        
        Retourne:
        - Instance de Protocol (ou None pour refuser connexion)
        
        C'est ici qu'on:
        - Crée l'instance de Protocol
        - Configure le Protocol
        - Valide la connexion (IP whitelist, etc.)
        - Log la connexion
        """
        # Vérifier si on accepte cette connexion
        if self.should_accept(addr):
            # Créer instance Protocol
            p = self.protocol()  # Appelle MyProtocol()
            
            # Lier Protocol à Factory
            p.factory = self
            
            # Configuration supplémentaire si nécessaire
            p.client_addr = addr
            
            print(f"[OK] Nouvelle connexion acceptée de {addr}")
            return p
        else:
            print(f"[X] Connexion refusée de {addr}")
            return None  # Refuser la connexion
    
    def should_accept(self, addr):
        """Logique de validation de connexion"""
        # Exemple: limiter nombre de connexions
        if len(self.clients) >= 100:
            return False
        
        # Exemple: whitelist IP
        # if addr.host not in self.allowed_ips:
        #     return False
        
        return True
    
    def startFactory(self):
        """
        [RAPIDE] Appelé quand factory démarre (avant connexions)
        
        Utilisé pour:
        - Initialisation de ressources
        - Ouverture de fichiers/DB
        - Démarrage de tâches périodiques
        - Logging
        """
        print("[USINE] Factory démarrée")
        self.start_time = time.time()
        
        # Exemple: démarrer tâche périodique
        from twisted.internet import task
        self.stats_loop = task.LoopingCall(self.print_stats)
        self.stats_loop.start(60)  # Toutes les 60 secondes
    
    def stopFactory(self):
        """
        [RAPIDE] Appelé quand factory s'arrête (après toutes connexions)
        
        Utilisé pour:
        - Cleanup de ressources
        - Fermeture fichiers/DB
        - Sauvegarde d'état
        - Arrêt tâches périodiques
        """
        print("[USINE] Factory arrêtée")
        
        # Arrêter tâches périodiques
        if hasattr(self, 'stats_loop'):
            self.stats_loop.stop()
        
        # Cleanup
        self.clients.clear()
    
    def print_stats(self):
        """Afficher statistiques"""
        uptime = time.time() - self.start_time
        print(f"[GRAPHIQUE] Stats: {len(self.clients)} clients, uptime: {uptime:.0f}s")

# Utilisation:
from twisted.internet import reactor

factory = MyFactory()
reactor.listenTCP(8000, factory)
reactor.run()

# === FACTORY AVEC ÉTAT PARTAGÉ ===

class ChatProtocol(LineReceiver):
    """Protocol pour chat multi-utilisateur"""
    
    def connectionMade(self):
        # S'enregistrer dans la factory
        self.factory.add_client(self)
        self.sendLine(b"Bienvenue! Tapez votre nom:")
        self.username = None
    
    def lineReceived(self, line):
        if self.username is None:
            # Première ligne = nom d'utilisateur
            self.username = line.decode().strip()
            self.factory.broadcast(f"{self.username} a rejoint".encode())
        else:
            # Messages suivants = chat
            msg = f"{self.username}: {line.decode()}".encode()
            self.factory.broadcast(msg, exclude=self)
    
    def connectionLost(self, reason):
        self.factory.remove_client(self)
        if self.username:
            self.factory.broadcast(
                f"{self.username} est parti".encode()
            )

class ChatFactory(Factory):
    """
    Factory gérant état partagé entre clients
    """
    
    protocol = ChatProtocol
    
    def __init__(self):
        # Dictionnaire: username -> protocol
        self.clients = {}
        
        # Historique des messages
        self.history = []
        self.max_history = 100
    
    def add_client(self, client):
        """Enregistrer nouveau client"""
        # On ne l'ajoute au dict que quand il a un username
        # Pour l'instant juste compter
        print(f"Client connecté (total: {len(self.clients) + 1})")
    
    def remove_client(self, client):
        """Désenregistrer client"""
        if client.username and client.username in self.clients:
            del self.clients[client.username]
            print(f"Client déconnecté: {client.username}")
    
    def broadcast(self, message, exclude=None):
        """
        Envoyer message à tous les clients
        
        Arguments:
        - message: bytes à envoyer
        - exclude: Protocol à exclure (optionnel)
        """
        # Ajouter à l'historique
        self.history.append(message)
        if len(self.history) > self.max_history:
            self.history.pop(0)
        
        # Envoyer à tous sauf exclude
        for username, client in self.clients.items():
            if client != exclude:
                client.sendLine(message)
    
    def get_user_list(self):
        """Obtenir liste des utilisateurs connectés"""
        return list(self.clients.keys())

# === CLIENT FACTORY ===

from twisted.internet.protocol import ClientFactory

class MyClientProtocol(Protocol):
    """Protocol pour client"""
    
    def connectionMade(self):
        print("[OK] Connecté au serveur")
        self.transport.write(b"Hello, server!\n")
    
    def dataReceived(self, data):
        print(f"[ENTREE] Reçu: {data.decode()}")
    
    def connectionLost(self, reason):
        print(f"[X] Déconnecté: {reason.value}")

class MyClientFactory(ClientFactory):
    """
    Factory pour client (différent de Factory serveur)
    
    Différences avec Factory:
    - Gère tentatives de connexion
    - Gère reconnexion automatique (optionnel)
    - Callbacks spécifiques: startedConnecting, clientConnectionLost, etc.
    """
    
    protocol = MyClientProtocol
    
    def startedConnecting(self, connector):
        """
        [RAPIDE] Appelé quand tentative de connexion démarre
        
        Arguments:
        - connector: IConnector (gère la connexion)
        """
        print("[PLUGIN] Tentative de connexion...")
        dest = connector.getDestination()
        print(f"   Vers {dest.host}:{dest.port}")
    
    def buildProtocol(self, addr):
        """
        [RAPIDE] Appelé quand connexion réussit
        """
        print("[OK] Connexion établie!")
        
        # Réinitialiser compteur de tentatives si reconnexion
        if hasattr(self, 'retries'):
            self.retries = 0
        
        return ClientFactory.buildProtocol(self, addr)
    
    def clientConnectionLost(self, connector, reason):
        """
        [RAPIDE] Appelé quand connexion est perdue
        
        Arguments:
        - connector: IConnector
        - reason: Failure (raison de la perte)
        """
        print(f"[X] Connexion perdue: {reason.value}")
        
        # Par défaut, ne reconnecte PAS automatiquement
        # Pour reconnexion automatique, utiliser ReconnectingClientFactory
    
    def clientConnectionFailed(self, connector, reason):
        """
        [RAPIDE] Appelé quand connexion échoue initialement
        
        Arguments:
        - connector: IConnector
        - reason: Failure (raison de l'échec)
        """
        print(f"[X] Connexion échouée: {reason.value}")
        
        # Arrêter le reactor (ou autre logique)
        from twisted.internet import reactor
        reactor.stop()

# Utilisation client:
from twisted.internet import reactor

factory = MyClientFactory()
reactor.connectTCP('localhost', 8000, factory)
reactor.run()

# === RECONNECTING CLIENT FACTORY ===

from twisted.internet.protocol import ReconnectingClientFactory

class RobustClientFactory(ReconnectingClientFactory):
    """
    Factory client avec reconnexion automatique
    
    Stratégie de reconnexion:
    - Première tentative: immédiate
    - Tentatives suivantes: délai exponentiel avec backoff
    - Exemple: 1s, 2s, 4s, 8s, 16s, 32s, 60s (max)
    """
    
    protocol = MyClientProtocol
    
    # Configuration de la reconnexion
    maxDelay = 60        # Délai maximum entre tentatives (secondes)
    initialDelay = 1.0   # Délai initial (secondes)
    factor = 2.0         # Facteur multiplicatif du délai
    maxRetries = None    # Nombre max de tentatives (None = infini)
    jitter = 0.1         # Variation aléatoire du délai (0 à 0.11959)
    
    def __init__(self):
        self.num_reconnects = 0
    
    def startedConnecting(self, connector):
        print(f"[PLUGIN] Tentative #{self.num_reconnects + 1}...")
    
    def buildProtocol(self, addr):
        """
        [RAPIDE] Appelé quand connexion réussit
        Important: réinitialiser le délai!
        """
        print("[OK] Connecté! Réinitialisation des délais de reconnexion")
        
        # IMPORTANT: Réinitialiser le délai de reconnexion
        self.resetDelay()
        
        self.num_reconnects = 0
        return ReconnectingClientFactory.buildProtocol(self, addr)
    
    def clientConnectionLost(self, connector, reason):
        """
        [RAPIDE] Connexion perdue -> reconnexion automatique
        """
        print(f"[X] Connexion perdue: {reason.value}")
        print(f"[HOURGLASS_WITH_FLOWING_SAND] Reconnexion dans {self.delay:.1f}s...")
        
        self.num_reconnects += 1
        
        # ReconnectingClientFactory gère la reconnexion automatiquement
        ReconnectingClientFactory.clientConnectionLost(
            self, connector, reason
        )
    
    def clientConnectionFailed(self, connector, reason):
        """
        [RAPIDE] Connexion échouée -> reconnexion automatique
        """
        print(f"[X] Connexion échouée: {reason.value}")
        
        # Vérifier si on a atteint maxRetries
        if self.maxRetries is not None and self.retries >= self.maxRetries:
            print(f"[INTERDIT] Nombre max de tentatives atteint ({self.maxRetries})")
            self.stopTrying()
            from twisted.internet import reactor
            reactor.stop()
        else:
            print(f"[HOURGLASS_WITH_FLOWING_SAND] Nouvelle tentative dans {self.delay:.1f}s...")
            ReconnectingClientFactory.clientConnectionFailed(
                self, connector, reason
            )
    
    def stopTrying(self):
        """Arrêter les tentatives de reconnexion"""
        print("[STOP] Arrêt des tentatives de reconnexion")
        ReconnectingClientFactory.stopTrying(self)
    
    def retry(self, connector=None):
        """Forcer une tentative de reconnexion immédiate"""
        print("[SYNC] Tentative de reconnexion forcée")
        ReconnectingClientFactory.retry(self, connector)

# Exemple d'utilisation avancée:
factory = RobustClientFactory()
factory.maxRetries = 5  # Max 5 tentatives
factory.maxDelay = 30   # Max 30s entre tentatives

connector = reactor.connectTCP('localhost', 8000, factory)

# Arrêter manuellement les tentatives:
# factory.stopTrying()

# Forcer reconnexion immédiate:
# factory.retry(connector)

# === FACTORY AVEC CONFIGURATION ===

class ConfigurableFactory(Factory):
    """
    Factory configurable avec paramètres
    """
    
    protocol = MyProtocol
    
    def __init__(self, max_clients=100, timeout=300, debug=False):
        """
        Arguments:
        - max_clients: nombre max de clients simultanés
        - timeout: timeout inactivité (secondes)
        - debug: mode debug
        """
        self.max_clients = max_clients
        self.timeout = timeout
        self.debug = debug
        
        self.clients = []
        self.stats = {
            'connections': 0,
            'bytes_sent': 0,
            'bytes_received': 0
        }
    
    def buildProtocol(self, addr):
        # Vérifier limite de clients
        if len(self.clients) >= self.max_clients:
            if self.debug:
                print(f"[ATTENTION] Max clients atteint ({self.max_clients})")
            return None  # Refuser connexion
        
        p = self.protocol()
        p.factory = self
        
        # Configurer le protocol avec paramètres de la factory
        p.timeout = self.timeout
        p.debug = self.debug
        
        self.stats['connections'] += 1
        
        return p

# Utilisation avec différentes configurations:
# Serveur de production
prod_factory = ConfigurableFactory(
    max_clients=1000,
    timeout=600,
    debug=False
)

# Serveur de développement
dev_factory = ConfigurableFactory(
    max_clients=10,
    timeout=60,
    debug=True
)

# === FACTORY AVEC PLUGINS ===

class PluggableFactory(Factory):
    """
    Factory avec système de plugins
    """
    
    protocol = MyProtocol
    
    def __init__(self):
        self.plugins = []
        self.clients = []
    
    def add_plugin(self, plugin):
        """Ajouter un plugin"""
        self.plugins.append(plugin)
        plugin.factory = self
        
        if hasattr(plugin, 'on_load'):
            plugin.on_load()
    
    def buildProtocol(self, addr):
        p = self.protocol()
        p.factory = self
        
        # Notifier les plugins
        for plugin in self.plugins:
            if hasattr(plugin, 'on_connect'):
                plugin.on_connect(addr)
        
        return p
    
    def notify_plugins(self, event, *args):
        """Notifier tous les plugins d'un événement"""
        for plugin in self.plugins:
            method = getattr(plugin, f'on_{event}', None)
            if method:
                method(*args)

# Exemple de plugin:
class LoggingPlugin:
    """Plugin pour logger toutes les connexions"""
    
    def on_load(self):
        print("[LISTE] Plugin de logging chargé")
        self.log_file = open('connections.log', 'a')
    
    def on_connect(self, addr):
        timestamp = time.strftime('%Y-%m-%d %H:%M:%S')
        self.log_file.write(f"{timestamp} - {addr.host}:{addr.port}\n")
        self.log_file.flush()

class StatsPlugin:
    """Plugin pour collecter statistiques"""
    
    def on_load(self):
        self.stats = {'connections': 0, 'messages': 0}
    
    def on_connect(self, addr):
        self.stats['connections'] += 1
    
    def get_stats(self):
        return self.stats.copy()

# Utilisation:
factory = PluggableFactory()
factory.add_plugin(LoggingPlugin())
factory.add_plugin(StatsPlugin())

# === BONNES PRATIQUES FACTORIES ===

class BestPracticesFactory(Factory):
    """Exemples de bonnes pratiques"""
    
    protocol = MyProtocol
    
    def __init__(self):
        # [OK] Initialiser TOUT l'état dans __init__
        self.clients = []
        self.config = {}
        self.resources = None
    
    def startFactory(self):
        """
        [OK] Ouvrir ressources dans startFactory, pas __init__
        Raison: startFactory appelé quand reactor démarre
        """
        # Ouvrir connexions DB, fichiers, etc.
        self.resources = open_database_connection()
        
        # Démarrer tâches périodiques
        from twisted.internet import task
        self.cleanup_task = task.LoopingCall(self.cleanup)
        self.cleanup_task.start(3600)  # Toutes les heures
    
    def stopFactory(self):
        """
        [OK] Toujours cleanup dans stopFactory
        """
        # Arrêter tâches
        if hasattr(self, 'cleanup_task'):
            self.cleanup_task.stop()
        
        # Fermer ressources
        if self.resources:
            self.resources.close()
        
        # Notifier clients de l'arrêt
        for client in self.clients:
            client.transport.loseConnection()
    
    def buildProtocol(self, addr):
        # [OK] Toujours vérifier limites
        if len(self.clients) >= MAX_CLIENTS:
            log.msg(f"Max clients reached, rejecting {addr}")
            return None
        
        # [OK] Toujours lier factory au protocol
        p = self.protocol()
        p.factory = self
        
        return p
    
    def cleanup(self):
        """[OK] Tâche périodique de maintenance"""
        # Timeout clients inactifs
        now = time.time()
        for client in self.clients[:]:  # Copie pour modification pendant itération
            if hasattr(client, 'last_activity'):
                if now - client.last_activity > 300:  # 5 min
                    log.msg(f"Timeout client {client.transport.getPeer()}")
                    client.transport.loseConnection()


[OK] SERVEURS TCP

# === CONCEPT DU SERVEUR TCP ===

# Serveur TCP = écoute sur un port et accepte des connexions
# 
# Architecture:
# 1. Factory crée des Protocols pour chaque connexion
# 2. Reactor gère l'écoute sur le port
# 3. Chaque client connecté a son propre Protocol
#
# Flux:
# Client se connecte -> Factory.buildProtocol() -> Protocol.connectionMade()
# Client envoie données -> Protocol.dataReceived()
# Client se déconnecte -> Protocol.connectionLost()

from twisted.internet import reactor, protocol

# === SERVEUR ECHO SIMPLE ===

class EchoProtocol(protocol.Protocol):
    """Protocol basique: renvoie tout ce qu'il reçoit"""
    
    def dataReceived(self, data):
        # Echo: renvoyer les données telles quelles
        self.transport.write(data)

class EchoFactory(protocol.Factory):
    """Factory minimale pour serveur echo"""
    protocol = EchoProtocol

# Démarrer serveur sur port 8000
factory = EchoFactory()
reactor.listenTCP(8000, factory)
print("[AUDIO] Serveur echo écoute sur port 8000")
reactor.run()

# Tester avec:
# telnet localhost 8000
# ou: nc localhost 8000
# ou: curl telnet://localhost:8000

# === SERVEUR AVEC GESTION CLIENTS ===

from twisted.protocols.basic import LineReceiver

class ChatProtocol(LineReceiver):
    """Protocol pour chat multi-utilisateur"""
    
    def connectionMade(self):
        """Nouveau client connecté"""
        # S'enregistrer dans la factory
        self.factory.clients.add(self)
        
        # Obtenir infos du client
        peer = self.transport.getPeer()
        self.client_addr = f"{peer.host}:{peer.port}"
        
        # Notifier les autres
        self.factory.broadcast(
            f"[Système] {self.client_addr} a rejoint ({len(self.factory.clients)} en ligne)".encode()
        )
        
        # Message de bienvenue
        self.sendLine(b"=== Bienvenue sur le chat! ===")
        self.sendLine(f"Vous êtes {self.client_addr}".encode())
        self.sendLine(b"Tapez /help pour l'aide")
    
    def connectionLost(self, reason):
        """Client déconnecté"""
        # Se désenregistrer
        self.factory.clients.discard(self)
        
        # Notifier les autres
        self.factory.broadcast(
            f"[Système] {self.client_addr} est parti ({len(self.factory.clients)} en ligne)".encode()
        )
    
    def lineReceived(self, line):
        """Message reçu du client"""
        # Commandes
        if line.startswith(b'/'):
            self.handle_command(line[1:])
        else:
            # Message normal - broadcaster
            msg = f"[{self.client_addr}] {line.decode()}".encode()
            self.factory.broadcast(msg, exclude=self)
    
    def handle_command(self, command):
        """Traiter commandes spéciales"""
        cmd = command.decode().lower().split()
        
        if not cmd:
            return
        
        if cmd[0] == 'help':
            self.sendLine(b"Commandes disponibles:")
            self.sendLine(b"  /help - Afficher cette aide")
            self.sendLine(b"  /list - Liste des connectes")
            self.sendLine(b"  /stats - Statistiques serveur")
            self.sendLine(b"  /quit - Se deconnecter")
        
        elif cmd[0] == 'list':
            self.sendLine(f"Clients connectes ({len(self.factory.clients)}):".encode())
            for client in self.factory.clients:
                self.sendLine(f"  - {client.client_addr}".encode())
        
        elif cmd[0] == 'stats':
            stats = self.factory.get_stats()
            self.sendLine(b"=== Statistiques Serveur ===")
            self.sendLine(f"Clients actuels: {stats['current_clients']}".encode())
            self.sendLine(f"Total connexions: {stats['total_connections']}".encode())
            self.sendLine(f"Messages envoyes: {stats['messages_sent']}".encode())
            self.sendLine(f"Uptime: {stats['uptime']:.0f}s".encode())
        
        elif cmd[0] == 'quit':
            self.sendLine(b"Au revoir!")
            self.transport.loseConnection()
        
        else:
            self.sendLine(f"Commande inconnue: {cmd[0]}".encode())

class ChatFactory(protocol.Factory):
    """Factory pour serveur chat avec état partagé"""
    
    protocol = ChatProtocol
    
    def __init__(self):
        # État partagé entre TOUS les clients
        self.clients = set()  # Set de tous les protocols connectés
        self.start_time = time.time()
        self.total_connections = 0
        self.messages_sent = 0
    
    def buildProtocol(self, addr):
        """Créer protocol pour nouveau client"""
        self.total_connections += 1
        
        p = self.protocol()
        p.factory = self
        
        print(f"[OK] Nouvelle connexion #{self.total_connections} de {addr.host}:{addr.port}")
        
        return p
    
    def broadcast(self, message, exclude=None):
        """
        Envoyer message à tous les clients
        
        Arguments:
        - message: bytes à envoyer
        - exclude: Protocol à ne pas inclure (optionnel)
        """
        self.messages_sent += 1
        
        for client in self.clients:
            if client != exclude:
                client.sendLine(message)
    
    def get_stats(self):
        """Obtenir statistiques serveur"""
        return {
            'current_clients': len(self.clients),
            'total_connections': self.total_connections,
            'messages_sent': self.messages_sent,
            'uptime': time.time() - self.start_time
        }

# Démarrer serveur chat
if __name__ == '__main__':
    import time
    factory = ChatFactory()
    reactor.listenTCP(8000, factory)
    print("[AUDIO] Serveur chat écoute sur port 8000")
    reactor.run()

# === OPTIONS D'ÉCOUTE TCP ===

# 1. Interface spécifique
# Écouter seulement sur localhost (127.0.0.1)
reactor.listenTCP(8000, factory, interface='127.0.0.1')

# Écouter sur toutes les interfaces (défaut)
reactor.listenTCP(8000, factory, interface='0.0.0.0')

# Écouter sur IPv6
reactor.listenTCP(8000, factory, interface='::')

# Écouter sur interface spécifique (par IP)
reactor.listenTCP(8000, factory, interface='192.168.1.100')

# 2. Backlog (taille de la queue de connexions en attente)
reactor.listenTCP(8000, factory, backlog=50)
# Par défaut: 50 (système peut ajuster)
# Augmenter pour serveurs haute charge
reactor.listenTCP(8000, factory, backlog=128)

# 3. Obtenir informations sur le port
port_obj = reactor.listenTCP(8000, factory)

# Port effectif (utile si port=0 pour port aléatoire)
actual_port = port_obj.getHost().port
print(f"Écoute sur port: {actual_port}")

# Adresse complète
host_info = port_obj.getHost()
print(f"Écoute sur {host_info.host}:{host_info.port}")

# 4. Arrêter le serveur proprement
port_obj = reactor.listenTCP(8000, factory)

# Plus tard...
d = port_obj.stopListening()  # Retourne un Deferred
d.addCallback(lambda _: print("Serveur arrêté"))

# stopListening():
# - Arrête d'accepter nouvelles connexions
# - Les connexions existantes continuent
# - Retourne Deferred qui fire quand tout est fermé

# 5. Port aléatoire (système choisit)
port_obj = reactor.listenTCP(0, factory)  # Port 0 = automatique
actual_port = port_obj.getHost().port
print(f"Serveur sur port aléatoire: {actual_port}")

# === SERVEUR SSL/TLS ===

from twisted.internet import ssl, reactor

# MÉTHODE 1: Contexte SSL simple
context_factory = ssl.DefaultOpenSSLContextFactory(
    'server-key.pem',    # Fichier clé privée
    'server-cert.pem'    # Fichier certificat
)

reactor.listenSSL(8443, factory, context_factory)

# MÉTHODE 2: Contexte SSL avancé (recommandé)
from twisted.internet.ssl import CertificateOptions
from OpenSSL import SSL

# Charger certificat et clé
with open('server-cert.pem') as f:
    cert_data = f.read()

with open('server-key.pem') as f:
    key_data = f.read()

from twisted.internet.ssl import PrivateCertificate

# Créer certificat
cert = PrivateCertificate.loadPEM(cert_data + key_data)

# Options SSL modernes et sécurisées
context_factory = cert.options()

# Ou avec configuration personnalisée:
context_factory = CertificateOptions(
    privateKey=cert.privateKey.original,
    certificate=cert.original,
    
    # Protocoles acceptés (désactiver vieux protocoles)
    method=SSL.TLSv1_2_METHOD,  # Ou SSL.SSLv23_METHOD
    
    # Ciphers sécurisés uniquement
    # (liste personnalisée ou laisser défaut)
)

# Lancer serveur SSL
reactor.listenSSL(8443, factory, context_factory)
print("[VERROUILLE] Serveur SSL/TLS écoute sur port 8443")

# MÉTHODE 3: Let's Encrypt / ACME
# Pour certificats gratuits automatiques
# Installer: pip install txacme

from txacme.client import Client
from txacme.challenges import HTTP01Responder
from txacme.service import AcmeIssuingService
from txacme.store import DirectoryStore

# Configuration ACME pour Let's Encrypt
store = DirectoryStore('certificates/')
client = Client.from_url(
    reactor,
    'https://acme-v02.api.letsencrypt.org/directory',
    key=store.as_key_store()
)

# Service qui gère renouvellement automatique
service = AcmeIssuingService(
    cert_store=store,
    client=client,
    clock=reactor,
    responders=[HTTP01Responder()]
)

# === SERVEUR MULTI-PORT ===

# Écouter sur plusieurs ports simultanément
factory1 = Factory1()
factory2 = Factory2()
factory3 = Factory3()

port1 = reactor.listenTCP(8000, factory1)
port2 = reactor.listenTCP(8001, factory2)
port3 = reactor.listenTCP(8002, factory3)

print("[AUDIO] Serveurs actifs sur ports 8000, 8001, 8002")
reactor.run()

# Cas d'usage:
# - Port 8000: API publique
# - Port 8001: Admin panel
# - Port 8002: Monitoring/metrics

# === SERVEUR AVEC LIMITE DE CONNEXIONS ===

class LimitedFactory(protocol.Factory):
    """Factory avec limite de connexions simultanées"""
    
    protocol = MyProtocol
    max_connections = 100  # Limite de connexions
    
    def __init__(self):
        self.active_connections = 0
        self.rejected_count = 0
    
    def buildProtocol(self, addr):
        # Vérifier limite
        if self.active_connections >= self.max_connections:
            print(f"[ATTENTION] Limite atteinte, connexion refusée de {addr.host}")
            self.rejected_count += 1
            return None  # Refuse la connexion
        
        # Accepter connexion
        self.active_connections += 1
        
        p = self.protocol()
        p.factory = self
        
        # Wrapper pour décrémenter compteur à la déconnexion
        original_lost = p.connectionLost
        
        def tracked_lost(reason):
            self.active_connections -= 1
            original_lost(reason)
        
        p.connectionLost = tracked_lost
        
        return p

# === SERVEUR AVEC WHITELIST IP ===

class WhitelistFactory(protocol.Factory):
    """Factory acceptant seulement IPs autorisées"""
    
    protocol = MyProtocol
    
    def __init__(self, allowed_ips):
        """
        Arguments:
        - allowed_ips: liste d'IPs autorisées (strings)
        """
        self.allowed_ips = set(allowed_ips)
    
    def buildProtocol(self, addr):
        # Vérifier IP
        if addr.host not in self.allowed_ips:
            print(f"[INTERDIT] Connexion refusée de {addr.host} (non autorisée)")
            return None
        
        print(f"[OK] Connexion autorisée de {addr.host}")
        
        p = self.protocol()
        p.factory = self
        return p

# Utilisation:
factory = WhitelistFactory(['127.0.0.1', '192.168.1.100', '10.0.0.5'])
reactor.listenTCP(8000, factory)

# === SERVEUR AVEC TIMEOUT INACTIVITÉ ===

from twisted.internet import task

class TimeoutProtocol(protocol.Protocol):
    """Protocol avec timeout d'inactivité"""
    
    timeout = 300  # 5 minutes
    
    def connectionMade(self):
        self.reset_timeout()
    
    def dataReceived(self, data):
        # Réinitialiser timeout à chaque activité
        self.reset_timeout()
        
        # Traiter données...
        self.transport.write(b"Echo: " + data)
    
    def reset_timeout(self):
        """Réinitialiser timer de timeout"""
        # Annuler timeout précédent
        if hasattr(self, 'timeout_call'):
            if self.timeout_call.active():
                self.timeout_call.cancel()
        
        # Nouveau timeout
        self.timeout_call = reactor.callLater(
            self.timeout,
            self.on_timeout
        )
    
    def on_timeout(self):
        """Appelé après période d'inactivité"""
        print(f"[TEMPS] Timeout pour {self.transport.getPeer()}")
        self.transport.write(b"Timeout inactivite\n")
        self.transport.loseConnection()
    
    def connectionLost(self, reason):
        # Annuler timeout en attente
        if hasattr(self, 'timeout_call'):
            if self.timeout_call.active():
                self.timeout_call.cancel()

# === SERVEUR AVEC RATE LIMITING ===

import time
from collections import defaultdict

class RateLimitedFactory(protocol.Factory):
    """Factory avec rate limiting par IP"""
    
    protocol = MyProtocol
    
    # Configuration rate limiting
    max_connections_per_ip = 5        # Max connexions simultanées par IP
    max_connections_per_minute = 10   # Max nouvelles connexions/minute par IP
    
    def __init__(self):
        # Tracking par IP
        self.connections_by_ip = defaultdict(int)  # IP -> count
        self.connection_times = defaultdict(list)   # IP -> [timestamps]
    
    def buildProtocol(self, addr):
        ip = addr.host
        
        # Vérifier limite de connexions simultanées
        if self.connections_by_ip[ip] >= self.max_connections_per_ip:
            print(f"[ATTENTION] {ip}: limite connexions simultanées atteinte")
            return None
        
        # Vérifier rate limit (connexions/minute)
        now = time.time()
        
        # Nettoyer vieux timestamps (> 1 minute)
        self.connection_times[ip] = [
            t for t in self.connection_times[ip]
            if now - t < 60
        ]
        
        # Vérifier nombre de connexions dans la dernière minute
        if len(self.connection_times[ip]) >= self.max_connections_per_minute:
            print(f"[ATTENTION] {ip}: rate limit dépassé")
            return None
        
        # Accepter connexion
        self.connections_by_ip[ip] += 1
        self.connection_times[ip].append(now)
        
        p = self.protocol()
        p.factory = self
        
        # Tracker déconnexion
        original_lost = p.connectionLost
        
        def tracked_lost(reason):
            self.connections_by_ip[ip] -= 1
            if self.connections_by_ip[ip] == 0:
                del self.connections_by_ip[ip]
            original_lost(reason)
        
        p.connectionLost = tracked_lost
        
        return p

# === SERVEUR AVEC LOGGING ===

from twisted.python import log
import sys

class LoggedFactory(protocol.Factory):
    """Factory avec logging détaillé"""
    
    protocol = MyProtocol
    
    def __init__(self):
        self.connection_count = 0
    
    def startFactory(self):
        """Factory démarre"""
        log.msg("[USINE] Factory démarrée", system="Server")
    
    def stopFactory(self):
        """Factory s'arrête"""
        log.msg("[USINE] Factory arrêtée", system="Server")
    
    def buildProtocol(self, addr):
        self.connection_count += 1
        
        log.msg(
            f"Nouvelle connexion #{self.connection_count}",
            system="Server",
            ip=addr.host,
            port=addr.port
        )
        
        p = self.protocol()
        p.factory = self
        p.connection_id = self.connection_count
        
        # Wrapper pour logger déconnexion
        original_lost = p.connectionLost
        
        def logged_lost(reason):
            log.msg(
                f"Déconnexion #{p.connection_id}",
                system="Server",
                reason=str(reason.value)
            )
            original_lost(reason)
        
        p.connectionLost = logged_lost
        
        return p

# Démarrer logging
log.startLogging(sys.stdout)

factory = LoggedFactory()
reactor.listenTCP(8000, factory)
reactor.run()

# === SERVEUR AVEC STATISTIQUES ===

class StatsFactory(protocol.Factory):
    """Factory avec collecte de statistiques"""
    
    protocol = MyProtocol
    
    def __init__(self):
        self.stats = {
            'connections_total': 0,
            'connections_current': 0,
            'connections_rejected': 0,
            'bytes_sent': 0,
            'bytes_received': 0,
            'start_time': time.time()
        }
        
        # Démarrer reporting périodique
        from twisted.internet import task
        self.stats_loop = task.LoopingCall(self.report_stats)
        self.stats_loop.start(60)  # Toutes les 60 secondes
    
    def buildProtocol(self, addr):
        self.stats['connections_total'] += 1
        self.stats['connections_current'] += 1
        
        p = self.protocol()
        p.factory = self
        
        # Wrapper pour tracking
        original_write = p.transport.write
        original_received = p.dataReceived
        original_lost = p.connectionLost
        
        def tracked_write(data):
            self.stats['bytes_sent'] += len(data)
            return original_write(data)
        
        def tracked_received(data):
            self.stats['bytes_received'] += len(data)
            return original_received(data)
        
        def tracked_lost(reason):
            self.stats['connections_current'] -= 1
            return original_lost(reason)
        
        # Note: ce wrapping est simplifié
        # En production, utiliser un Protocol custom
        
        return p
    
    def report_stats(self):
        """Reporter statistiques"""
        uptime = time.time() - self.stats['start_time']
        
        print("\n" + "="*50)
        print("[GRAPHIQUE] STATISTIQUES SERVEUR")
        print("="*50)
        print(f"Uptime: {uptime:.0f}s ({uptime/3600:.1f}h)")
        print(f"Connexions actuelles: {self.stats['connections_current']}")
        print(f"Connexions totales: {self.stats['connections_total']}")
        print(f"Connexions refusées: {self.stats['connections_rejected']}")
        print(f"Bytes envoyés: {self.stats['bytes_sent']:,}")
        print(f"Bytes reçus: {self.stats['bytes_received']:,}")
        print("="*50 + "\n")
    
    def get_stats(self):
        """Obtenir statistiques courantes"""
        return self.stats.copy()

# === EXEMPLE COMPLET: SERVEUR ROBUSTE ===

from twisted.internet import reactor, protocol
from twisted.protocols.basic import LineReceiver
from twisted.python import log
import sys
import time

class RobustProtocol(LineReceiver):
    """Protocol avec toutes les features"""
    
    delimiter = b'\n'
    MAX_LENGTH = 16384
    timeout = 300  # 5 minutes
    
    def connectionMade(self):
        # Enregistrement
        self.factory.register_client(self)
        
        # Info client
        peer = self.transport.getPeer()
        self.client_ip = peer.host
        self.client_port = peer.port
        
        # État
        self.authenticated = False
        self.username = None
        
        # Timeout
        self.reset_timeout()
        
        # Bienvenue
        self.sendLine(b"Bienvenue! Login:")
        
        log.msg(f"Client connecté: {self.client_ip}:{self.client_port}")
    
    def lineReceived(self, line):
        self.reset_timeout()
        
        try:
            if not self.authenticated:
                self.handle_login(line)
            else:
                self.handle_command(line)
        except Exception as e:
            log.err(e)
            self.sendLine(b"ERROR: Internal error")
    
    def handle_login(self, line):
        # Login simple (à améliorer en production)
        self.username = line.decode().strip()
        self.authenticated = True
        self.sendLine(f"Bienvenue {self.username}!".encode())
        log.msg(f"User {self.username} logged in from {self.client_ip}")
    
    def handle_command(self, line):
        # Traiter commandes
        cmd = line.decode().strip().lower()
        
        if cmd == 'quit':
            self.sendLine(b"Bye!")
            self.transport.loseConnection()
        elif cmd == 'stats':
            stats = self.factory.get_stats()
            for key, value in stats.items():
                self.sendLine(f"{key}: {value}".encode())
        else:
            self.sendLine(b"Unknown command")
    
    def reset_timeout(self):
        if hasattr(self, 'timeout_call') and self.timeout_call.active():
            self.timeout_call.cancel()
        self.timeout_call = reactor.callLater(self.timeout, self.on_timeout)
    
    def on_timeout(self):
        log.msg(f"Timeout for {self.username or self.client_ip}")
        self.sendLine(b"Timeout")
        self.transport.loseConnection()
    
    def connectionLost(self, reason):
        if hasattr(self, 'timeout_call') and self.timeout_call.active():
            self.timeout_call.cancel()
        
        self.factory.unregister_client(self)
        log.msg(f"Client déconnecté: {self.username or self.client_ip}")

class RobustFactory(protocol.Factory):
    """Factory robuste pour production"""
    
    protocol = RobustProtocol
    
    def __init__(self):
        self.clients = set()
        self.stats = {
            'connections_total': 0,
            'connections_current': 0,
            'start_time': time.time()
        }
    
    def buildProtocol(self, addr):
        self.stats['connections_total'] += 1
        self.stats['connections_current'] += 1
        
        p = self.protocol()
        p.factory = self
        return p
    
    def register_client(self, client):
        self.clients.add(client)
    
    def unregister_client(self, client):
        self.clients.discard(client)
        self.stats['connections_current'] -= 1
    
    def get_stats(self):
        return {
            **self.stats,
            'uptime': time.time() - self.stats['start_time']
        }

# Lancer serveur
if __name__ == '__main__':
    log.startLogging(sys.stdout)
    factory = RobustFactory()
    reactor.listenTCP(8000, factory)
    log.msg("[AUDIO] Serveur démarré sur port 8000")
    reactor.run()


[OK] CLIENTS TCP

# Créer client TCP qui se connecte à un serveur

from twisted.internet import reactor, protocol

# === CLIENT SIMPLE ===

class EchoClientProtocol(protocol.Protocol):
    
    def connectionMade(self):
        print("Connecté au serveur")
        self.transport.write(b"Hello, server!")
    
    def dataReceived(self, data):
        print(f"Reçu: {data}")
        self.transport.loseConnection()
    
    def connectionLost(self, reason):
        print("Connexion fermée")
        reactor.stop()

class EchoClientFactory(protocol.ClientFactory):
    protocol = EchoClientProtocol
    
    def clientConnectionFailed(self, connector, reason):
        print(f"Échec: {reason}")
        reactor.stop()
    
    def clientConnectionLost(self, connector, reason):
        print(f"Perdu: {reason}")

# Se connecter
factory = EchoClientFactory()
reactor.connectTCP('localhost', 8000, factory)
reactor.run()

# === CLIENT AVEC RECONNEXION ===

class RobustClientFactory(protocol.ReconnectingClientFactory):
    
    protocol = MyClientProtocol
    maxDelay = 60
    
    def buildProtocol(self, addr):
        print("Connecté!")
        self.resetDelay()
        return protocol.ReconnectingClientFactory.buildProtocol(self, addr)

# === CLIENT SSL/TLS ===

from twisted.internet import ssl

# Connecter via SSL
context_factory = ssl.ClientContextFactory()
reactor.connectSSL('example.com', 443, factory, context_factory)

# Vérification certificat
from twisted.internet.ssl import CertificateOptions

options = CertificateOptions(verify=True)
reactor.connectSSL('example.com', 443, factory, options)

# === OPTIONS DE CONNEXION ===

# Timeout
reactor.connectTCP('example.com', 8000, factory, timeout=30)

# Interface locale spécifique
reactor.connectTCP('example.com', 8000, factory, bindAddress=('192.168.1.100', 0))

# === CLIENT ENDPOINTS (API moderne) ===

from twisted.internet import endpoints

# TCP endpoint
endpoint = endpoints.TCP4ClientEndpoint(reactor, 'localhost', 8000)
d = endpoint.connect(factory)
d.addCallback(lambda p: print("Connecté!"))
d.addErrback(lambda f: print(f"Erreur: {f}"))

# SSL endpoint
endpoint = endpoints.SSL4ClientEndpoint(
    reactor, 'example.com', 443, ssl.ClientContextFactory()
)

# Avec timeout
endpoint = endpoints.TCP4ClientEndpoint(reactor, 'localhost', 8000, timeout=30)


[OK] UDP (DATAGRAM PROTOCOL)

# UDP = protocole sans connexion, pas de garantie de livraison

from twisted.internet.protocol import DatagramProtocol
from twisted.internet import reactor

# === SERVEUR UDP ===

class UDPServer(DatagramProtocol):
    
    def startProtocol(self):
        print("UDP serveur démarré")
    
    def datagramReceived(self, datagram, address):
        print(f"Reçu de {address}: {datagram}")
        # Répondre
        self.transport.write(b"Response: " + datagram, address)
    
    def stopProtocol(self):
        print("UDP serveur arrêté")

# Lancer serveur UDP
reactor.listenUDP(9999, UDPServer())
reactor.run()

# === CLIENT UDP ===

class UDPClient(DatagramProtocol):
    
    def startProtocol(self):
        self.transport.connect('localhost', 9999)
        self.transport.write(b"Hello via UDP!")
    
    def datagramReceived(self, datagram, address):
        print(f"Réponse: {datagram}")
        reactor.stop()

# Lancer client UDP
reactor.listenUDP(0, UDPClient())  # Port 0 = port aléatoire
reactor.run()

# === BROADCAST UDP ===

class BroadcastProtocol(DatagramProtocol):
    
    def startProtocol(self):
        self.transport.setBroadcastAllowed(True)
        self.transport.write(b"Broadcast message", ('<broadcast>', 9999))

# === MULTICAST UDP ===

class MulticastProtocol(DatagramProtocol):
    
    def startProtocol(self):
        self.transport.joinGroup('224.0.0.1')  # Groupe multicast
    
    def datagramReceived(self, datagram, address):
        print(f"Multicast reçu: {datagram}")

reactor.listenMulticast(9999, MulticastProtocol(), listenMultiple=True)


[OK] HTTP CLIENT

# Twisted inclut un client HTTP complet

from twisted.web.client import Agent, readBody
from twisted.internet import reactor

# === REQUÊTE HTTP SIMPLE ===

agent = Agent(reactor)

def print_response(body):
    print(f"Body: {body}")
    reactor.stop()

def handle_response(response):
    print(f"Status: {response.code}")
    d = readBody(response)
    d.addCallback(print_response)
    return d

d = agent.request(b'GET', b'http://example.com')
d.addCallback(handle_response)
reactor.run()

# === AVEC INLINECALLBACKS ===

from twisted.internet.defer import inlineCallbacks
from twisted.web.client import Agent, readBody

@inlineCallbacks
def fetch_page(url):
    agent = Agent(reactor)
    response = yield agent.request(b'GET', url.encode())
    body = yield readBody(response)
    print(f"Body: {body[:100]}")
    reactor.stop()

fetch_page('http://example.com')
reactor.run()

# === POST REQUEST ===

from twisted.web.client import Agent, FileBodyProducer
from io import BytesIO

agent = Agent(reactor)

body = FileBodyProducer(BytesIO(b'{"key": "value"}'))

d = agent.request(
    b'POST',
    b'http://example.com/api',
    headers={'Content-Type': ['application/json']},
    bodyProducer=body
)

# === HEADERS PERSONNALISÉS ===

from twisted.web.http_headers import Headers

headers = Headers({
    b'User-Agent': [b'MyApp/1.0'],
    b'Authorization': [b'Bearer token123'],
    b'Content-Type': [b'application/json']
})

d = agent.request(b'GET', b'http://api.example.com', headers=headers)

# === TÉLÉCHARGER FICHIER ===

from twisted.web.client import downloadPage

d = downloadPage(b'http://example.com/file.zip', b'output.zip')
d.addCallback(lambda _: print("Téléchargement terminé"))
d.addErrback(lambda f: print(f"Erreur: {f}"))

# === TREQ (API REQUESTS-LIKE) ===

# Installer: pip install treq
import treq

@inlineCallbacks
def fetch_with_treq():
    response = yield treq.get('http://example.com')
    body = yield response.text()
    print(body)

# POST avec JSON
@inlineCallbacks
def post_json():
    response = yield treq.post(
        'http://api.example.com',
        json={'key': 'value'},
        headers={'Authorization': 'Bearer token'}
    )
    data = yield response.json()
    print(data)


[OK] HTTP SERVER

# Twisted peut servir du contenu HTTP

from twisted.web import server, resource
from twisted.internet import reactor

# === RESSOURCE SIMPLE ===

class HelloResource(resource.Resource):
    isLeaf = True  # Pas d'enfants
    
    def render_GET(self, request):
        return b"Hello, World!"

root = HelloResource()
factory = server.Site(root)
reactor.listenTCP(8080, factory)
reactor.run()

# === AVEC MÉTHODES HTTP ===

class APIResource(resource.Resource):
    isLeaf = True
    
    def render_GET(self, request):
        return b'{"method": "GET"}'
    
    def render_POST(self, request):
        data = request.content.read()
        return b'{"received": "' + data + b'"}'
    
    def render_PUT(self, request):
        return b'{"method": "PUT"}'
    
    def render_DELETE(self, request):
        return b'{"method": "DELETE"}'

# === RESSOURCES HIÉRARCHIQUES ===

class RootResource(resource.Resource):
    
    def getChild(self, name, request):
        if name == b'api':
            return APIResource()
        elif name == b'admin':
            return AdminResource()
        return resource.NoResource()

# Ou avec putChild
root = resource.Resource()
root.putChild(b'api', APIResource())
root.putChild(b'admin', AdminResource())

# === PARAMÈTRES REQUEST ===

class ParamsResource(resource.Resource):
    isLeaf = True
    
    def render_GET(self, request):
        # Query parameters
        name = request.args.get(b'name', [b'Guest'])[0]
        
        # Path
        path = request.path
        
        # Headers
        user_agent = request.getHeader(b'user-agent')
        
        # Client address
        client_ip = request.getClientIP()
        
        return b"Hello, " + name

# === RÉPONSE PERSONNALISÉE ===

class CustomResponse(resource.Resource):
    isLeaf = True
    
    def render_GET(self, request):
        # Status code
        request.setResponseCode(201)
        
        # Headers
        request.setHeader(b'Content-Type', b'application/json')
        request.setHeader(b'X-Custom-Header', b'value')
        
        # Cookies
        request.addCookie(b'session', b'abc123', path=b'/')
        
        return b'{"status": "created"}'

# === RÉPONSE ASYNCHRONE (DEFERRED) ===

from twisted.web import resource, server
from twisted.internet import defer

class AsyncResource(resource.Resource):
    isLeaf = True
    
    def render_GET(self, request):
        d = self.async_operation()
        d.addCallback(self.handle_result, request)
        return server.NOT_DONE_YET
    
    @defer.inlineCallbacks
    def async_operation(self):
        # Opération asynchrone
        result = yield some_deferred_operation()
        defer.returnValue(result)
    
    def handle_result(self, result, request):
        request.write(b"Result: " + str(result).encode())
        request.finish()

# === SERVIR FICHIERS STATIQUES ===

from twisted.web.static import File

root = resource.Resource()
root.putChild(b'static', File('./static'))

# === TEMPLATE HTML ===

from twisted.web.template import Element, renderer, XMLFile
from twisted.python.filepath import FilePath

class MyElement(Element):
    
    loader = XMLFile(FilePath('template.html'))
    
    @renderer
    def title(self, request, tag):
        return tag("Mon Site")
    
    @renderer
    def content(self, request, tag):
        return tag("Contenu dynamique")

# === WSGI (COMPATIBILITÉ FLASK, DJANGO, etc.) ===

from twisted.web.wsgi import WSGIResource
from twisted.internet import reactor

# Flask app
from flask import Flask
app = Flask(__name__)

@app.route('/')
def hello():
    return "Hello from Flask!"

# Twisted WSGI Resource
resource = WSGIResource(reactor, reactor.getThreadPool(), app)
factory = server.Site(resource)
reactor.listenTCP(8080, factory)
reactor.run()


[OK] WEBSOCKETS

# Support WebSocket dans Twisted

# Installer: pip install autobahn
from autobahn.twisted.websocket import WebSocketServerProtocol, WebSocketServerFactory

# === SERVEUR WEBSOCKET ===

class MyServerProtocol(WebSocketServerProtocol):
    
    def onConnect(self, request):
        print(f"Client connecté depuis {request.peer}")
    
    def onOpen(self):
        print("Connexion WebSocket ouverte")
    
    def onMessage(self, payload, isBinary):
        if isBinary:
            print(f"Message binaire reçu: {len(payload)} bytes")
        else:
            print(f"Message texte reçu: {payload.decode('utf8')}")
        
        # Répondre
        self.sendMessage(payload, isBinary)
    
    def onClose(self, wasClean, code, reason):
        print(f"Connexion fermée: {reason}")

# Factory
factory = WebSocketServerFactory("ws://localhost:9000")
factory.protocol = MyServerProtocol

# Lancer serveur
reactor.listenTCP(9000, factory)
reactor.run()

# === CLIENT WEBSOCKET ===

from autobahn.twisted.websocket import WebSocketClientProtocol, WebSocketClientFactory

class MyClientProtocol(WebSocketClientProtocol):
    
    def onConnect(self, response):
        print("Connecté au serveur")
    
    def onOpen(self):
        print("Connexion ouverte")
        self.sendMessage(b"Hello, server!", isBinary=False)
    
    def onMessage(self, payload, isBinary):
        print(f"Reçu: {payload.decode('utf8')}")
        reactor.stop()
    
    def onClose(self, wasClean, code, reason):
        print("Connexion fermée")

factory = WebSocketClientFactory("ws://localhost:9000")
factory.protocol = MyClientProtocol

reactor.connectTCP("localhost", 9000, factory)
reactor.run()

# === BROADCAST WEBSOCKET ===

class BroadcastProtocol(WebSocketServerProtocol):
    
    def onOpen(self):
        self.factory.register(self)
    
    def onMessage(self, payload, isBinary):
        self.factory.broadcast(payload, exclude=self)
    
    def onClose(self, wasClean, code, reason):
        self.factory.unregister(self)

class BroadcastFactory(WebSocketServerFactory):
    
    def __init__(self, url):
        super().__init__(url)
        self.clients = []
    
    def register(self, client):
        self.clients.append(client)
    
    def unregister(self, client):
        self.clients.remove(client)
    
    def broadcast(self, message, exclude=None):
        for client in self.clients:
            if client != exclude:
                client.sendMessage(message)


[OK] EMAIL (SMTP, POP3, IMAP)

# Twisted supporte les protocoles email

# === ENVOYER EMAIL (SMTP) ===

from twisted.mail import smtp
from twisted.internet import reactor, defer

@defer.inlineCallbacks
def send_email():
    msg = """From: sender@example.com
To: recipient@example.com
Subject: Test Email

Ceci est un email de test.
"""
    
    result = yield smtp.sendmail(
        'smtp.example.com',
        'sender@example.com',
        ['recipient@example.com'],
        msg.encode(),
        port=587,
        username='sender@example.com',
        password='password',
        requireAuthentication=True,
        requireTransportSecurity=True
    )
    
    print(f"Email envoyé: {result}")
    reactor.stop()

send_email()
reactor.run()

# === SERVEUR SMTP SIMPLE ===

from twisted.mail import smtp
from twisted.internet import protocol

class MessageDelivery:
    
    def receivedHeader(self, helo, origin, recipients):
        return "Received: from %s" % helo[0]
    
    def validateFrom(self, helo, origin):
        return origin
    
    def validateTo(self, user):
        return lambda: MessageStorage()

class MessageStorage:
    
    def __init__(self):
        self.lines = []
    
    def lineReceived(self, line):
        self.lines.append(line)
    
    def eomReceived(self):
        print(f"Email reçu: {b''.join(self.lines)}")
        return defer.succeed(None)
    
    def connectionLost(self):
        pass

class SMTPFactory(smtp.SMTPFactory):
    protocol = smtp.SMTP
    
    def __init__(self):
        smtp.SMTPFactory.__init__(self)
        self.delivery = MessageDelivery()
    
    def buildProtocol(self, addr):
        p = smtp.SMTP(self.delivery)
        p.factory = self
        return p

reactor.listenTCP(2525, SMTPFactory())
reactor.run()

# === CLIENT POP3 ===

from twisted.mail import pop3client
from twisted.internet import defer

@defer.inlineCallbacks
def fetch_emails():
    client = yield pop3client.POP3Client(
        'pop.example.com',
        995,
        'username',
        'password',
        useSSL=True
    )
    
    # Liste des messages
    count = yield client.listSize()
    print(f"{count} messages")
    
    # Récupérer message
    message = yield client.retrieve(1)
    print(message)
    
    # Supprimer message
    yield client.delete(1)
    
    yield client.quit()
    reactor.stop()


[OK] SSH

# Client et serveur SSH avec Twisted

# === CLIENT SSH ===

from twisted.conch.ssh import transport, userauth, connection, channel, common
from twisted.internet import defer, protocol, reactor

class SSHClientTransport(transport.SSHClientTransport):
    
    def verifyHostKey(self, pubKey, fingerprint):
        return defer.succeed(True)
    
    def connectionSecure(self):
        self.requestService(SSHUserAuth('username', SSHConnection()))

class SSHUserAuth(userauth.SSHUserAuthClient):
    
    def getPassword(self):
        return defer.succeed(b'password')
    
    def getPublicKey(self):
        # Authentification par clé publique
        return None

class SSHConnection(connection.SSHConnection):
    
    def serviceStarted(self):
        self.openChannel(SSHChannel())

class SSHChannel(channel.SSHChannel):
    
    name = b'session'
    
    def channelOpen(self, data):
        self.conn.sendRequest(self, 'exec', common.NS(b'ls -la'))
    
    def dataReceived(self, data):
        print(f"Sortie: {data.decode()}")
    
    def closed(self):
        reactor.stop()

# Connexion
factory = protocol.Factory()
factory.protocol = SSHClientTransport
reactor.connectTCP('example.com', 22, factory)
reactor.run()

# === SERVEUR SSH (SFTP) ===

from twisted.conch.ssh import factory, keys, session
from twisted.cred import portal, checkers

class SSHServerFactory(factory.SSHFactory):
    
    publicKeys = {
        b'ssh-rsa': keys.Key.fromFile('server-key.pub')
    }
    privateKeys = {
        b'ssh-rsa': keys.Key.fromFile('server-key')
    }

# Configuration avec authentification
portal = portal.Portal(SSHRealm())
portal.registerChecker(checkers.InMemoryUsernamePasswordDatabaseDontUse(
    username=b'password'
))

factory = SSHServerFactory()
factory.portal = portal
reactor.listenTCP(2222, factory)
reactor.run()


[OK] DNS

# Client et serveur DNS

from twisted.names import dns, server, client, cache
from twisted.internet import reactor, defer

# === CLIENT DNS ===

@defer.inlineCallbacks
def lookup_dns():
    resolver = client.Resolver('/etc/resolv.conf')
    
    # Lookup A record
    result = yield resolver.lookupAddress('example.com')
    for answer in result[0]:
        print(f"IP: {answer.payload.dottedQuad()}")
    
    # Lookup MX record
    result = yield resolver.lookupMailExchange('example.com')
    for answer in result[0]:
        print(f"MX: {answer.payload.name}")
    
    reactor.stop()

lookup_dns()
reactor.run()

# === SERVEUR DNS SIMPLE ===

class DynamicResolver:
    
    def query(self, query, timeout=None):
        if query.type == dns.A:
            name = query.name.name
            if name == b'example.com':
                answer = dns.RRHeader(
                    name=name,
                    payload=dns.Record_A(address=b'93.184.216.34')
                )
                return defer.succeed(([answer], [], []))
        
        return defer.fail(error.DomainError())

# Lancer serveur DNS
factory = server.DNSServerFactory(
    clients=[DynamicResolver(), cache.CacheResolver()]
)

protocol = dns.DNSDatagramProtocol(controller=factory)
reactor.listenUDP(10053, protocol)
reactor.listenTCP(10053, factory)
reactor.run()


[OK] FTP CLIENT & SERVER

# Support FTP complet

# === CLIENT FTP ===

from twisted.protocols.ftp import FTPClient
from twisted.internet import reactor, protocol

class FTPClientCreator(protocol.ClientCreator):
    pass

@defer.inlineCallbacks
def ftp_operations():
    creator = protocol.ClientCreator(reactor, FTPClient, 'username', 'password')
    client = yield creator.connectTCP('ftp.example.com', 21)
    
    # Lister répertoire
    files = yield client.list('.')
    print(f"Fichiers: {files}")
    
    # Télécharger fichier
    yield client.retrieveFile('remote.txt', open('local.txt', 'wb'))
    
    # Upload fichier
    yield client.storeFile('remote.txt', open('local.txt', 'rb'))
    
    # Changer répertoire
    yield client.cwd('/path/to/dir')
    
    # Créer répertoire
    yield client.makeDirectory('newdir')
    
    yield client.quit()
    reactor.stop()

# === SERVEUR FTP ===

from twisted.protocols.ftp import FTPFactory, FTPRealm
from twisted.cred.portal import Portal
from twisted.cred.checkers import AllowAnonymousAccess

realm = FTPRealm('/tmp')  # Racine FTP
portal = Portal(realm, [AllowAnonymousAccess()])

factory = FTPFactory(portal)
reactor.listenTCP(2121, factory)
reactor.run()


[OK] PROCESS PROTOCOLS - EXÉCUTION DE COMMANDES

# Exécuter des processus système

from twisted.internet import protocol, reactor, utils

# === PROCESS PROTOCOL ===

class CommandProtocol(protocol.ProcessProtocol):
    
    def __init__(self):
        self.output = []
        self.error = []
    
    def connectionMade(self):
        print("Process démarré")
        # Envoyer à stdin
        self.transport.write(b"input data\n")
        self.transport.closeStdin()
    
    def outReceived(self, data):
        """stdout"""
        print(f"stdout: {data.decode()}")
        self.output.append(data)
    
    def errReceived(self, data):
        """stderr"""
        print(f"stderr: {data.decode()}")
        self.error.append(data)
    
    def processEnded(self, status):
        print(f"Process terminé: {status.value.exitCode}")
        reactor.stop()

# Lancer process
proc = CommandProtocol()
reactor.spawnProcess(
    proc,
    '/bin/ls',
    ['ls', '-la', '/tmp'],
    env={'PATH': '/bin:/usr/bin'}
)
reactor.run()

# === GETPROCESSOUTPUT (SIMPLE) ===

@defer.inlineCallbacks
def run_command():
    output = yield utils.getProcessOutput('/bin/ls', ['-la'])
    print(f"Output: {output.decode()}")
    reactor.stop()

run_command()
reactor.run()

# === GETPROCESSVALUE (AVEC EXIT CODE) ===

@defer.inlineCallbacks
def run_with_exit_code():
    exitcode, output, error = yield utils.getProcessOutputAndValue(
        '/bin/ls', ['-la', '/nonexistent']
    )
    print(f"Exit: {exitcode}, Output: {output}, Error: {error}")
    reactor.stop()


[OK] LOGGING

# Système de logging de Twisted

from twisted.python import log
from twisted.logger import Logger, textFileLogObserver
import sys

# === LOGGING BASIQUE ===

# Démarrer logging vers stdout
log.startLogging(sys.stdout)

# Logger messages
log.msg("Message info")
log.msg("Message avec contexte", system="MyApp")

# Logger erreurs
try:
    1 / 0
except:
    log.err()  # Log l'exception courante

# Logger erreur personnalisée
log.err(Exception("Erreur custom"))

# === NOUVEAU SYSTÈME (twisted.logger) ===

from twisted.logger import Logger, globalLogBeginner, textFileLogObserver

# Créer logger
logger = Logger()

# Démarrer logging
globalLogBeginner.beginLoggingTo([
    textFileLogObserver(sys.stdout)
])

# Logger avec niveaux
logger.debug("Message debug")
logger.info("Message info")
logger.warn("Message warning")
logger.error("Message erreur")
logger.critical("Message critique")

# Logger avec contexte
logger.info("User logged in", user="john", ip="192.168.1.1")

# === LOGGING VERS FICHIER ===

from twisted.logger import textFileLogObserver, FileLogObserver

# Fichier texte
with open('app.log', 'a') as f:
    observer = textFileLogObserver(f)
    globalLogBeginner.beginLoggingTo([observer])

# Format JSON
from twisted.logger import jsonFileLogObserver

with open('app.json', 'a') as f:
    observer = jsonFileLogObserver(f)
    globalLogBeginner.beginLoggingTo([observer])

# === FILTRES DE LOG ===

from twisted.logger import FilteringLogObserver, LogLevelFilterPredicate, LogLevel

# Filtrer par niveau
predicate = LogLevelFilterPredicate(defaultLogLevel=LogLevel.info)
observer = FilteringLogObserver(textFileLogObserver(sys.stdout), [predicate])

# === LOG ROTATION ===

from twisted.python.logfile import DailyLogFile

log_file = DailyLogFile('app.log', '/var/log/myapp')
log.startLogging(log_file)


[OK] TESTING

# Twisted inclut un framework de test complet

from twisted.trial import unittest
from twisted.internet import defer, reactor

# === TEST UNITAIRE ===

class MyTest(unittest.TestCase):
    
    def test_simple(self):
        self.assertEqual(1 + 1, 2)
    
    def test_failure(self):
        self.assertRaises(ValueError, int, 'not a number')

# === TEST ASYNCHRONE ===

class AsyncTest(unittest.TestCase):
    
    @defer.inlineCallbacks
    def test_async_operation(self):
        result = yield some_async_function()
        self.assertEqual(result, 42)
    
    def test_with_deferred(self):
        d = some_async_function()
        d.addCallback(lambda r: self.assertEqual(r, 42))
        return d  # Retourner Deferred

# === TEST TIMEOUT ===

class TimeoutTest(unittest.TestCase):
    
    timeout = 5  # Timeout en secondes
    
    @defer.inlineCallbacks
    def test_slow_operation(self):
        result = yield slow_async_function()
        self.assertTrue(result)

# === SETUP ET TEARDOWN ===

class SetupTest(unittest.TestCase):
    
    def setUp(self):
        """Avant chaque test"""
        self.data = []
    
    def tearDown(self):
        """Après chaque test"""
        self.data = None
    
    def test_something(self):
        self.data.append(1)
        self.assertEqual(len(self.data), 1)

# === MOCK PROTOCOLS ===

from twisted.test import proto_helpers

class ProtocolTest(unittest.TestCase):
    
    def setUp(self):
        self.protocol = MyProtocol()
        self.transport = proto_helpers.StringTransport()
        self.protocol.makeConnection(self.transport)
    
    def test_data_received(self):
        self.protocol.dataReceived(b"Hello")
        # Vérifier ce qui a été écrit
        self.assertEqual(self.transport.value(), b"Response")

# === MOCK REACTOR ===

from twisted.internet import task

class ClockTest(unittest.TestCase):
    
    def test_with_clock(self):
        clock = task.Clock()
        
        d = task.deferLater(clock, 5, lambda: 42)
        
        # Avancer le temps
        clock.advance(5)
        
        # Vérifier résultat
        self.assertEqual(self.successResultOf(d), 42)

# === EXÉCUTER TESTS ===

# Ligne de commande:
# trial mymodule
# trial mymodule.MyTest
# trial mymodule.MyTest.test_method

# Avec coverage:
# trial --coverage mymodule


[OK] TWISTED.WEB AVANCÉ

# Fonctionnalités avancées du serveur web

# === REST API ===

from twisted.web import resource, server
import json

class APIResource(resource.Resource):
    
    def render_GET(self, request):
        request.setHeader(b'Content-Type', b'application/json')
        data = {'status': 'ok', 'data': [1, 2, 3]}
        return json.dumps(data).encode()
    
    def render_POST(self, request):
        content = request.content.read()
        data = json.loads(content)
        
        # Traiter data
        response = {'received': data, 'status': 'processed'}
        
        request.setHeader(b'Content-Type', b'application/json')
        return json.dumps(response).encode()

# === AUTHENTICATION ===

from twisted.web import resource
from twisted.cred import portal, checkers, credentials

class ProtectedResource(resource.Resource):
    
    def render(self, request):
        # Vérifier authentification
        auth_header = request.getHeader(b'Authorization')
        if not auth_header:
            request.setResponseCode(401)
            request.setHeader(b'WWW-Authenticate', b'Basic realm="API"')
            return b'Unauthorized'
        
        # Décoder Basic Auth
        import base64
        auth_decoded = base64.b64decode(auth_header.split()[1])
        username, password = auth_decoded.decode().split(':')
        
        if self.check_auth(username, password):
            return b'Authenticated content'
        else:
            request.setResponseCode(401)
            return b'Invalid credentials'
    
    def check_auth(self, username, password):
        return username == 'admin' and password == 'secret'

# === SESSIONS ===

from twisted.web.server import Session

class SessionResource(resource.Resource):
    
    def render_GET(self, request):
        session = request.getSession()
        
        # Stocker dans session
        if not hasattr(session, 'counter'):
            session.counter = 0
        session.counter += 1
        
        return f"Visite #{session.counter}".encode()

# === UPLOAD FICHIER ===

class UploadResource(resource.Resource):
    
    def render_POST(self, request):
        filename = request.args[b'filename'][0]
        content = request.args[b'file'][0]
        
        with open(filename.decode(), 'wb') as f:
            f.write(content)
        
        return b'File uploaded'

# === CORS ===

class CORSResource(resource.Resource):
    
    def render_OPTIONS(self, request):
        self.set_cors_headers(request)
        return b''
    
    def render_GET(self, request):
        self.set_cors_headers(request)
        return b'{"data": "value"}'
    
    def set_cors_headers(self, request):
        request.setHeader(b'Access-Control-Allow-Origin', b'*')
        request.setHeader(b'Access-Control-Allow-Methods', b'GET, POST, OPTIONS')
        request.setHeader(b'Access-Control-Allow-Headers', b'Content-Type')

# === SERVER-SENT EVENTS (SSE) ===

from twisted.web import resource, server

class SSEResource(resource.Resource):
    isLeaf = True
    
    def __init__(self):
        resource.Resource.__init__(self)
        self.clients = []
    
    def render_GET(self, request):
        request.setHeader(b'Content-Type', b'text/event-stream')
        request.setHeader(b'Cache-Control', b'no-cache')
        request.setHeader(b'Connection', b'keep-alive')
        
        self.clients.append(request)
        
        # Envoyer message initial
        request.write(b'data: Connected\n\n')
        
        return server.NOT_DONE_YET
    
    def broadcast(self, message):
        data = f'data: {message}\n\n'.encode()
        for client in self.clients:
            client.write(data)


[OK] TWISTED AVEC ASYNCIO

# Intégration avec asyncio (Python 3.4+)

from twisted.internet import asyncioreactor
asyncioreactor.install()

from twisted.internet import reactor, defer
import asyncio

# === UTILISER ASYNC/AWAIT ===

async def async_operation():
    await asyncio.sleep(1)
    return 42

@defer.inlineCallbacks
def twisted_operation():
    # Appeler coroutine asyncio depuis Twisted
    result = yield defer.ensureDeferred(async_operation())
    print(f"Result: {result}")
    reactor.stop()

reactor.callWhenRunning(twisted_operation)
reactor.run()

# === DEFERRED TO ASYNCIO ===

from twisted.internet import defer

async def use_twisted_from_asyncio():
    # Convertir Deferred en Future
    future = defer.Deferred().asFuture(asyncio.get_event_loop())
    result = await future
    return result


[OK] TWISTED APPLICATION (twistd)

# twistd = daemon Twisted pour production

# === CRÉER APPLICATION ===

from twisted.application import service, internet
from twisted.web import server, resource

class HelloResource(resource.Resource):
    isLeaf = True
    def render_GET(self, request):
        return b"Hello, World!"

# Créer application
application = service.Application("myapp")

# Ajouter service
root = HelloResource()
site = server.Site(root)
service = internet.TCPServer(8080, site)
service.setServiceParent(application)

# === SAUVEGARDER (myapp.tac) ===

# Fichier: myapp.tac
from twisted.application import service, internet
from twisted.web import server, resource

class Root(resource.Resource):
    def render_GET(self, request):
        return b"Running via twistd"

application = service.Application("myapp")
site = server.Site(Root())
internet.TCPServer(8080, site).setServiceParent(application)

# === LANCER AVEC TWISTD ===

# Démarrer en foreground
twistd -ny myapp.tac

# Démarrer en daemon
twistd -y myapp.tac

# Avec log personnalisé
twistd -y myapp.tac --logfile=/var/log/myapp.log

# Avec PID file
twistd -y myapp.tac --pidfile=/var/run/myapp.pid

# Sans daemon (foreground)
twistd -noy myapp.tac

# Arrêter
kill $(cat twistd.pid)

# === PLUGINS D'APPLICATION ===

# Créer twisted/plugins/myapp_plugin.py
from twisted.application.service import IServiceMaker
from twisted.plugin import IPlugin
from twisted.python import usage
from zope.interface import implementer

class Options(usage.Options):
    optParameters = [
        ['port', 'p', 8080, 'Port to listen on'],
    ]

@implementer(IServiceMaker, IPlugin)
class MyServiceMaker:
    tapname = "myapp"
    description = "My Application"
    options = Options
    
    def makeService(self, options):
        from twisted.application import internet
        from twisted.web import server, resource
        
        root = resource.Resource()
        site = server.Site(root)
        return internet.TCPServer(int(options['port']), site)

serviceMaker = MyServiceMaker()

# Lancer:
# twistd myapp --port=8080


[OK] TWISTED CONCH (SSH/TERMINAL)

# Conch = implémentation SSH complète

# === SERVEUR SSH INTERACTIF ===

from twisted.conch import avatar, recvline, insults
from twisted.conch.ssh import factory, keys, session
from twisted.cred import portal, checkers
from twisted.internet import reactor

class SSHProtocol(recvline.HistoricRecvLine):
    
    def __init__(self, user):
        self.user = user
    
    def connectionMade(self):
        recvline.HistoricRecvLine.connectionMade(self)
        self.terminal.write(b"Welcome!\n")
        self.showPrompt()
    
    def showPrompt(self):
        self.terminal.write(b"$ ")
    
    def lineReceived(self, line):
        line = line.strip()
        if line:
            self.terminal.write(f"You typed: {line}\n".encode())
        self.showPrompt()

class SSHAvatar(avatar.ConchUser):
    
    def __init__(self, username):
        avatar.ConchUser.__init__(self)
        self.username = username
        self.channelLookup.update({b'session': session.SSHSession})
    
    def openShell(self, protocol):
        serverProtocol = insults.ServerProtocol(SSHProtocol, self.username)
        serverProtocol.makeConnection(protocol)
        protocol.makeConnection(session.wrapProtocol(serverProtocol))

# Configuration serveur
class SSHRealm:
    def requestAvatar(self, avatarId, mind, *interfaces):
        return interfaces[0], SSHAvatar(avatarId), lambda: None


[OK] AMP (ASYNCHRONOUS MESSAGING PROTOCOL)

# AMP = protocole RPC binaire de Twisted

from twisted.protocols import amp
from twisted.internet import reactor

# === DÉFINIR COMMANDES ===

class Sum(amp.Command):
    arguments = [
        (b'a', amp.Integer()),
        (b'b', amp.Integer())
    ]
    response = [(b'result', amp.Integer())]

class Divide(amp.Command):
    arguments = [
        (b'numerator', amp.Integer()),
        (b'denominator', amp.Integer())
    ]
    response = [(b'result', amp.Float())]
    errors = {ZeroDivisionError: b'ZERO_DIVISION'}

# === SERVEUR AMP ===

class MathProtocol(amp.AMP):
    
    @Sum.responder
    def sum(self, a, b):
        return {'result': a + b}
    
    @Divide.responder
    def divide(self, numerator, denominator):
        if denominator == 0:
            raise ZeroDivisionError()
        return {'result': numerator / denominator}

factory = amp.Factory(MathProtocol)
reactor.listenTCP(8000, factory)

# === CLIENT AMP ===

class MathClient(amp.AMP):
    pass

@defer.inlineCallbacks
def use_amp_client():
    creator = protocol.ClientCreator(reactor, MathClient)
    client = yield creator.connectTCP('localhost', 8000)
    
    # Appeler commandes
    result = yield client.callRemote(Sum, a=5, b=3)
    print(f"Sum: {result[b'result']}")
    
    result = yield client.callRemote(Divide, numerator=10, denominator=2)
    print(f"Divide: {result[b'result']}")
    
    reactor.stop()

# === TYPES AMP ===

# Types disponibles:
# amp.String()
# amp.Integer()
# amp.Float()
# amp.Boolean()
# amp.Unicode()
# amp.DateTime()
# amp.ListOf(amp.Integer())
# amp.AmpList([(b'key', amp.String())])


[OK] PERFORMANCE ET OPTIMISATION

# Conseils pour optimiser applications Twisted

# === PRODUCER/CONSUMER ===

from twisted.internet import interfaces
from zope.interface import implementer

@implementer(interfaces.IPushProducer)
class FileProducer:
    """Envoyer fichier efficacement"""
    
    def __init__(self, file_obj, consumer):
        self.file = file_obj
        self.consumer = consumer
    
    def resumeProducing(self):
        # Lire et envoyer chunks
        data = self.file.read(8192)
        if data:
            self.consumer.write(data)
        else:
            self.consumer.unregisterProducer()
            self.consumer.finish()
    
    def pauseProducing(self):
        pass
    
    def stopProducing(self):
        self.file.close()

# Utilisation dans Protocol
def dataReceived(self, data):
    if data == b'GET_FILE':
        f = open('largefile.bin', 'rb')
        producer = FileProducer(f, self.transport)
        self.transport.registerProducer(producer, True)

# === CONNECTION POOLING ===

from twisted.internet import defer

class ConnectionPool:
    
    def __init__(self, factory, size=5):
        self.factory = factory
        self.size = size
        self.connections = []
        self.waiting = []
    
    @defer.inlineCallbacks
    def get_connection(self):
        if self.connections:
            defer.returnValue(self.connections.pop())
        elif len(self.waiting) < self.size:
            conn = yield self.factory.create_connection()
            defer.returnValue(conn)
        else:
            d = defer.Deferred()
            self.waiting.append(d)
            conn = yield d
            defer.returnValue(conn)
    
    def return_connection(self, conn):
        if self.waiting:
            d = self.waiting.pop(0)
            d.callback(conn)
        else:
            self.connections.append(conn)

# === RATE LIMITING ===

from twisted.internet import task

class RateLimiter:
    
    def __init__(self, rate, per_seconds):
        self.rate = rate
        self.per_seconds = per_seconds
        self.allowance = rate
        self.last_check = reactor.seconds()
    
    def allow(self):
        current = reactor.seconds()
        time_passed = current - self.last_check
        self.last_check = current
        
        self.allowance += time_passed * (self.rate / self.per_seconds)
        
        if self.allowance > self.rate:
            self.allowance = self.rate
        
        if self.allowance < 1.0:
            return False
        else:
            self.allowance -= 1.0
            return True

# === CACHING ===

from twisted.internet import defer

class Cache:
    
    def __init__(self, ttl=300):
        self.cache = {}
        self.ttl = ttl
    
    @defer.inlineCallbacks
    def get(self, key, fetch_func):
        if key in self.cache:
            value, timestamp = self.cache[key]
            if reactor.seconds() - timestamp < self.ttl:
                defer.returnValue(value)
        
        value = yield fetch_func()
        self.cache[key] = (value, reactor.seconds())
        defer.returnValue(value)


[OK] SÉCURITÉ

# Bonnes pratiques de sécurité

# === SSL/TLS MODERNE ===

from twisted.internet import ssl, reactor
from OpenSSL import SSL

class ServerTLSContext(ssl.DefaultOpenSSLContextFactory):
    
    def __init__(self, privateKeyFileName, certificateFileName):
        ssl.DefaultOpenSSLContextFactory.__init__(
            self, privateKeyFileName, certificateFileName
        )
    
    def getContext(self):
        ctx = ssl.DefaultOpenSSLContextFactory.getContext(self)
        
        # Protocoles modernes seulement
        ctx.set_options(SSL.OP_NO_SSLv2)
        ctx.set_options(SSL.OP_NO_SSLv3)
        ctx.set_options(SSL.OP_NO_TLSv1)
        ctx.set_options(SSL.OP_NO_TLSv1_1)
        
        # Ciphers sécurisés
        ctx.set_cipher_list(b'ECDHE+AESGCM:ECDHE+CHACHA20:DHE+AESGCM')
        
        return ctx

# === VALIDATION INPUT ===

class SecureProtocol(LineReceiver):
    
    MAX_LENGTH = 1024  # Limite taille ligne
    
    def lineReceived(self, line):
        # Valider input
        if not self.validate_input(line):
            self.transport.loseConnection()
            return
        
        self.process_line(line)
    
    def validate_input(self, line):
        # Vérifier caractères dangereux
        if any(c in line for c in b'\x00\r\n'):
            return False
        
        # Vérifier longueur
        if len(line) > self.MAX_LENGTH:
            return False
        
        return True

# === PROTECTION BRUTE FORCE ===

class LoginRateLimiter:
    
    def __init__(self):
        self.attempts = {}  # IP -> (count, timestamp)
        self.max_attempts = 5
        self.lockout_time = 300  # 5 minutes
    
    def check_allowed(self, ip):
        now = reactor.seconds()
        
        if ip in self.attempts:
            count, timestamp = self.attempts[ip]
            
            # Reset après lockout
            if now - timestamp > self.lockout_time:
                del self.attempts[ip]
                return True
            
            if count >= self.max_attempts:
                return False
        
        return True
    
    def record_attempt(self, ip, success):
        now = reactor.seconds()
        
        if success:
            if ip in self.attempts:
                del self.attempts[ip]
        else:
            if ip in self.attempts:
                count, _ = self.attempts[ip]
                self.attempts[ip] = (count + 1, now)
            else:
                self.attempts[ip] = (1, now)

# === SANITIZATION ===

import html

class SafeWebResource(resource.Resource):
    
    def render_GET(self, request):
        # Échapper input utilisateur
        name = request.args.get(b'name', [b'Guest'])[0]
        safe_name = html.escape(name.decode())
        
        return f"<h1>Hello, {safe_name}!</h1>".encode()


[OK] DEBUGGING

# Techniques de debugging

# === ACTIVER DEBUG MODE ===

from twisted.python import log
import sys

log.startLogging(sys.stdout)

# Debug Deferred
defer.setDebugging(True)

# === INSPECTEUR D'ÉTAT ===

from twisted.manhole import telnet

# Ajouter shell interactif
factory = telnet.ShellFactory()
factory.username = b'admin'
factory.password = b'secret'
factory.namespace = {'reactor': reactor, 'factory': my_factory}

reactor.listenTCP(2222, factory)

# Connecter: telnet localhost 2222

# === PROFILING ===

from twisted.python import log
import cProfile

def profile_function():
    profiler = cProfile.Profile()
    profiler.enable()
    
    # Code à profiler
    my_function()
    
    profiler.disable()
    profiler.print_stats(sort='cumulative')

# === MEMORY DEBUGGING ===

from twisted.python import log
import gc

def check_memory():
    gc.collect()
    objects = gc.get_objects()
    log.msg(f"Objects in memory: {len(objects)}")
    
    # Compter par type
    types = {}
    for obj in objects:
        t = type(obj).__name__
        types[t] = types.get(t, 0) + 1
    
    for t, count in sorted(types.items(), key=lambda x: x[1], reverse=True)[:10]:
        log.msg(f"{t}: {count}")

# Vérifier périodiquement
from twisted.internet import task
loop = task.LoopingCall(check_memory)
loop.start(60)  # Toutes les 60 secondes


[OK] PATTERNS COMMUNS

# Patterns d'architecture fréquents

# === SINGLETON FACTORY ===

class SingletonFactory(protocol.Factory):
    
    _instance = None
    
    def __new__(cls):
        if cls._instance is None:
            cls._instance = super().__new__(cls)
        return cls._instance

# === OBSERVER PATTERN ===

class Observable:
    
    def __init__(self):
        self.observers = []
    
    def attach(self, observer):
        self.observers.append(observer)
    
    def detach(self, observer):
        self.observers.remove(observer)
    
    def notify(self, event):
        for observer in self.observers:
            observer.update(event)

# === COMMAND PATTERN ===

class Command:
    def execute(self): pass
    def undo(self): pass

class CommandQueue:
    
    def __init__(self):
        self.commands = []
        self.history = []
    
    @defer.inlineCallbacks
    def execute_command(self, command):
        yield command.execute()
        self.history.append(command)
    
    @defer.inlineCallbacks
    def undo_last(self):
        if self.history:
            command = self.history.pop()
            yield command.undo()

# === STATE MACHINE ===

class StateMachine:
    
    def __init__(self):
        self.state = 'IDLE'
        self.transitions = {
            'IDLE': {'START': 'RUNNING'},
            'RUNNING': {'STOP': 'IDLE', 'PAUSE': 'PAUSED'},
            'PAUSED': {'RESUME': 'RUNNING', 'STOP': 'IDLE'}
        }
    
    def transition(self, event):
        if event in self.transitions.get(self.state, {}):
            old_state = self.state
            self.state = self.transitions[self.state][event]
            self.on_transition(old_state, self.state)
            return True
        return False
    
    def on_transition(self, old, new):
        print(f"Transition: {old} -> {new}")


[OK] EXEMPLES COMPLETS

# === CHAT SERVER COMPLET ===

from twisted.internet import reactor, protocol
from twisted.protocols.basic import LineReceiver

class ChatProtocol(LineReceiver):
    
    def __init__(self, factory):
        self.factory = factory
        self.username = None
        self.state = "USERNAME"
    
    def connectionMade(self):
        self.sendLine(b"Entrez votre nom:")
    
    def connectionLost(self, reason):
        if self.username:
            self.factory.broadcast(f"{self.username} a quitté".encode())
            self.factory.remove_user(self)
    
    def lineReceived(self, line):
        if self.state == "USERNAME":
            self.handle_username(line)
        elif self.state == "CHAT":
            self.handle_message(line)
    
    def handle_username(self, username):
        username = username.decode().strip()
        
        if not username or username in self.factory.users:
            self.sendLine(b"Nom invalide ou déjà pris. Réessayez:")
            return
        
        self.username = username
        self.state = "CHAT"
        self.factory.add_user(self)
        self.factory.broadcast(f"{username} a rejoint le chat".encode())
        self.sendLine(b"Bienvenue! Tapez /help pour l'aide")
    
    def handle_message(self, message):
        message = message.decode().strip()
        
        if message.startswith('/'):
            self.handle_command(message)
        else:
            self.factory.broadcast(
                f"{self.username}: {message}".encode(),
                exclude=self
            )
    
    def handle_command(self, command):
        if command == '/help':
            self.sendLine(b"Commandes: /users, /quit, /help")
        elif command == '/users':
            users = ', '.join(self.factory.users.keys())
            self.sendLine(f"Utilisateurs: {users}".encode())
        elif command == '/quit':
            self.transport.loseConnection()
        else:
            self.sendLine(b"Commande inconnue")

class ChatFactory(protocol.Factory):
    
    def __init__(self):
        self.users = {}  # username -> protocol
    
    def buildProtocol(self, addr):
        return ChatProtocol(self)
    
    def add_user(self, protocol):
        self.users[protocol.username] = protocol
    
    def remove_user(self, protocol):
        if protocol.username in self.users:
            del self.users[protocol.username]
    
    def broadcast(self, message, exclude=None):
        for protocol in self.users.values():
            if protocol != exclude:
                protocol.sendLine(message)

if __name__ == '__main__':
    reactor.listenTCP(8000, ChatFactory())
    print("Chat server sur port 8000")
    reactor.run()

# === PROXY HTTP ===

from twisted.web import proxy, http
from twisted.internet import reactor

class ProxyClient(proxy.ProxyClient):
    
    def handleHeader(self, key, value):
        # Modifier headers si nécessaire
        if key.lower() == b'user-agent':
            value = b'CustomProxy/1.0'
        proxy.ProxyClient.handleHeader(self, key, value)

class ProxyClientFactory(proxy.ProxyClientFactory):
    protocol = ProxyClient

class ProxyRequest(proxy.ProxyRequest):
    protocols = {b'http': ProxyClientFactory}

class Proxy(proxy.Proxy):
    requestFactory = ProxyRequest

class ProxyFactory(http.HTTPFactory):
    protocol = Proxy

reactor.listenTCP(8080, ProxyFactory())
print("Proxy HTTP sur port 8080")
reactor.run()

# === API REST COMPLET ===

from twisted.web import resource, server
from twisted.internet import reactor
import json

class Item:
    def __init__(self, id, name):
        self.id = id
        self.name = name
    
    def to_dict(self):
        return {'id': self.id, 'name': self.name}

class ItemsResource(resource.Resource):
    
    def __init__(self):
        resource.Resource.__init__(self)
        self.items = {}
        self.next_id = 1
    
    def getChild(self, path, request):
        if path:
            return ItemResource(self, path.decode())
        return self
    
    def render_GET(self, request):
        request.setHeader(b'Content-Type', b'application/json')
        items = [item.to_dict() for item in self.items.values()]
        return json.dumps(items).encode()
    
    def render_POST(self, request):
        data = json.loads(request.content.read())
        
        item = Item(self.next_id, data['name'])
        self.items[self.next_id] = item
        self.next_id += 1
        
        request.setResponseCode(201)
        request.setHeader(b'Content-Type', b'application/json')
        return json.dumps(item.to_dict()).encode()

class ItemResource(resource.Resource):
    
    isLeaf = True
    
    def __init__(self, items_resource, item_id):
        resource.Resource.__init__(self)
        self.items = items_resource.items
        self.item_id = int(item_id)
    
    def render_GET(self, request):
        if self.item_id not in self.items:
            request.setResponseCode(404)
            return b'{"error": "Not found"}'
        
        request.setHeader(b'Content-Type', b'application/json')
        return json.dumps(self.items[self.item_id].to_dict()).encode()
    
    def render_PUT(self, request):
        if self.item_id not in self.items:
            request.setResponseCode(404)
            return b'{"error": "Not found"}'
        
        data = json.loads(request.content.read())
        self.items[self.item_id].name = data['name']
        
        request.setHeader(b'Content-Type', b'application/json')
        return json.dumps(self.items[self.item_id].to_dict()).encode()
    
    def render_DELETE(self, request):
        if self.item_id not in self.items:
            request.setResponseCode(404)
            return b'{"error": "Not found"}'
        
        del self.items[self.item_id]
        request.setResponseCode(204)
        return b''

root = resource.Resource()
root.putChild(b'items', ItemsResource())

reactor.listenTCP(8080, server.Site(root))
print("API REST sur port 8080")
reactor.run()


[OK] MIGRATION ET INTÉGRATION

# Intégrer Twisted dans projets existants

# === TWISTED + DJANGO ===

# settings.py
INSTALLED_APPS = [
    'daphne',  # pip install daphne
    # ... autres apps
]

# ASGI configuration
ASGI_APPLICATION = 'myproject.asgi.application'

# === TWISTED + FLASK ===

from twisted.web.wsgi import WSGIResource
from twisted.internet import reactor
from flask import Flask

app = Flask(__name__)

@app.route('/')
def hello():
    return "Hello from Flask + Twisted"

resource = WSGIResource(reactor, reactor.getThreadPool(), app)
from twisted.web.server import Site
reactor.listenTCP(8080, Site(resource))
reactor.run()

# === TWISTED + CELERY ===

from twisted.internet import defer, reactor
from celery import Celery

app = Celery('tasks', broker='redis://localhost')

@defer.inlineCallbacks
def call_celery_task():
    # Exécuter tâche Celery depuis Twisted
    result = yield threads.deferToThread(
        my_celery_task.delay, arg1, arg2
    )
    defer.returnValue(result.get())


[OK] BONNES PRATIQUES

# Recommandations pour code production

# 1. TOUJOURS GÉRER LES ERREURS
d = some_operation()
d.addErrback(log.err)  # Au minimum logger les erreurs

# 2. UTILISER INLINECALLBACKS
@defer.inlineCallbacks
def my_function():
    # Plus lisible que chaînes de callbacks
    result = yield operation1()
    result2 = yield operation2(result)
    defer.returnValue(result2)

# 3. TIMEOUT SUR OPERATIONS
from twisted.internet import task

d = task.deferLater(reactor, 30, slow_operation)
# Ou annuler si trop lent

# 4. FERMER CONNEXIONS PROPREMENT
def connectionLost(self, reason):
    # Cleanup resources
    self.cleanup()
    Protocol.connectionLost(self, reason)

# 5. LOGGING APPROPRIÉ
from twisted.python import log
log.startLogging(sys.stdout)
log.msg("Info message", system="MyApp")

# 6. TESTS UNITAIRES
from twisted.trial import unittest

class MyTest(unittest.TestCase):
    @defer.inlineCallbacks
    def test_async(self):
        result = yield my_async_function()
        self.assertEqual(result, expected)

# 7. UTILISER TWISTD EN PRODUCTION
# Ne pas appeler reactor.run() directement
# Créer .tac file et utiliser twistd

# 8. RATE LIMITING
# Implémenter rate limiting pour éviter abus

# 9. VALIDATION INPUT
# Toujours valider données utilisateur

# 10. MONITORING
# Exposer métriques (Prometheus, etc.)


[OK] DÉPANNAGE

# Problèmes courants et solutions

# === PROBLÈME: Reactor already running ===
# Solution: Ne pas appeler reactor.run() deux fois
# Utiliser reactor.callWhenRunning() pour code initial

# === PROBLÈME: Reactor not restartable ===
# Solution: Reactor ne peut pas être redémarré
# Utiliser twisted.trial pour tests

# === PROBLÈME: Callback never called ===
# Solution: Vérifier que Deferred est bien déclenché
defer.setDebugging(True)  # Aide à trouver Deferred non résolus

# === PROBLÈME: Memory leak ===
# Solution: Vérifier références circulaires
# Nettoyer dans connectionLost()

# === PROBLÈME: Port already in use ===
# Solution:
port = reactor.listenTCP(8000, factory)
# Ou utiliser SO_REUSEADDR
from twisted.internet.protocol import Factory
factory.protocol.transport.setTcpKeepAlive(1)

# === PROBLÈME: SSL certificate errors ===
# Solution: Vérifier certificats
from twisted.internet import ssl
ssl.CertificateOptions(verify=False)  # Seulement pour dev!


[OK] RESSOURCES

# Documentation et liens utiles

# Documentation officielle:
# https://docs.twistedmatrix.com/

# Tutoriels:
# https://twistedmatrix.com/documents/current/core/howto/index.html

# API Reference:
# https://docs.twistedmatrix.com/en/stable/api/

# Exemples:
# https://github.com/twisted/twisted/tree/trunk/docs/core/examples

# Twisted Community:
# IRC: #twisted sur irc.libera.chat
# Mailing list: twisted-python@twistedmatrix.com

# Livres:
# "Twisted Network Programming Essentials" - O'Reilly
# "Expert Twisted" - Mark Williams

# Projets utilisant Twisted:
# - Scrapy (web scraping)
# - Buildbot (CI/CD)
# - Tahoe-LAFS (stockage distribué)
# - Klein (micro web framework)
# - Crossbar.io (WAMP router)


[OK] ALTERNATIVES ET COMPARAISONS

# Twisted vs autres frameworks

# === TWISTED vs ASYNCIO ===
# Twisted:
# + Framework complet (protocols, web, email, etc.)
# + Mature et stable (20+ ans)
# + Excellente documentation
# - Courbe d'apprentissage
# - API parfois verbeuse

# asyncio:
# + Standard library (Python 3.4+)
# + Syntaxe async/await moderne
# + Plus simple pour débutants
# - Moins de protocols built-in
# - Écosystème moins mature

# === TWISTED vs TORNADO ===
# Twisted: Framework complet, tous protocols
# Tornado: Focus sur HTTP/WebSocket, plus simple

# === TWISTED vs GEVENT ===
# Twisted: Asynchrone explicite (Deferred)
# Gevent: Asynchrone transparent (greenlets)

# === QUAND UTILISER TWISTED ===
# [OK] Applications réseau complexes
# [OK] Protocols personnalisés
# [OK] Besoin de stabilité long-terme
# [OK] Applications avec multiples protocols
# [OK] Serveurs haute performance

# === QUAND NE PAS UTILISER TWISTED ===
# [X] Application web simple (Flask/Django suffit)
# [X] Scripts courts/simples
# [X] Équipe préfère async/await
# [X] Besoin de bibliothèques spécifiques asyncio