blob: e725a2b12ad856fe8a357465b555421c30994d5a [file] [log] [blame]
Guido van Rossum0039d7b1999-01-12 20:19:27 +00001# -*- Mode: Python; tab-width: 4 -*-
Guido van Rossum0079b281999-09-14 20:17:50 +00002# Id: asynchat.py,v 2.23 1999/05/01 04:49:24 rushing Exp
Guido van Rossum0039d7b1999-01-12 20:19:27 +00003# Author: Sam Rushing <rushing@nightmare.com>
4
5# ======================================================================
6# Copyright 1996 by Sam Rushing
7#
8# All Rights Reserved
9#
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
17# permission.
18#
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# ======================================================================
27
28import socket
29import asyncore
30import string
31
32# This class adds support for 'chat' style protocols - where one side
33# sends a 'command', and the other sends a response (examples would be
34# the common internet protocols - smtp, nntp, ftp, etc..).
35
36# The handle_read() method looks at the input stream for the current
37# 'terminator' (usually '\r\n' for single-line responses, '\r\n.\r\n'
38# for multi-line output), calling self.found_terminator() on its
39# receipt.
40
41# for example:
42# Say you build an async nntp client using this class. At the start
43# of the connection, you'll have self.terminator set to '\r\n', in
44# order to process the single-line greeting. Just before issuing a
45# 'LIST' command you'll set it to '\r\n.\r\n'. The output of the LIST
46# command will be accumulated (using your own 'collect_incoming_data'
47# method) up to the terminator, and then control will be returned to
48# you - by calling your self.found_terminator() method
49
50class async_chat (asyncore.dispatcher):
51 """This is an abstract class. You must derive from this class, and add
52 the two methods collect_incoming_data() and found_terminator()"""
53
54 # these are overridable defaults
55
56 ac_in_buffer_size = 4096
57 ac_out_buffer_size = 4096
58
59 def __init__ (self, conn=None):
60 self.ac_in_buffer = ''
61 self.ac_out_buffer = ''
62 self.producer_fifo = fifo()
63 asyncore.dispatcher.__init__ (self, conn)
64
65 def set_terminator (self, term):
Guido van Rossuma8d0f4f1999-06-08 13:20:05 +000066 "Set the input delimiter. Can be a fixed string of any length, an integer, or None"
67 self.terminator = term
Guido van Rossum0039d7b1999-01-12 20:19:27 +000068
69 def get_terminator (self):
70 return self.terminator
71
72 # grab some more data from the socket,
73 # throw it to the collector method,
74 # check for the terminator,
75 # if found, transition to the next state.
76
77 def handle_read (self):
78
79 try:
80 data = self.recv (self.ac_in_buffer_size)
81 except socket.error, why:
Guido van Rossuma8d0f4f1999-06-08 13:20:05 +000082 self.handle_error()
Guido van Rossum0039d7b1999-01-12 20:19:27 +000083 return
84
85 self.ac_in_buffer = self.ac_in_buffer + data
86
87 # Continue to search for self.terminator in self.ac_in_buffer,
88 # while calling self.collect_incoming_data. The while loop
89 # is necessary because we might read several data+terminator
90 # combos with a single recv(1024).
91
92 while self.ac_in_buffer:
Guido van Rossuma8d0f4f1999-06-08 13:20:05 +000093 lb = len(self.ac_in_buffer)
Guido van Rossum0039d7b1999-01-12 20:19:27 +000094 terminator = self.get_terminator()
Guido van Rossuma8d0f4f1999-06-08 13:20:05 +000095 if terminator is None:
96 # no terminator, collect it all
97 self.collect_incoming_data (self.ac_in_buffer)
98 self.ac_in_buffer = ''
99 elif type(terminator) == type(0):
100 # numeric terminator
101 n = terminator
102 if lb < n:
103 self.collect_incoming_data (self.ac_in_buffer)
104 self.ac_in_buffer = ''
105 self.terminator = self.terminator - lb
106 else:
107 self.collect_incoming_data (self.ac_in_buffer[:n])
108 self.ac_in_buffer = self.ac_in_buffer[n:]
109 self.terminator = 0
110 self.found_terminator()
111 else:
112 # 3 cases:
113 # 1) end of buffer matches terminator exactly:
114 # collect data, transition
115 # 2) end of buffer matches some prefix:
116 # collect data to the prefix
117 # 3) end of buffer does not match any prefix:
118 # collect data
119 terminator_len = len(terminator)
Guido van Rossum0039d7b1999-01-12 20:19:27 +0000120 index = string.find (self.ac_in_buffer, terminator)
121 if index != -1:
122 # we found the terminator
123 self.collect_incoming_data (self.ac_in_buffer[:index])
124 self.ac_in_buffer = self.ac_in_buffer[index+terminator_len:]
125 # This does the Right Thing if the terminator is changed here.
126 self.found_terminator()
127 else:
128 # check for a prefix of the terminator
129 index = find_prefix_at_end (self.ac_in_buffer, terminator)
130 if index:
Guido van Rossuma8d0f4f1999-06-08 13:20:05 +0000131 if index != lb:
132 # we found a prefix, collect up to the prefix
133 self.collect_incoming_data (self.ac_in_buffer[:-index])
134 self.ac_in_buffer = self.ac_in_buffer[-index:]
Guido van Rossum0039d7b1999-01-12 20:19:27 +0000135 break
136 else:
137 # no prefix, collect it all
138 self.collect_incoming_data (self.ac_in_buffer)
139 self.ac_in_buffer = ''
Guido van Rossum0039d7b1999-01-12 20:19:27 +0000140
141 def handle_write (self):
142 self.initiate_send ()
143
144 def handle_close (self):
145 self.close()
146
147 def push (self, data):
148 self.producer_fifo.push (simple_producer (data))
149 self.initiate_send()
150
151 def push_with_producer (self, producer):
152 self.producer_fifo.push (producer)
153 self.initiate_send()
154
155 def readable (self):
Guido van Rossuma8d0f4f1999-06-08 13:20:05 +0000156 "predicate for inclusion in the readable for select()"
Guido van Rossum0039d7b1999-01-12 20:19:27 +0000157 return (len(self.ac_in_buffer) <= self.ac_in_buffer_size)
158
159 def writable (self):
Guido van Rossuma8d0f4f1999-06-08 13:20:05 +0000160 "predicate for inclusion in the writable for select()"
161 # return len(self.ac_out_buffer) or len(self.producer_fifo) or (not self.connected)
162 # this is about twice as fast, though not as clear.
163 return not (
164 (self.ac_out_buffer is '') and
165 self.producer_fifo.is_empty() and
166 self.connected
167 )
Guido van Rossum0039d7b1999-01-12 20:19:27 +0000168
169 def close_when_done (self):
Guido van Rossuma8d0f4f1999-06-08 13:20:05 +0000170 "automatically close this channel once the outgoing queue is empty"
Guido van Rossum0039d7b1999-01-12 20:19:27 +0000171 self.producer_fifo.push (None)
172
173 # refill the outgoing buffer by calling the more() method
174 # of the first producer in the queue
175 def refill_buffer (self):
Guido van Rossuma8d0f4f1999-06-08 13:20:05 +0000176 _string_type = type('')
Guido van Rossum0039d7b1999-01-12 20:19:27 +0000177 while 1:
178 if len(self.producer_fifo):
179 p = self.producer_fifo.first()
180 # a 'None' in the producer fifo is a sentinel,
181 # telling us to close the channel.
182 if p is None:
183 if not self.ac_out_buffer:
184 self.producer_fifo.pop()
185 self.close()
186 return
Guido van Rossuma8d0f4f1999-06-08 13:20:05 +0000187 elif type(p) is _string_type:
188 self.producer_fifo.pop()
189 self.ac_out_buffer = self.ac_out_buffer + p
190 return
Guido van Rossum0039d7b1999-01-12 20:19:27 +0000191 data = p.more()
192 if data:
193 self.ac_out_buffer = self.ac_out_buffer + data
194 return
195 else:
196 self.producer_fifo.pop()
197 else:
198 return
199
200 def initiate_send (self):
201 obs = self.ac_out_buffer_size
202 # try to refill the buffer
Guido van Rossuma8d0f4f1999-06-08 13:20:05 +0000203 if (len (self.ac_out_buffer) < obs):
Guido van Rossum0039d7b1999-01-12 20:19:27 +0000204 self.refill_buffer()
205
206 if self.ac_out_buffer and self.connected:
207 # try to send the buffer
Guido van Rossuma8d0f4f1999-06-08 13:20:05 +0000208 try:
209 num_sent = self.send (self.ac_out_buffer[:obs])
210 if num_sent:
211 self.ac_out_buffer = self.ac_out_buffer[num_sent:]
212
213 except socket.error, why:
214 self.handle_error()
215 return
Guido van Rossum0039d7b1999-01-12 20:19:27 +0000216
217 def discard_buffers (self):
218 # Emergencies only!
219 self.ac_in_buffer = ''
220 self.ac_out_buffer == ''
221 while self.producer_fifo:
222 self.producer_fifo.pop()
223
Guido van Rossum0039d7b1999-01-12 20:19:27 +0000224class simple_producer:
Guido van Rossuma8d0f4f1999-06-08 13:20:05 +0000225
Guido van Rossum0039d7b1999-01-12 20:19:27 +0000226 def __init__ (self, data, buffer_size=512):
227 self.data = data
228 self.buffer_size = buffer_size
229
230 def more (self):
231 if len (self.data) > self.buffer_size:
232 result = self.data[:self.buffer_size]
233 self.data = self.data[self.buffer_size:]
234 return result
235 else:
236 result = self.data
237 self.data = ''
238 return result
239
240class fifo:
241 def __init__ (self, list=None):
242 if not list:
243 self.list = []
244 else:
245 self.list = list
246
247 def __len__ (self):
248 return len(self.list)
249
Guido van Rossuma8d0f4f1999-06-08 13:20:05 +0000250 def is_empty (self):
251 return self.list == []
252
Guido van Rossum0039d7b1999-01-12 20:19:27 +0000253 def first (self):
254 return self.list[0]
255
256 def push (self, data):
257 self.list.append (data)
258
259 def pop (self):
260 if self.list:
261 result = self.list[0]
262 del self.list[0]
263 return (1, result)
264 else:
265 return (0, None)
266
267# Given 'haystack', see if any prefix of 'needle' is at its end. This
268# assumes an exact match has already been checked. Return the number of
269# characters matched.
270# for example:
271# f_p_a_e ("qwerty\r", "\r\n") => 1
272# f_p_a_e ("qwerty\r\n", "\r\n") => 2
273# f_p_a_e ("qwertydkjf", "\r\n") => 0
274
275# this could maybe be made faster with a computed regex?
276
277##def find_prefix_at_end (haystack, needle):
278## nl = len(needle)
279## result = 0
280## for i in range (1,nl):
281## if haystack[-(nl-i):] == needle[:(nl-i)]:
282## result = nl-i
283## break
284## return result
285
286# yes, this is about twice as fast, but still seems
287# to be neglible CPU. The previous could do about 290
288# searches/sec. the new one about 555/sec.
289
290import regex
291
292prefix_cache = {}
293
294def prefix_regex (needle):
295 if prefix_cache.has_key (needle):
296 return prefix_cache[needle]
297 else:
298 reg = needle[-1]
299 for i in range(1,len(needle)):
300 reg = '%c\(%s\)?' % (needle[-(i+1)], reg)
301 reg = regex.compile (reg+'$')
302 prefix_cache[needle] = reg, len(needle)
303 return reg, len(needle)
304
305def find_prefix_at_end (haystack, needle):
306 reg, length = prefix_regex (needle)
307 lh = len(haystack)
308 result = reg.search (haystack, max(0,lh-length))
309 if result >= 0:
310 return (lh - result)
311 else:
312 return 0