asynchat — Обработчик команд/ответов асинхронного сокета

Не рекомендуется, начиная с версии 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 из producers. У производителя должен быть только один метод, 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)

Вызывается с 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: используется для установки числового признака конца для чтения нужного количества данных из канала.

Метод 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()