Skip to content

Commit cf7eaa4

Browse files
authored
Revert "bpo-28533: Remove asyncore, asynchat, smtpd modules (GH-29521)" (GH-29951)
This reverts commit 9bf2cbc.
1 parent 2bf5517 commit cf7eaa4

28 files changed

+3228
-34
lines changed

.github/CODEOWNERS

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,8 @@ Lib/ast.py @isidentical
130130

131131
**/*typing* @gvanrossum @Fidget-Spinner
132132

133+
**/*asyncore @giampaolo
134+
**/*asynchat @giampaolo
133135
**/*ftplib @giampaolo
134136
**/*shutil @giampaolo
135137

Doc/library/asynchat.rst

Lines changed: 213 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,213 @@
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()

0 commit comments

Comments
 (0)