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

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

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


Этот модуль построен на инфраструктуре 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)

Вызывается с 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().

17.6.1. asynchat – Вспомогательные классыasynchat - Auxiliary Classes

class asynchat.fifo(list=None)

A fifo holding data which has been pushed by the application but not yet popped for writing to the channel. A fifo is a list used to hold data and/or producers until they are required. If the list argument is provided then it should contain producers or data items to be written to the channel.

is_empty()

Возвращает True, если и только если fifo пуст.

first()

Возвращает самый старый элемент из fifo, добавленный с помощью push().

push(data)

Добавляет переданные данные (строку или объект-производитель) в producer fifo.

pop()

Если fifo не пуст, возвращает True, first(), удаляя извлечённый элемент. Если fifo пуст, возвращает False, None.

17.6.2. Пример использования asynchatasynchat Example

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

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

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

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