Документация Python неофициальный перевод
Содержание страницы

18.7. asynchat – асинхронный обработчик команд/ответов сокетовasynchat – Asynchronous socket command/response handler

Исходный код: Lib/asynchat.py


Примечание

Этот модуль существует только для обратной совместимости. Для нового кода рекомендуется использовать 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() пустого объекта bytes. В этот момент объект 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)

Вызывается с параметром 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 может быть одним из трёх типов значений, соответствующих трём различным способам обработки входящих протокольных данных.

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

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

18.7.1. Пример asynchatasynchat Example

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

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

Метод 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):
        """Буферизировать данные"""
        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)  # браузеры иногда отправляют лишние данные
            self.cgi_data = parse(self.headers, b"".join(self.ibuffer))
            self.handling = True
            self.ibuffer = []
            self.handle_request()