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