18.7. "asynchat" --- Gestionnaire d'interfaces de connexion (*socket*) commande/réponse asynchrones
***************************************************************************************************

*Code source :** Lib/asynchat.py

Obsolète depuis la version 3.6: Utilisez "asyncio" à la place.

======================================================================

Note:

  Ce module n'existe que pour des raisons de rétrocompatibilité. Pour
  du code nouveau, l'utilisation de "asyncio" est recommandée.

Ce module s'appuie sur l'infrastructure de "asyncore", en simplifiant
les clients et serveurs asynchrones et en rendant plus facile la
gestion de protocoles dont les éléments finissent par une chaine
arbitraire, ou sont de longueur variable. "asynchat" définit une
classe abstraite "async_chat" dont vous héritez, et qui fournit des
implémentations des méthodes "collect_incoming_data()" et
"found_terminator()". Il utilise la même boucle asynchrone que
"asyncore", et deux types de canaux, "asyncore.dispatcher" et
"asynchat.async_chat", qui peuvent être librement mélangés dans la
carte des canaux. Habituellement, un canal de serveur
"asyncore.dispatcher" génère de nouveaux canaux d'objets
"asynchat.async_chat" à la réception de requêtes de connexion.

class asynchat.async_chat

   Cette classe est une sous-classe abstraite de
   "asyncore.dispatcher". Pour en faire un usage pratique, vous devez
   créer une classe héritant de "async_chat", et implémentant des
   méthodes "collect_incoming_data()" et "found_terminator()" sensées.
   Les méthodes de  "asyncore.dispatcher" peuvent être utilisées, même
   si toutes n'ont pas de sens dans un contexte de messages/réponse.

   Comme "asyncore.dispatcher", "async_chat" définit un ensemble
   d’événements générés par une analyse de l'état des interfaces de
   connexion (*socket* en anglais) après un appel à "select()". Une
   fois que la boucle de scrutation (*polling* en anglais) a été
   lancée, les méthodes des objets "async_chat" sont appelées par le
   *framework* de traitement d’événements sans que le programmeur
   n'ait à le spécifier.

   Deux attributs de classe peuvent être modifiés, pour améliorer la
   performance, ou potentiellement pour économiser de la mémoire.

   ac_in_buffer_size

      La taille du tampon d'entrées asynchrones ("4096" par défaut).

   ac_out_buffer_size

      La taille du tampon de sorties asynchrones ("4096" par défaut).

   Contrairement à "asyncore.dispatcher", "async_chat" permet de
   définir une queue FIFO (first-in, first-out) de *producteurs*. Un
   producteur nécessite seulement une méthode, "more()", qui renvoie
   la donnée à transmettre au canal. Le producteur indique son
   épuisement (*c.-à-d.* qu'il ne contiens plus de données) en ne
   retournant avec sa méthode "more()" l'objet bytes vide. L'objet
   "async_chat" retire alors le producteur de la queue et commence à
   utiliser le producteur suivant, si il y en à un. Quand la queue de
   producteurs est vide, la méthode "handle_write()" ne fait rien. La
   méthode "set_terminator()" de l'objet du canal est utilisé pour
   décrire comment reconnaître la fin, ou la présence d'un point
   d'arrêt, dans in transmission entrante depuis le point d’accès
   distant.

   Pour construire une sous classe fonctionnelle de "async_chat" pour
   vos méthodes d'entrées "collect_incoming_data()" et
   "found_terminator()" doivent gérer la donnée que le canal reçoit de
   manière asynchrone. Ces méthodes sont décrites ci-dessous.

async_chat.close_when_done()

   Pousse un "None" sur la pile de producteurs. Quand ce producteur
   est récupéré dans la queue, le canal est fermé.

async_chat.collect_incoming_data(data)

   Appelé avec *data* contenant une quantité arbitraire de données. La
   méthode par défaut, qui doit être écrasée, lève une
   "NotImplementedError".

async_chat.discard_buffers()

   En cas d'urgence, cette méthode va supprimer tout donnée présente
   dans les tampons d'entrée et/ou de sortie dans la queue de
   producteurs.

async_chat.found_terminator()

   Appelée quand le flux de donné corresponds à la condition de fin
   décrite par "set_terminator()". La méthode par défaut, qui doit
   être écrasée, lève une "NotImplementedError". Les données entrantes
   mise en tampon devraient être disponible via un attribut de
   l'instance.

async_chat.get_terminator()

   Renvoie le terminateur courant pour le canal.

async_chat.push(data)

   Pousse *data* sur la pile du canal pour assurer sa transmission.
   C'est tout ce dont on a besoin pour que le canal envoie des données
   sur le réseau. Cependant, il est possible d'utiliser vos propres
   producteurs dans des schémas plus complexes qui implémentent de la
   cryptographie et du *chunking* par exemple.

async_chat.push_with_producer(producer)

   Prends un objet producteur l'ajoute à la queue de producteurs
   associée au canal. Quand tout les producteurs actuellement poussés
   ont été épuisé, le canal consomme les données de ce producteur en
   appelant sa méthode "more()" et envoie les données au point d’accès
   distant.

async_chat.set_terminator(term)

   Définit le marqueur de fin que le canal doit reconnaître. "term"
   peut être n'importe lequel des trois types de valeurs,
   correspondant aux trois différentes manières de gérer les données
   entrantes.

   +-------------+-----------------------------------------------+
   | *term*      | Description                                   |
   |=============|===============================================|
   | *string*    | Appellera "found_terminator()" quand la       |
   |             | chaîne est trouvée dans le flux d'entré       |
   +-------------+-----------------------------------------------+
   | *integer*   | Appellera "found_terminator()" quand le       |
   |             | nombre de caractère indiqué à été reçu        |
   +-------------+-----------------------------------------------+
   | "None"      | Le canal continue de collecter des            |
   |             | informations indéfiniment                     |
   +-------------+-----------------------------------------------+

   Notez que toute donnée située après le marqueur de fin sera
   accessible en lecture par le canal après que "found_terminator()"
   ai été appelé.


18.7.1. Exemple *asynchat*
==========================

L'exemple partiel suivant montre comment des requêtes HTTP peuvent
être lues avec "async_chat". Un serveur web pourrait créer un objet
"http_request_handler" pour chaque connections lient entrantes. Notez
que initialement, le marqueur de fin du canal est défini pour
reconnaître les lignes vides à la fin des entêtes HTTP, et une option
indique que les entêtes sont en train d'être lues.

Une fois que les entêtes ont été lues, si la requête est de type
*POST* (ce qui indique que davantage de données sont présent dans dans
le flux entrant) alors l'entête "Content-Length:" est utilisé pour
définir un marqueur de fin numérique pour lire la bonne quantité de
donné depuis le canal.

La méthode "handle_request()" est appelée une fois que toutes les
données pertinentes ont été rassemblées, après avoir définit le
marqueur de fin à "None" pour s'assurer que toute données étrangères
envoyées par le client web sont ignorées.

   import asynchat

   class http_request_handler(asynchat.async_chat):

       def __init__(self, sock, addr, sessions, log):
           asynchat.async_chat.__init__(self, sock=sock)
           self.addr = addr
           self.sessions = sessions
           self.ibuffer = []
           self.obuffer = b""
           self.set_terminator(b"\r\n\r\n")
           self.reading_headers = True
           self.handling = False
           self.cgi_data = None
           self.log = log

       def collect_incoming_data(self, data):
           """Buffer the data"""
           self.ibuffer.append(data)

       def found_terminator(self):
           if self.reading_headers:
               self.reading_headers = False
               self.parse_headers(b"".join(self.ibuffer))
               self.ibuffer = []
               if self.op.upper() == b"POST":
                   clen = self.headers.getheader("content-length")
                   self.set_terminator(int(clen))
               else:
                   self.handling = True
                   self.set_terminator(None)
                   self.handle_request()
           elif not self.handling:
               self.set_terminator(None)  # browsers sometimes over-send
               self.cgi_data = parse(self.headers, b"".join(self.ibuffer))
               self.handling = True
               self.ibuffer = []
               self.handle_request()
