asynchat — обробник команд/відповідей асинхронного сокета

Вихідний код: Lib/asynchat.py

Застаріло починаючи з версії 3.6: asynchat will be removed in Python 3.12 (see PEP 594 for details). Please use asyncio instead.


Примітка

Цей модуль існує лише для зворотної сумісності. Для нового коду ми рекомендуємо використовувати 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)

Takes a producer object and adds it to the producer queue associated with the channel. When all currently-pushed producers have been exhausted the channel will consume this producer’s data by calling its more() method and send the data to the remote endpoint.

async_chat.set_terminator(term)

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

термін

опис

рядок

Викличе found_terminator(), коли рядок знайдено у вхідному потоці

ціле

Викличе found_terminator(), коли буде отримано вказану кількість символів

Жодного

Канал продовжує збирати дані вічно

Зауважте, що будь-які дані після термінатора будуть доступні для читання каналом після виклику found_terminator().

Приклад асинчат

Наступний частковий приклад показує, як 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):
        """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()