asynchat — Gestor de comandos/respuestas en sockets asíncronos

Código fuente: Lib/asynchat.py

Obsoleto desde la versión 3.6: asynchat will be removed in Python 3.12 (see PEP 594 for details). Please use asyncio instead.


Nota

Este módulo existe únicamente por motivos de retrocompatibilidad. Para nuevo código, es recomendable usar asyncio.

Este módulo se construye en la infraestructura de asyncore, simplificando los clientes y servidores asíncronos y facilitando la gestión de protocolos cuyos elementos son terminados por cadenas de texto arbitrarias, o que son de longitud variable. asynchat define la clase abstracta async_chat de la que se debe heredar, implementado los métodos collect_incoming_data() y found_terminator(). Utiliza el mismo bucle asíncrono que asyncore, y los dos tipos de canal, asyncore.dispatcher y asynchat.async_chat, se pueden mezclar libremente en el mapa de canal. Normalmente un canal de servidor asyncore.dispatcher genera nuevos objetos de canal asynchat.async_chat, al recibir peticiones de conexión entrantes.

class asynchat.async_chat

Esta clase es una subclase abstracta de asyncore.dispatcher. Para el uso práctico del código se debe heredar async_chat, definiendo los métodos significativos collect_incoming_data() y found_terminator(). Los métodos de asyncore.dispatcher se pueden utilizar, aunque no todos tienen sentido en un contexto de mensaje/respuesta.

Al igual que asyncore.dispatcher, async_chat define una serie de eventos generados por un análisis sobre las condiciones de _socket_, tras una llamada a select(). Una vez que el bucle de _polling_ haya sido iniciado, los métodos de los objetos async_chat son llamados por el _framework_ que procesa los eventos, sin que tengamos que programar ninguna acción a mayores.

Se pueden modificar dos atributos de clase, para mejorar el rendimiento o incluso hasta para ahorrar memoria.

ac_in_buffer_size

El tamaño del _buffer_ de entrada asíncrona (por defecto 4096).

ac_out_buffer_size

El tamaño del _buffer_ de salida asíncrona (por defecto 4096).

Al contrario que asyncore.dispatcher, async_chat permite definir una cola FIFO de productores (producers). Un productor necesita tener un solo método, more(), que debe devolver los datos que se vayan a transmitir en el canal. Cuando el método more() devuelve un objeto bytes vacío, significa que el productor ya se ha agotado (por ejemplo, que no contiene más datos). En este punto, el objeto async_chat elimina el productor de la cola y empieza a usar el siguiente productor, si existiese. Cuando la cola de productores está vacía, el método handle_write() no hace nada. El método set_terminator() de los objetos de canal se utiliza para describir cómo reconocer, en una transmisión entrante desde el punto remoto, el final de esta transmisión o un punto de ruptura importante en la misma.

Para construir una subclase funcional de async_chat, los métodos de entrada collect_incoming_data() and found_terminator() deben tratar los datos que el canal recibe asíncronamente. Los métodos se describen a continuación.

async_chat.close_when_done()

Añade un None en la cola de productores. Cuando este productor se extrae de la cola, hace que el canal se cierre.

async_chat.collect_incoming_data(data)

Llamado con data, conteniendo una cantidad arbitraria de datos recibidos. El método por defecto, que debe ser reemplazado, lanza una excepción NotImplementedError.

async_chat.discard_buffers()

En situaciones de emergencia, este método descarta cualquier dato albergado en los búfers de entrada y/o salida y en la cola del productor.

async_chat.found_terminator()

Llamado cuando el flujo de datos de entrada coincide con la condición de finalización establecida por set_terminator(). El método por defecto, que debe ser reemplazado, lanza una excepción NotImplementedError. Los datos de entrada en búfer deberían estar disponibles a través de un atributo de instancia.

async_chat.get_terminator()

Retorna el terminador actual del canal.

async_chat.push(data)

Añade datos en la cola del canal para asegurarse de su transmisión. Esto es todo lo que se necesita hacer para que el canal envíe los datos a la red, aunque es posible usar productores personalizados en esquemas más complejos para implementar características como encriptación o fragmentación.

async_chat.push_with_producer(producer)

Obtiene un objeto productor y lo añade a la cola de productores asociada al canal. Cuando todos los productores añadidos actualmente han sido agotados, el canal consumirá los datos de este productor llamando al método more(), y enviando los datos al punto remoto.

async_chat.set_terminator(term)

Establece la condición de finalización que será reconocida en este canal. term puede ser uno de los tres tipos de valores posibles, correspondientes a tres formas diferentes de tratar los datos de protocolo entrantes.

término

Descripción

string

Llamará a found_terminator() cuando la cadena de caracteres se encuentre en el flujo de datos de entrada

integer

Llamará a found_terminator() cuando el número de caracteres indicado se haya recibido

None

El canal continúa recopilando datos indefinidamente

Téngase en cuenta que cualquier dato posterior al terminador estará disponible para ser leído por el canal después de llamar a found_terminator().

Ejemplo de asynchat

El siguiente ejemplo parcial muestra cómo se pueden leer peticiones HTTP con async_chat. Un servidor web podría crear un objeto http_request_handler para cada conexión de cliente entrante. Téngase en cuenta que, inicialmente, el terminador del canal está configurado para detectar la línea vacía presente al final de las cabeceras HTTP, y una bandera indica que las cabeceras se están leyendo.

Una vez que las cabeceras se hayan leído, si la petición es de tipo POST (lo cual indica que hay más datos disponibles en el flujo de entrada), la cabecera Content-Length: se utiliza para establecer un terminador numérico para leer la cantidad de datos correcta en el canal.

El método handle_request() se llama una vez todas las entradas relevantes han sido serializadas (marshalled), tras establecer el terminador del canal a None para asegurarse de que cualquier dato extraño enviado por el cliente web es ignorado.

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