asynchat — Асинхронный сокет обработчика запрос/ответ

Source code: Lib/asynchat.py

Не рекомендуется, начиная с версии 3.6: Пожалуйста используйте вместо него asyncio.


Примечание

Модуль существует только для обратной совместимости. Для нового кода мы рекомендуем использовать asyncio.

Модуль создан на основе asyncore для упрощающения реализации асинхронных клиентов и серверов, а также упрощения обработки протоколов элементы которого заканчиваются произвольной строкой или имеют переменную длину. asynchat определяет абстрактный подкласс async_chat, реализующий методы collect_incoming_data() и found_terminator(). Подобно asyncore он использует тот же асинхронный цикл и два типа каналов asyncore.dispatcher и asynchat.async_chat, которые могут свободно смешиваться в карте каналов. Как правило, серверный канал asyncore.dispatcher создаёт новые asynchat.async_chat объекты канала при приеме входящих запросов.

class asynchat.async_chat

Абстрактный подкласс asyncore.dispatcher. Для практического использования кода необходим подкласс async_chat, предоставляя значимые методы collect_incoming_data() и found_terminator(). Метод asyncore.dispatcher также может использоваться, хотя не всё имеет смысл в контексте запроса/ответа.

Как и asyncore.dispatcher, async_chat определяет набор событий, которые генерируются при анализе условий сокета, после вызова select(). После запуска цикла опроса событий, методы объекта async_chat вызываются фреймворком обработки событий без каких-либо действий со стороны программиста.

Два атрибута класса могут быть изменены для повышения производительности или, возможно, даже для экономии памяти.

ac_in_buffer_size

Размер асинхронного входного буфера (по умолчанию 4096).

ac_out_buffer_size

Размер асинхронного выходного буфера (по умолчанию 4096).

В отличие от asyncore.dispatcher, async_chat позволяет определить очередь производителя FIFO. Производитель должен содержать только один метод, more(), который должен возвращать данные для передачи по каналу. Производитель указывает на исчерпание (другими словами, что он больше не содержит данных), путём возвращения методом more() пустого байтового объекта. В этот момент объект async_chat удаляет производителя из очереди и начинает использовать следующего производителя, если он есть. Когда очередь производителей пуста, метод handle_write() останавливает свою работу. Используется метод set_terminator() объекта канала используемый для распознания конеца или важной точки останова входящей передачи от удаленного узла.

Чтобы создать функционирующий async_chat подкласс, Ваши входные методы collect_incoming_data() и found_terminator() должны обрабатывать данные, которые канал получает асинхронно. Эти методы описаны ниже.

async_chat.close_when_done()

Перемещает None в очередь производителей. Когда производитель выходит из очереди, это приводит к закрытию канала.

async_chat.collect_incoming_data(data)

Вызывается с данными, содержащими произвольное количество полученных данных. Метод по умолчанию, который должен быть переопределен, вызывающий исключение NotImplementedError.

async_chat.discard_buffers()

В чрезвычайных ситуациях метод отбрасывает любые данные, хранящиеся во входном и/или выходном буферах и очереди производителей.

async_chat.found_terminator()

Вызывается, когда входящий поток данных соответствует условию окончания, заданному set_terminator(). Метод по умолчанию, который должен быть переопределен, вызывает исключение NotImplementedError. Буферизованные входные данные должны быть доступны через атрибут сущностьи.

async_chat.get_terminator()

Возвращает текущий признак окончания для канала.

async_chat.push(data)

Перемещает данные в очередь канала для обеспечения его передачи. Это все, что нужно сделать, чтобы канал записывал данные в сеть, хотя можно использовать собственных производителей, например в более сложных схемах для реализации шифрования и чанкинга.

async_chat.push_with_producer(producer)

Принимает объект производителя и добавляет его в очередь производителей, связанную с каналом. После того как все перемещенные в данный момент производители исчерпаны, канал будет использовать данные этого производителя путем вызова метода more() и отправки данных удаленному узлу.

async_chat.set_terminator(term)

Устанавливает условие завершения для распознавания в канале. term может быть любым из трех типов значений, соответствующим тремя различными способами обработки протокола входящих данных .

term Описание
string Вызывает found_terminator() при обнаружении строки во входном потоке
integer Вызов found_terminator() после получения указанного количества символов
None Канал продолжает собирать данные всегда

Следует отметить, что любые данные, следующие за терминатором, будут доступны для считывания каналом после вызова found_terminator().

Пример asynchat

В следующем частичном примере показано, как читать HTTP запросы с помощью async_chat. Веб-сервер может создать объект http_request_handler для каждого входящего клиентского подключения. Обратите внимание, что первоначально признак конца канала должен соответствовать пустой строке в конеце заголовков HTTP и флага указателя того, что заголовки нужно начать читать.

После считывания заголовков, если запрос типа POST (с указанием что дополнительные данные присутствуют во входном потоке), то используется заголовок Content-Length: для установки числового терминатора для чтения из канала правильного объёма данных.

Метод: meth: handle_request вызывается после того, как все соответствующие входные данные были построены, после установки терминатора канала в `` None``, чтобы убедиться в том, что любые посторонние данные, отправленные веб-клиентом, были проигнорированы.:

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