• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
22This module builds on the :mod:`asyncore` infrastructure, simplifying
23asynchronous clients and servers and making it easier to handle protocols
24whose elements are terminated by arbitrary strings, or are of variable length.
25:mod:`asynchat` defines the abstract class :class:`async_chat` that you
26subclass, 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`
29and :class:`asynchat.async_chat`, can freely be mixed in the channel map.
30Typically an :class:`asyncore.dispatcher` server channel generates new
31:class:`asynchat.async_chat` channel objects as it receives incoming
32connection 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
156asynchat Example
157----------------
158
159The 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.
162Notice that initially the channel terminator is set to match the blank line at
163the end of the HTTP headers, and a flag indicates that the headers are being
164read.
165
166Once the headers have been read, if the request is of type POST (indicating
167that further data are present in the input stream) then the
168``Content-Length:`` header is used to set a numeric terminator to read the
169right amount of data from the channel.
170
171The :meth:`handle_request` method is called once all relevant input has been
172marshalled, after setting the channel terminator to ``None`` to ensure that
173any 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()
214