1 # -*- Mode: Python; tab-width: 4 -*-
2 # Id: asynchat.py,v 2.26 2000/09/07 22:29:26 rushing Exp
3 # Author: Sam Rushing <rushing@nightmare.com>
5 # ======================================================================
6 # Copyright 1996 by Sam Rushing
10 # Permission to use, copy, modify, and distribute this software and
11 # its documentation for any purpose and without fee is hereby
12 # granted, provided that the above copyright notice appear in all
13 # copies and that both that copyright notice and this permission
14 # notice appear in supporting documentation, and that the name of Sam
15 # Rushing not be used in advertising or publicity pertaining to
16 # distribution of the software without specific, written prior
19 # SAM RUSHING DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
20 # INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
21 # NO EVENT SHALL SAM RUSHING BE LIABLE FOR ANY SPECIAL, INDIRECT OR
22 # CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
23 # OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
24 # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
25 # CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
26 # ======================================================================
28 r
"""A class supporting chat-style (command/response) protocols.
30 This class adds support for 'chat' style protocols - where one side
31 sends a 'command', and the other sends a response (examples would be
32 the common internet protocols - smtp, nntp, ftp, etc..).
34 The handle_read() method looks at the input stream for the current
35 'terminator' (usually '\r\n' for single-line responses, '\r\n.\r\n'
36 for multi-line output), calling self.found_terminator() on its
40 Say you build an async nntp client using this class. At the start
41 of the connection, you'll have self.terminator set to '\r\n', in
42 order to process the single-line greeting. Just before issuing a
43 'LIST' command you'll set it to '\r\n.\r\n'. The output of the LIST
44 command will be accumulated (using your own 'collect_incoming_data'
45 method) up to the terminator, and then control will be returned to
46 you - by calling your self.found_terminator() method.
51 from collections
import deque
52 from sys
import py3kwarning
53 from warnings
import filterwarnings
, catch_warnings
55 class async_chat (asyncore
.dispatcher
):
56 """This is an abstract class. You must derive from this class, and add
57 the two methods collect_incoming_data() and found_terminator()"""
59 # these are overridable defaults
61 ac_in_buffer_size
= 4096
62 ac_out_buffer_size
= 4096
64 def __init__ (self
, sock
=None, map=None):
65 # for string terminator matching
66 self
.ac_in_buffer
= ''
68 # we use a list here rather than cStringIO for a few reasons...
69 # del lst[:] is faster than sio.truncate(0)
70 # lst = [] is faster than sio.truncate(0)
71 # cStringIO will be gaining unicode support in py3k, which
72 # will negatively affect the performance of bytes compared to
73 # a ''.join() equivalent
76 # we toss the use of the "simple producer" and replace it with
77 # a pure deque, which the original fifo was a wrapping of
78 self
.producer_fifo
= deque()
79 asyncore
.dispatcher
.__init
__ (self
, sock
, map)
81 def collect_incoming_data(self
, data
):
82 raise NotImplementedError("must be implemented in subclass")
84 def _collect_incoming_data(self
, data
):
85 self
.incoming
.append(data
)
88 d
= ''.join(self
.incoming
)
92 def found_terminator(self
):
93 raise NotImplementedError("must be implemented in subclass")
95 def set_terminator (self
, term
):
96 "Set the input delimiter. Can be a fixed string of any length, an integer, or None"
97 self
.terminator
= term
99 def get_terminator (self
):
100 return self
.terminator
102 # grab some more data from the socket,
103 # throw it to the collector method,
104 # check for the terminator,
105 # if found, transition to the next state.
107 def handle_read (self
):
110 data
= self
.recv (self
.ac_in_buffer_size
)
111 except socket
.error
, why
:
115 self
.ac_in_buffer
= self
.ac_in_buffer
+ data
117 # Continue to search for self.terminator in self.ac_in_buffer,
118 # while calling self.collect_incoming_data. The while loop
119 # is necessary because we might read several data+terminator
120 # combos with a single recv(4096).
122 while self
.ac_in_buffer
:
123 lb
= len(self
.ac_in_buffer
)
124 terminator
= self
.get_terminator()
126 # no terminator, collect it all
127 self
.collect_incoming_data (self
.ac_in_buffer
)
128 self
.ac_in_buffer
= ''
129 elif isinstance(terminator
, int) or isinstance(terminator
, long):
133 self
.collect_incoming_data (self
.ac_in_buffer
)
134 self
.ac_in_buffer
= ''
135 self
.terminator
= self
.terminator
- lb
137 self
.collect_incoming_data (self
.ac_in_buffer
[:n
])
138 self
.ac_in_buffer
= self
.ac_in_buffer
[n
:]
140 self
.found_terminator()
143 # 1) end of buffer matches terminator exactly:
144 # collect data, transition
145 # 2) end of buffer matches some prefix:
146 # collect data to the prefix
147 # 3) end of buffer does not match any prefix:
149 terminator_len
= len(terminator
)
150 index
= self
.ac_in_buffer
.find(terminator
)
152 # we found the terminator
154 # don't bother reporting the empty string (source of subtle bugs)
155 self
.collect_incoming_data (self
.ac_in_buffer
[:index
])
156 self
.ac_in_buffer
= self
.ac_in_buffer
[index
+terminator_len
:]
157 # This does the Right Thing if the terminator is changed here.
158 self
.found_terminator()
160 # check for a prefix of the terminator
161 index
= find_prefix_at_end (self
.ac_in_buffer
, terminator
)
164 # we found a prefix, collect up to the prefix
165 self
.collect_incoming_data (self
.ac_in_buffer
[:-index
])
166 self
.ac_in_buffer
= self
.ac_in_buffer
[-index
:]
169 # no prefix, collect it all
170 self
.collect_incoming_data (self
.ac_in_buffer
)
171 self
.ac_in_buffer
= ''
173 def handle_write (self
):
176 def handle_close (self
):
179 def push (self
, data
):
180 sabs
= self
.ac_out_buffer_size
182 for i
in xrange(0, len(data
), sabs
):
183 self
.producer_fifo
.append(data
[i
:i
+sabs
])
185 self
.producer_fifo
.append(data
)
188 def push_with_producer (self
, producer
):
189 self
.producer_fifo
.append(producer
)
193 "predicate for inclusion in the readable for select()"
194 # cannot use the old predicate, it violates the claim of the
195 # set_terminator method.
197 # return (len(self.ac_in_buffer) <= self.ac_in_buffer_size)
201 "predicate for inclusion in the writable for select()"
202 return self
.producer_fifo
or (not self
.connected
)
204 def close_when_done (self
):
205 "automatically close this channel once the outgoing queue is empty"
206 self
.producer_fifo
.append(None)
208 def initiate_send(self
):
209 while self
.producer_fifo
and self
.connected
:
210 first
= self
.producer_fifo
[0]
211 # handle empty string/buffer or None entry
213 del self
.producer_fifo
[0]
218 # handle classic producer behavior
219 obs
= self
.ac_out_buffer_size
221 with
catch_warnings():
223 filterwarnings("ignore", ".*buffer", DeprecationWarning)
224 data
= buffer(first
, 0, obs
)
228 self
.producer_fifo
.appendleft(data
)
230 del self
.producer_fifo
[0]
235 num_sent
= self
.send(data
)
241 if num_sent
< len(data
) or obs
< len(first
):
242 self
.producer_fifo
[0] = first
[num_sent
:]
244 del self
.producer_fifo
[0]
245 # we tried to send some actual data
248 def discard_buffers (self
):
250 self
.ac_in_buffer
= ''
252 self
.producer_fifo
.clear()
254 class simple_producer
:
256 def __init__ (self
, data
, buffer_size
=512):
258 self
.buffer_size
= buffer_size
261 if len (self
.data
) > self
.buffer_size
:
262 result
= self
.data
[:self
.buffer_size
]
263 self
.data
= self
.data
[self
.buffer_size
:]
271 def __init__ (self
, list=None):
275 self
.list = deque(list)
278 return len(self
.list)
286 def push (self
, data
):
287 self
.list.append(data
)
291 return (1, self
.list.popleft())
295 # Given 'haystack', see if any prefix of 'needle' is at its end. This
296 # assumes an exact match has already been checked. Return the number of
297 # characters matched.
299 # f_p_a_e ("qwerty\r", "\r\n") => 1
300 # f_p_a_e ("qwertydkjf", "\r\n") => 0
301 # f_p_a_e ("qwerty\r\n", "\r\n") => <undefined>
303 # this could maybe be made faster with a computed regex?
304 # [answer: no; circa Python-2.0, Jan 2001]
305 # new python: 28961/s
306 # old python: 18307/s
310 def find_prefix_at_end (haystack
, needle
):
312 while l
and not haystack
.endswith(needle
[:l
]):