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.
52 class async_chat (asyncore
.dispatcher
):
53 """This is an abstract class. You must derive from this class, and add
54 the two methods collect_incoming_data() and found_terminator()"""
56 # these are overridable defaults
58 ac_in_buffer_size
= 4096
59 ac_out_buffer_size
= 4096
61 def __init__ (self
, conn
=None):
62 self
.ac_in_buffer
= ''
63 self
.ac_out_buffer
= ''
64 self
.producer_fifo
= fifo()
65 asyncore
.dispatcher
.__init
__ (self
, conn
)
67 def set_terminator (self
, term
):
68 "Set the input delimiter. Can be a fixed string of any length, an integer, or None"
69 self
.terminator
= term
71 def get_terminator (self
):
72 return self
.terminator
74 # grab some more data from the socket,
75 # throw it to the collector method,
76 # check for the terminator,
77 # if found, transition to the next state.
79 def handle_read (self
):
82 data
= self
.recv (self
.ac_in_buffer_size
)
83 except socket
.error
, why
:
87 self
.ac_in_buffer
= self
.ac_in_buffer
+ data
89 # Continue to search for self.terminator in self.ac_in_buffer,
90 # while calling self.collect_incoming_data. The while loop
91 # is necessary because we might read several data+terminator
92 # combos with a single recv(1024).
94 while self
.ac_in_buffer
:
95 lb
= len(self
.ac_in_buffer
)
96 terminator
= self
.get_terminator()
97 if terminator
is None:
98 # no terminator, collect it all
99 self
.collect_incoming_data (self
.ac_in_buffer
)
100 self
.ac_in_buffer
= ''
101 elif type(terminator
) == type(0):
105 self
.collect_incoming_data (self
.ac_in_buffer
)
106 self
.ac_in_buffer
= ''
107 self
.terminator
= self
.terminator
- lb
109 self
.collect_incoming_data (self
.ac_in_buffer
[:n
])
110 self
.ac_in_buffer
= self
.ac_in_buffer
[n
:]
112 self
.found_terminator()
115 # 1) end of buffer matches terminator exactly:
116 # collect data, transition
117 # 2) end of buffer matches some prefix:
118 # collect data to the prefix
119 # 3) end of buffer does not match any prefix:
121 terminator_len
= len(terminator
)
122 index
= self
.ac_in_buffer
.find(terminator
)
124 # we found the terminator
126 # don't bother reporting the empty string (source of subtle bugs)
127 self
.collect_incoming_data (self
.ac_in_buffer
[:index
])
128 self
.ac_in_buffer
= self
.ac_in_buffer
[index
+terminator_len
:]
129 # This does the Right Thing if the terminator is changed here.
130 self
.found_terminator()
132 # check for a prefix of the terminator
133 index
= find_prefix_at_end (self
.ac_in_buffer
, terminator
)
136 # we found a prefix, collect up to the prefix
137 self
.collect_incoming_data (self
.ac_in_buffer
[:-index
])
138 self
.ac_in_buffer
= self
.ac_in_buffer
[-index
:]
141 # no prefix, collect it all
142 self
.collect_incoming_data (self
.ac_in_buffer
)
143 self
.ac_in_buffer
= ''
145 def handle_write (self
):
146 self
.initiate_send ()
148 def handle_close (self
):
151 def push (self
, data
):
152 self
.producer_fifo
.push (simple_producer (data
))
155 def push_with_producer (self
, producer
):
156 self
.producer_fifo
.push (producer
)
160 "predicate for inclusion in the readable for select()"
161 return (len(self
.ac_in_buffer
) <= self
.ac_in_buffer_size
)
164 "predicate for inclusion in the writable for select()"
165 # return len(self.ac_out_buffer) or len(self.producer_fifo) or (not self.connected)
166 # this is about twice as fast, though not as clear.
168 (self
.ac_out_buffer
== '') and
169 self
.producer_fifo
.is_empty() and
173 def close_when_done (self
):
174 "automatically close this channel once the outgoing queue is empty"
175 self
.producer_fifo
.push (None)
177 # refill the outgoing buffer by calling the more() method
178 # of the first producer in the queue
179 def refill_buffer (self
):
180 _string_type
= type('')
182 if len(self
.producer_fifo
):
183 p
= self
.producer_fifo
.first()
184 # a 'None' in the producer fifo is a sentinel,
185 # telling us to close the channel.
187 if not self
.ac_out_buffer
:
188 self
.producer_fifo
.pop()
191 elif type(p
) is _string_type
:
192 self
.producer_fifo
.pop()
193 self
.ac_out_buffer
= self
.ac_out_buffer
+ p
197 self
.ac_out_buffer
= self
.ac_out_buffer
+ data
200 self
.producer_fifo
.pop()
204 def initiate_send (self
):
205 obs
= self
.ac_out_buffer_size
206 # try to refill the buffer
207 if (len (self
.ac_out_buffer
) < obs
):
210 if self
.ac_out_buffer
and self
.connected
:
211 # try to send the buffer
213 num_sent
= self
.send (self
.ac_out_buffer
[:obs
])
215 self
.ac_out_buffer
= self
.ac_out_buffer
[num_sent
:]
217 except socket
.error
, why
:
221 def discard_buffers (self
):
223 self
.ac_in_buffer
= ''
224 self
.ac_out_buffer
= ''
225 while self
.producer_fifo
:
226 self
.producer_fifo
.pop()
229 class simple_producer
:
231 def __init__ (self
, data
, buffer_size
=512):
233 self
.buffer_size
= buffer_size
236 if len (self
.data
) > self
.buffer_size
:
237 result
= self
.data
[:self
.buffer_size
]
238 self
.data
= self
.data
[self
.buffer_size
:]
246 def __init__ (self
, list=None):
253 return len(self
.list)
256 return self
.list == []
261 def push (self
, data
):
262 self
.list.append (data
)
266 result
= self
.list[0]
272 # Given 'haystack', see if any prefix of 'needle' is at its end. This
273 # assumes an exact match has already been checked. Return the number of
274 # characters matched.
276 # f_p_a_e ("qwerty\r", "\r\n") => 1
277 # f_p_a_e ("qwerty\r\n", "\r\n") => 2
278 # f_p_a_e ("qwertydkjf", "\r\n") => 0
280 # this could maybe be made faster with a computed regex?
281 # [answer: no; circa Python-2.0, Jan 2001]
286 def find_prefix_at_end (haystack
, needle
):
289 for i
in range (1,nl
):
290 if haystack
[-(nl
-i
):] == needle
[:(nl
-i
)]: