Revert "bpo-28533: Remove asyncore, asynchat, smtpd modules (GH-29521… · python/cpython@cf7eaa4
1+:mod:`asynchat` --- Asynchronous socket command/response handler
2+================================================================
3+4+.. module:: asynchat
5+:synopsis: Support for asynchronous command/response protocols.
6+7+.. moduleauthor:: Sam Rushing <rushing@nightmare.com>
8+.. sectionauthor:: Steve Holden <sholden@holdenweb.com>
9+10+**Source code:** :source:`Lib/asynchat.py`
11+12+.. deprecated:: 3.6
13+ Please use :mod:`asyncio` instead.
14+15+--------------
16+17+.. note::
18+19+ This module exists for backwards compatibility only. For new code we
20+ recommend using :mod:`asyncio`.
21+22+This module builds on the :mod:`asyncore` infrastructure, simplifying
23+asynchronous clients and servers and making it easier to handle protocols
24+whose elements are terminated by arbitrary strings, or are of variable length.
25+:mod:`asynchat` defines the abstract class :class:`async_chat` that you
26+subclass, providing implementations of the :meth:`collect_incoming_data` and
27+:meth:`found_terminator` methods. It uses the same asynchronous loop as
28+:mod:`asyncore`, and the two types of channel, :class:`asyncore.dispatcher`
29+and :class:`asynchat.async_chat`, can freely be mixed in the channel map.
30+Typically an :class:`asyncore.dispatcher` server channel generates new
31+:class:`asynchat.async_chat` channel objects as it receives incoming
32+connection requests.
33+34+35+.. class:: async_chat()
36+37+ This class is an abstract subclass of :class:`asyncore.dispatcher`. To make
38+ practical use of the code you must subclass :class:`async_chat`, providing
39+ meaningful :meth:`collect_incoming_data` and :meth:`found_terminator`
40+ methods.
41+ The :class:`asyncore.dispatcher` methods can be used, although not all make
42+ sense in a message/response context.
43+44+ Like :class:`asyncore.dispatcher`, :class:`async_chat` defines a set of
45+ events that are generated by an analysis of socket conditions after a
46+:c:func:`select` call. Once the polling loop has been started the
47+:class:`async_chat` object's methods are called by the event-processing
48+ framework with no action on the part of the programmer.
49+50+ Two class attributes can be modified, to improve performance, or possibly
51+ even to conserve memory.
52+53+54+ .. data:: ac_in_buffer_size
55+56+ The asynchronous input buffer size (default ``4096``).
57+58+59+ .. data:: ac_out_buffer_size
60+61+ The asynchronous output buffer size (default ``4096``).
62+63+ Unlike :class:`asyncore.dispatcher`, :class:`async_chat` allows you to
64+ define a :abbr:`FIFO (first-in, first-out)` queue of *producers*. A producer need
65+ have only one method, :meth:`more`, which should return data to be
66+ transmitted on the channel.
67+ The producer indicates exhaustion (*i.e.* that it contains no more data) by
68+ having its :meth:`more` method return the empty bytes object. At this point
69+ the :class:`async_chat` object removes the producer from the queue and starts
70+ using the next producer, if any. When the producer queue is empty the
71+:meth:`handle_write` method does nothing. You use the channel object's
72+:meth:`set_terminator` method to describe how to recognize the end of, or
73+ an important breakpoint in, an incoming transmission from the remote
74+ endpoint.
75+76+ To build a functioning :class:`async_chat` subclass your input methods
77+:meth:`collect_incoming_data` and :meth:`found_terminator` must handle the
78+ data that the channel receives asynchronously. The methods are described
79+ below.
80+81+82+.. method:: async_chat.close_when_done()
83+84+ Pushes a ``None`` on to the producer queue. When this producer is popped off
85+ the queue it causes the channel to be closed.
86+87+88+.. method:: async_chat.collect_incoming_data(data)
89+90+ Called with *data* holding an arbitrary amount of received data. The
91+ default method, which must be overridden, raises a
92+:exc:`NotImplementedError` exception.
93+94+95+.. method:: async_chat.discard_buffers()
96+97+ In emergencies this method will discard any data held in the input and/or
98+ output buffers and the producer queue.
99+100+101+.. method:: async_chat.found_terminator()
102+103+ Called when the incoming data stream matches the termination condition set
104+ by :meth:`set_terminator`. The default method, which must be overridden,
105+ raises a :exc:`NotImplementedError` exception. The buffered input data
106+ should be available via an instance attribute.
107+108+109+.. method:: async_chat.get_terminator()
110+111+ Returns the current terminator for the channel.
112+113+114+.. method:: async_chat.push(data)
115+116+ Pushes data on to the channel's queue to ensure its transmission.
117+ This is all you need to do to have the channel write the data out to the
118+ network, although it is possible to use your own producers in more complex
119+ schemes to implement encryption and chunking, for example.
120+121+122+.. method:: async_chat.push_with_producer(producer)
123+124+ Takes a producer object and adds it to the producer queue associated with
125+ the channel. When all currently-pushed producers have been exhausted the
126+ channel will consume this producer's data by calling its :meth:`more`
127+ method and send the data to the remote endpoint.
128+129+130+.. method:: async_chat.set_terminator(term)
131+132+ Sets the terminating condition to be recognized on the channel. ``term``
133+ may be any of three types of value, corresponding to three different ways
134+ to handle incoming protocol data.
135+136++-----------+---------------------------------------------+
137+ | term | Description |
138++===========+=============================================+
139+ | *string* | Will call :meth:`found_terminator` when the |
140+ | | string is found in the input stream |
141++-----------+---------------------------------------------+
142+ | *integer* | Will call :meth:`found_terminator` when the |
143+ | | indicated number of characters have been |
144+ | | received |
145++-----------+---------------------------------------------+
146+ | ``None`` | The channel continues to collect data |
147+ | | forever |
148++-----------+---------------------------------------------+
149+150+ Note that any data following the terminator will be available for reading
151+ by the channel after :meth:`found_terminator` is called.
152+153+154+.. _asynchat-example:
155+156+asynchat Example
157+----------------
158+159+The following partial example shows how HTTP requests can be read with
160+:class:`async_chat`. A web server might create an
161+:class:`http_request_handler` object for each incoming client connection.
162+Notice that initially the channel terminator is set to match the blank line at
163+the end of the HTTP headers, and a flag indicates that the headers are being
164+read.
165+166+Once the headers have been read, if the request is of type POST (indicating
167+that further data are present in the input stream) then the
168+``Content-Length:`` header is used to set a numeric terminator to read the
169+right amount of data from the channel.
170+171+The :meth:`handle_request` method is called once all relevant input has been
172+marshalled, after setting the channel terminator to ``None`` to ensure that
173+any extraneous data sent by the web client are ignored. ::
174+175+176+ import asynchat
177+178+ class http_request_handler(asynchat.async_chat):
179+180+ def __init__(self, sock, addr, sessions, log):
181+ asynchat.async_chat.__init__(self, sock=sock)
182+ self.addr = addr
183+ self.sessions = sessions
184+ self.ibuffer = []
185+ self.obuffer = b""
186+ self.set_terminator(b"\r\n\r\n")
187+ self.reading_headers = True
188+ self.handling = False
189+ self.cgi_data = None
190+ self.log = log
191+192+ def collect_incoming_data(self, data):
193+ """Buffer the data"""
194+ self.ibuffer.append(data)
195+196+ def found_terminator(self):
197+ if self.reading_headers:
198+ self.reading_headers = False
199+ self.parse_headers(b"".join(self.ibuffer))
200+ self.ibuffer = []
201+ if self.op.upper() == b"POST":
202+ clen = self.headers.getheader("content-length")
203+ self.set_terminator(int(clen))
204+ else:
205+ self.handling = True
206+ self.set_terminator(None)
207+ self.handle_request()
208+ elif not self.handling:
209+ self.set_terminator(None) # browsers sometimes over-send
210+ self.cgi_data = parse(self.headers, b"".join(self.ibuffer))
211+ self.handling = True
212+ self.ibuffer = []
213+ self.handle_request()