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