Vega strike Python Modules doc  0.5.1
Documentation of the " Modules " folder of Vega strike
 All Data Structures Namespaces Files Functions Variables
asynchat.py
Go to the documentation of this file.
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>
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 
28 r"""A class supporting chat-style (command/response) protocols.
29 
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..).
33 
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
37 receipt.
38 
39 for example:
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.
47 """
48 
49 import socket
50 import asyncore
51 
53  """This is an abstract class. You must derive from this class, and add
54  the two methods collect_incoming_data() and found_terminator()"""
55 
56  # these are overridable defaults
57 
58  ac_in_buffer_size = 4096
59  ac_out_buffer_size = 4096
60 
61  def __init__ (self, conn=None):
62  self.ac_in_buffer = ''
63  self.ac_out_buffer = ''
65  asyncore.dispatcher.__init__ (self, conn)
66 
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
70 
71  def get_terminator (self):
72  return self.terminator
73 
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.
78 
79  def handle_read (self):
80 
81  try:
82  data = self.recv (self.ac_in_buffer_size)
83  except socket.error, why:
84  self.handle_error()
85  return
86 
87  self.ac_in_buffer = self.ac_in_buffer + data
88 
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).
93 
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):
102  # numeric terminator
103  n = terminator
104  if lb < n:
105  self.collect_incoming_data (self.ac_in_buffer)
106  self.ac_in_buffer = ''
107  self.terminator = self.terminator - lb
108  else:
109  self.collect_incoming_data (self.ac_in_buffer[:n])
110  self.ac_in_buffer = self.ac_in_buffer[n:]
111  self.terminator = 0
112  self.found_terminator()
113  else:
114  # 3 cases:
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:
120  # collect data
121  terminator_len = len(terminator)
122  index = self.ac_in_buffer.find(terminator)
123  if index != -1:
124  # we found the terminator
125  if index > 0:
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()
131  else:
132  # check for a prefix of the terminator
133  index = find_prefix_at_end (self.ac_in_buffer, terminator)
134  if index:
135  if index != lb:
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:]
139  break
140  else:
141  # no prefix, collect it all
142  self.collect_incoming_data (self.ac_in_buffer)
143  self.ac_in_buffer = ''
144 
145  def handle_write (self):
146  self.initiate_send ()
147 
148  def handle_close (self):
149  self.close()
150 
151  def push (self, data):
152  self.producer_fifo.push (simple_producer (data))
153  self.initiate_send()
154 
155  def push_with_producer (self, producer):
156  self.producer_fifo.push (producer)
157  self.initiate_send()
158 
159  def readable (self):
160  "predicate for inclusion in the readable for select()"
161  return (len(self.ac_in_buffer) <= self.ac_in_buffer_size)
162 
163  def writable (self):
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.
167  return not (
168  (self.ac_out_buffer == '') and
169  self.producer_fifo.is_empty() and
170  self.connected
171  )
172 
173  def close_when_done (self):
174  "automatically close this channel once the outgoing queue is empty"
175  self.producer_fifo.push (None)
176 
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('')
181  while 1:
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.
186  if p is None:
187  if not self.ac_out_buffer:
188  self.producer_fifo.pop()
189  self.close()
190  return
191  elif type(p) is _string_type:
192  self.producer_fifo.pop()
193  self.ac_out_buffer = self.ac_out_buffer + p
194  return
195  data = p.more()
196  if data:
197  self.ac_out_buffer = self.ac_out_buffer + data
198  return
199  else:
200  self.producer_fifo.pop()
201  else:
202  return
203 
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):
208  self.refill_buffer()
209 
210  if self.ac_out_buffer and self.connected:
211  # try to send the buffer
212  try:
213  num_sent = self.send (self.ac_out_buffer[:obs])
214  if num_sent:
215  self.ac_out_buffer = self.ac_out_buffer[num_sent:]
216 
217  except socket.error, why:
218  self.handle_error()
219  return
220 
221  def discard_buffers (self):
222  # Emergencies only!
223  self.ac_in_buffer = ''
224  self.ac_out_buffer = ''
225  while self.producer_fifo:
226  self.producer_fifo.pop()
227 
228 
230 
231  def __init__ (self, data, buffer_size=512):
232  self.data = data
233  self.buffer_size = buffer_size
234 
235  def more (self):
236  if len (self.data) > self.buffer_size:
237  result = self.data[:self.buffer_size]
238  self.data = self.data[self.buffer_size:]
239  return result
240  else:
241  result = self.data
242  self.data = ''
243  return result
244 
245 class fifo:
246  def __init__ (self, list=None):
247  if not list:
248  self.list = []
249  else:
250  self.list = list
251 
252  def __len__ (self):
253  return len(self.list)
254 
255  def is_empty (self):
256  return self.list == []
257 
258  def first (self):
259  return self.list[0]
260 
261  def push (self, data):
262  self.list.append (data)
263 
264  def pop (self):
265  if self.list:
266  result = self.list[0]
267  del self.list[0]
268  return (1, result)
269  else:
270  return (0, None)
271 
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.
275 # for example:
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
279 
280 # this could maybe be made faster with a computed regex?
281 # [answer: no; circa Python-2.0, Jan 2001]
282 # python: 18307/s
283 # re: 12820/s
284 # regex: 14035/s
285 
286 def find_prefix_at_end (haystack, needle):
287  nl = len(needle)
288  result = 0
289  for i in range (1,nl):
290  if haystack[-(nl-i):] == needle[:(nl-i)]:
291  result = nl-i
292  break
293  return result