"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 (first-in, first-out) 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()
