home *** CD-ROM | disk | FTP | other *** search
/ PC World 2001 April / PCWorld_2001-04_cd.bin / Software / TemaCD / webclean / !!!python!!! / BeOpen-Python-2.0.exe / ASYNCHAT.PY < prev    next >
Encoding:
Python Source  |  2000-09-28  |  9.6 KB  |  319 lines

  1. # -*- Mode: Python; tab-width: 4 -*-
  2. #    Id: asynchat.py,v 2.25 1999/11/18 11:01:08 rushing Exp 
  3. #    Author: Sam Rushing <rushing@nightmare.com>
  4.  
  5. # ======================================================================
  6. # Copyright 1996 by Sam Rushing
  7. #                         All Rights Reserved
  8. # Permission to use, copy, modify, and distribute this software and
  9. # its documentation for any purpose and without fee is hereby
  10. # granted, provided that the above copyright notice appear in all
  11. # copies and that both that copyright notice and this permission
  12. # notice appear in supporting documentation, and that the name of Sam
  13. # Rushing not be used in advertising or publicity pertaining to
  14. # distribution of the software without specific, written prior
  15. # permission.
  16. # SAM RUSHING DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
  17. # INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
  18. # NO EVENT SHALL SAM RUSHING BE LIABLE FOR ANY SPECIAL, INDIRECT OR
  19. # CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
  20. # OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
  21. # NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
  22. # CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
  23. # ======================================================================
  24.  
  25. """A class supporting chat-style (command/response) protocols.
  26.  
  27. This class adds support for 'chat' style protocols - where one side
  28. sends a 'command', and the other sends a response (examples would be
  29. the common internet protocols - smtp, nntp, ftp, etc..).
  30.  
  31. The handle_read() method looks at the input stream for the current
  32. 'terminator' (usually '\r\n' for single-line responses, '\r\n.\r\n'
  33. for multi-line output), calling self.found_terminator() on its
  34. receipt.
  35.  
  36. for example:
  37. Say you build an async nntp client using this class.  At the start
  38. of the connection, you'll have self.terminator set to '\r\n', in
  39. order to process the single-line greeting.  Just before issuing a
  40. 'LIST' command you'll set it to '\r\n.\r\n'.  The output of the LIST
  41. command will be accumulated (using your own 'collect_incoming_data'
  42. method) up to the terminator, and then control will be returned to
  43. you - by calling your self.found_terminator() method.
  44. """
  45.  
  46. import socket
  47. import asyncore
  48. import string
  49.  
  50. class 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):
  66.         "Set the input delimiter.  Can be a fixed string of any length, an integer, or None"
  67.         self.terminator = term
  68.  
  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:
  82.             self.handle_error()
  83.             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:
  93.             lb = len(self.ac_in_buffer)
  94.             terminator = self.get_terminator()
  95.             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)
  120.                 index = string.find (self.ac_in_buffer, terminator)
  121.                 if index != -1:
  122.                     # we found the terminator
  123.                     if index > 0:
  124.                         # don't bother reporting the empty string (source of subtle bugs)
  125.                         self.collect_incoming_data (self.ac_in_buffer[:index])
  126.                     self.ac_in_buffer = self.ac_in_buffer[index+terminator_len:]
  127.                     # This does the Right Thing if the terminator is changed here.
  128.                     self.found_terminator()
  129.                 else:
  130.                     # check for a prefix of the terminator
  131.                     index = find_prefix_at_end (self.ac_in_buffer, terminator)
  132.                     if index:
  133.                         if index != lb:
  134.                             # we found a prefix, collect up to the prefix
  135.                             self.collect_incoming_data (self.ac_in_buffer[:-index])
  136.                             self.ac_in_buffer = self.ac_in_buffer[-index:]
  137.                         break
  138.                     else:
  139.                         # no prefix, collect it all
  140.                         self.collect_incoming_data (self.ac_in_buffer)
  141.                         self.ac_in_buffer = ''
  142.  
  143.     def handle_write (self):
  144.         self.initiate_send ()
  145.         
  146.     def handle_close (self):
  147.         self.close()
  148.  
  149.     def push (self, data):
  150.         self.producer_fifo.push (simple_producer (data))
  151.         self.initiate_send()
  152.  
  153.     def push_with_producer (self, producer):
  154.         self.producer_fifo.push (producer)
  155.         self.initiate_send()
  156.  
  157.     def readable (self):
  158.         "predicate for inclusion in the readable for select()"
  159.         return (len(self.ac_in_buffer) <= self.ac_in_buffer_size)
  160.  
  161.     def writable (self):
  162.         "predicate for inclusion in the writable for select()"
  163.         # return len(self.ac_out_buffer) or len(self.producer_fifo) or (not self.connected)
  164.         # this is about twice as fast, though not as clear.
  165.         return not (
  166.             (self.ac_out_buffer is '') and
  167.             self.producer_fifo.is_empty() and
  168.             self.connected
  169.             )
  170.  
  171.     def close_when_done (self):
  172.         "automatically close this channel once the outgoing queue is empty"
  173.         self.producer_fifo.push (None)
  174.  
  175.     # refill the outgoing buffer by calling the more() method
  176.     # of the first producer in the queue
  177.     def refill_buffer (self):
  178.         _string_type = type('')
  179.         while 1:
  180.             if len(self.producer_fifo):
  181.                 p = self.producer_fifo.first()
  182.                 # a 'None' in the producer fifo is a sentinel,
  183.                 # telling us to close the channel.
  184.                 if p is None:
  185.                     if not self.ac_out_buffer:
  186.                         self.producer_fifo.pop()
  187.                         self.close()
  188.                     return
  189.                 elif type(p) is _string_type:
  190.                     self.producer_fifo.pop()
  191.                     self.ac_out_buffer = self.ac_out_buffer + p
  192.                     return
  193.                 data = p.more()
  194.                 if data:
  195.                     self.ac_out_buffer = self.ac_out_buffer + data
  196.                     return
  197.                 else:
  198.                     self.producer_fifo.pop()
  199.             else:
  200.                 return
  201.  
  202.     def initiate_send (self):
  203.         obs = self.ac_out_buffer_size
  204.         # try to refill the buffer
  205.         if (len (self.ac_out_buffer) < obs):
  206.             self.refill_buffer()
  207.  
  208.         if self.ac_out_buffer and self.connected:
  209.             # try to send the buffer
  210.             try:
  211.                 num_sent = self.send (self.ac_out_buffer[:obs])
  212.                 if num_sent:
  213.                     self.ac_out_buffer = self.ac_out_buffer[num_sent:]
  214.  
  215.             except socket.error, why:
  216.                 self.handle_error()
  217.                 return
  218.  
  219.     def discard_buffers (self):
  220.         # Emergencies only!
  221.         self.ac_in_buffer = ''
  222.         self.ac_out_buffer = ''
  223.         while self.producer_fifo:
  224.             self.producer_fifo.pop()
  225.  
  226.  
  227. class simple_producer:
  228.  
  229.     def __init__ (self, data, buffer_size=512):
  230.         self.data = data
  231.         self.buffer_size = buffer_size
  232.  
  233.     def more (self):
  234.         if len (self.data) > self.buffer_size:
  235.             result = self.data[:self.buffer_size]
  236.             self.data = self.data[self.buffer_size:]
  237.             return result
  238.         else:
  239.             result = self.data
  240.             self.data = ''
  241.             return result
  242.  
  243. class fifo:
  244.     def __init__ (self, list=None):
  245.         if not list:
  246.             self.list = []
  247.         else:
  248.             self.list = list
  249.         
  250.     def __len__ (self):
  251.         return len(self.list)
  252.  
  253.     def is_empty (self):
  254.         return self.list == []
  255.  
  256.     def first (self):
  257.         return self.list[0]
  258.  
  259.     def push (self, data):
  260.         self.list.append (data)
  261.  
  262.     def pop (self):
  263.         if self.list:
  264.             result = self.list[0]
  265.             del self.list[0]
  266.             return (1, result)
  267.         else:
  268.             return (0, None)
  269.  
  270. # Given 'haystack', see if any prefix of 'needle' is at its end.  This
  271. # assumes an exact match has already been checked.  Return the number of
  272. # characters matched.
  273. # for example:
  274. # f_p_a_e ("qwerty\r", "\r\n") => 1
  275. # f_p_a_e ("qwerty\r\n", "\r\n") => 2
  276. # f_p_a_e ("qwertydkjf", "\r\n") => 0
  277.  
  278. # this could maybe be made faster with a computed regex?
  279.  
  280. ##def find_prefix_at_end (haystack, needle):
  281. ##    nl = len(needle)
  282. ##    result = 0
  283. ##    for i in range (1,nl):
  284. ##        if haystack[-(nl-i):] == needle[:(nl-i)]:
  285. ##            result = nl-i
  286. ##            break
  287. ##    return result
  288.  
  289. # yes, this is about twice as fast, but still seems
  290. # to be negligible CPU.  The previous version could do about 290
  291. # searches/sec. the new one about 555/sec.
  292.  
  293. import regex
  294.  
  295. prefix_cache = {}
  296.  
  297. def prefix_regex (needle):
  298.     if prefix_cache.has_key (needle):
  299.         return prefix_cache[needle]
  300.     else:
  301.         reg = needle[-1]
  302.         for i in range(1,len(needle)):
  303.             reg = '%c\(%s\)?' % (needle[-(i+1)], reg)
  304.         reg = regex.compile (reg+'$')
  305.         prefix_cache[needle] = reg, len(needle)
  306.         return reg, len(needle)
  307.  
  308. def find_prefix_at_end (haystack, needle):
  309.     reg, length = prefix_regex (needle)
  310.     lh = len(haystack)
  311.     result = reg.search (haystack, max(0,lh-length))
  312.     if result >= 0:
  313.         return (lh - result)
  314.     else:
  315.         return 0
  316.