18.7. asynchat — Gestionnaire d’interfaces de connexion (socket) commande/réponse asynchrones

Code source :* Lib/asynchat.py


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).

Unlike asyncore.dispatcher, async_chat allows you to define a first-in-first-out queue (fifo) of producers. A producer need have only one method, more(), which should return data to be transmitted on the channel. The producer indicates exhaustion (i.e. that it contains no more data) by having its more() method return the empty bytes object. At this point the async_chat object removes the producer from the fifo and starts using the next producer, if any. When the producer fifo is empty the handle_write() method does nothing. You use the channel object’s set_terminator() method to describe how to recognize the end of, or an important breakpoint in, an incoming transmission from the remote endpoint.

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()

Pushes a None on to the producer fifo. When this producer is popped off the fifo it causes the channel to be closed.

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()

In emergencies this method will discard any data held in the input and/or output buffers and the producer fifo.

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)

Pushes data on to the channel’s fifo to ensure its transmission. This is all you need to do to have the channel write the data out to the network, although it is possible to use your own producers in more complex schemes to implement encryption and chunking, for example.

async_chat.push_with_producer(producer)

Takes a producer object and adds it to the producer fifo associated with the channel. When all currently-pushed producers have been exhausted the channel will consume this producer’s data by calling its more() method and send the data to the remote endpoint.

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()