python-2.5.2/win32/Lib/asynchat.py
changeset 0 ae805ac0140d
equal deleted inserted replaced
-1:000000000000 0:ae805ac0140d
       
     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 from collections import deque
       
    52 
       
    53 class async_chat (asyncore.dispatcher):
       
    54     """This is an abstract class.  You must derive from this class, and add
       
    55     the two methods collect_incoming_data() and found_terminator()"""
       
    56 
       
    57     # these are overridable defaults
       
    58 
       
    59     ac_in_buffer_size       = 4096
       
    60     ac_out_buffer_size      = 4096
       
    61 
       
    62     def __init__ (self, conn=None):
       
    63         self.ac_in_buffer = ''
       
    64         self.ac_out_buffer = ''
       
    65         self.producer_fifo = fifo()
       
    66         asyncore.dispatcher.__init__ (self, conn)
       
    67 
       
    68     def collect_incoming_data(self, data):
       
    69         raise NotImplementedError, "must be implemented in subclass"
       
    70 
       
    71     def found_terminator(self):
       
    72         raise NotImplementedError, "must be implemented in subclass"
       
    73 
       
    74     def set_terminator (self, term):
       
    75         "Set the input delimiter.  Can be a fixed string of any length, an integer, or None"
       
    76         self.terminator = term
       
    77 
       
    78     def get_terminator (self):
       
    79         return self.terminator
       
    80 
       
    81     # grab some more data from the socket,
       
    82     # throw it to the collector method,
       
    83     # check for the terminator,
       
    84     # if found, transition to the next state.
       
    85 
       
    86     def handle_read (self):
       
    87 
       
    88         try:
       
    89             data = self.recv (self.ac_in_buffer_size)
       
    90         except socket.error, why:
       
    91             self.handle_error()
       
    92             return
       
    93 
       
    94         self.ac_in_buffer = self.ac_in_buffer + data
       
    95 
       
    96         # Continue to search for self.terminator in self.ac_in_buffer,
       
    97         # while calling self.collect_incoming_data.  The while loop
       
    98         # is necessary because we might read several data+terminator
       
    99         # combos with a single recv(1024).
       
   100 
       
   101         while self.ac_in_buffer:
       
   102             lb = len(self.ac_in_buffer)
       
   103             terminator = self.get_terminator()
       
   104             if not terminator:
       
   105                 # no terminator, collect it all
       
   106                 self.collect_incoming_data (self.ac_in_buffer)
       
   107                 self.ac_in_buffer = ''
       
   108             elif isinstance(terminator, int) or isinstance(terminator, long):
       
   109                 # numeric terminator
       
   110                 n = terminator
       
   111                 if lb < n:
       
   112                     self.collect_incoming_data (self.ac_in_buffer)
       
   113                     self.ac_in_buffer = ''
       
   114                     self.terminator = self.terminator - lb
       
   115                 else:
       
   116                     self.collect_incoming_data (self.ac_in_buffer[:n])
       
   117                     self.ac_in_buffer = self.ac_in_buffer[n:]
       
   118                     self.terminator = 0
       
   119                     self.found_terminator()
       
   120             else:
       
   121                 # 3 cases:
       
   122                 # 1) end of buffer matches terminator exactly:
       
   123                 #    collect data, transition
       
   124                 # 2) end of buffer matches some prefix:
       
   125                 #    collect data to the prefix
       
   126                 # 3) end of buffer does not match any prefix:
       
   127                 #    collect data
       
   128                 terminator_len = len(terminator)
       
   129                 index = self.ac_in_buffer.find(terminator)
       
   130                 if index != -1:
       
   131                     # we found the terminator
       
   132                     if index > 0:
       
   133                         # don't bother reporting the empty string (source of subtle bugs)
       
   134                         self.collect_incoming_data (self.ac_in_buffer[:index])
       
   135                     self.ac_in_buffer = self.ac_in_buffer[index+terminator_len:]
       
   136                     # This does the Right Thing if the terminator is changed here.
       
   137                     self.found_terminator()
       
   138                 else:
       
   139                     # check for a prefix of the terminator
       
   140                     index = find_prefix_at_end (self.ac_in_buffer, terminator)
       
   141                     if index:
       
   142                         if index != lb:
       
   143                             # we found a prefix, collect up to the prefix
       
   144                             self.collect_incoming_data (self.ac_in_buffer[:-index])
       
   145                             self.ac_in_buffer = self.ac_in_buffer[-index:]
       
   146                         break
       
   147                     else:
       
   148                         # no prefix, collect it all
       
   149                         self.collect_incoming_data (self.ac_in_buffer)
       
   150                         self.ac_in_buffer = ''
       
   151 
       
   152     def handle_write (self):
       
   153         self.initiate_send ()
       
   154 
       
   155     def handle_close (self):
       
   156         self.close()
       
   157 
       
   158     def push (self, data):
       
   159         self.producer_fifo.push (simple_producer (data))
       
   160         self.initiate_send()
       
   161 
       
   162     def push_with_producer (self, producer):
       
   163         self.producer_fifo.push (producer)
       
   164         self.initiate_send()
       
   165 
       
   166     def readable (self):
       
   167         "predicate for inclusion in the readable for select()"
       
   168         return (len(self.ac_in_buffer) <= self.ac_in_buffer_size)
       
   169 
       
   170     def writable (self):
       
   171         "predicate for inclusion in the writable for select()"
       
   172         # return len(self.ac_out_buffer) or len(self.producer_fifo) or (not self.connected)
       
   173         # this is about twice as fast, though not as clear.
       
   174         return not (
       
   175                 (self.ac_out_buffer == '') and
       
   176                 self.producer_fifo.is_empty() and
       
   177                 self.connected
       
   178                 )
       
   179 
       
   180     def close_when_done (self):
       
   181         "automatically close this channel once the outgoing queue is empty"
       
   182         self.producer_fifo.push (None)
       
   183 
       
   184     # refill the outgoing buffer by calling the more() method
       
   185     # of the first producer in the queue
       
   186     def refill_buffer (self):
       
   187         while 1:
       
   188             if len(self.producer_fifo):
       
   189                 p = self.producer_fifo.first()
       
   190                 # a 'None' in the producer fifo is a sentinel,
       
   191                 # telling us to close the channel.
       
   192                 if p is None:
       
   193                     if not self.ac_out_buffer:
       
   194                         self.producer_fifo.pop()
       
   195                         self.close()
       
   196                     return
       
   197                 elif isinstance(p, str):
       
   198                     self.producer_fifo.pop()
       
   199                     self.ac_out_buffer = self.ac_out_buffer + p
       
   200                     return
       
   201                 data = p.more()
       
   202                 if data:
       
   203                     self.ac_out_buffer = self.ac_out_buffer + data
       
   204                     return
       
   205                 else:
       
   206                     self.producer_fifo.pop()
       
   207             else:
       
   208                 return
       
   209 
       
   210     def initiate_send (self):
       
   211         obs = self.ac_out_buffer_size
       
   212         # try to refill the buffer
       
   213         if (len (self.ac_out_buffer) < obs):
       
   214             self.refill_buffer()
       
   215 
       
   216         if self.ac_out_buffer and self.connected:
       
   217             # try to send the buffer
       
   218             try:
       
   219                 num_sent = self.send (self.ac_out_buffer[:obs])
       
   220                 if num_sent:
       
   221                     self.ac_out_buffer = self.ac_out_buffer[num_sent:]
       
   222 
       
   223             except socket.error, why:
       
   224                 self.handle_error()
       
   225                 return
       
   226 
       
   227     def discard_buffers (self):
       
   228         # Emergencies only!
       
   229         self.ac_in_buffer = ''
       
   230         self.ac_out_buffer = ''
       
   231         while self.producer_fifo:
       
   232             self.producer_fifo.pop()
       
   233 
       
   234 
       
   235 class simple_producer:
       
   236 
       
   237     def __init__ (self, data, buffer_size=512):
       
   238         self.data = data
       
   239         self.buffer_size = buffer_size
       
   240 
       
   241     def more (self):
       
   242         if len (self.data) > self.buffer_size:
       
   243             result = self.data[:self.buffer_size]
       
   244             self.data = self.data[self.buffer_size:]
       
   245             return result
       
   246         else:
       
   247             result = self.data
       
   248             self.data = ''
       
   249             return result
       
   250 
       
   251 class fifo:
       
   252     def __init__ (self, list=None):
       
   253         if not list:
       
   254             self.list = deque()
       
   255         else:
       
   256             self.list = deque(list)
       
   257 
       
   258     def __len__ (self):
       
   259         return len(self.list)
       
   260 
       
   261     def is_empty (self):
       
   262         return not self.list
       
   263 
       
   264     def first (self):
       
   265         return self.list[0]
       
   266 
       
   267     def push (self, data):
       
   268         self.list.append(data)
       
   269 
       
   270     def pop (self):
       
   271         if self.list:
       
   272             return (1, self.list.popleft())
       
   273         else:
       
   274             return (0, None)
       
   275 
       
   276 # Given 'haystack', see if any prefix of 'needle' is at its end.  This
       
   277 # assumes an exact match has already been checked.  Return the number of
       
   278 # characters matched.
       
   279 # for example:
       
   280 # f_p_a_e ("qwerty\r", "\r\n") => 1
       
   281 # f_p_a_e ("qwertydkjf", "\r\n") => 0
       
   282 # f_p_a_e ("qwerty\r\n", "\r\n") => <undefined>
       
   283 
       
   284 # this could maybe be made faster with a computed regex?
       
   285 # [answer: no; circa Python-2.0, Jan 2001]
       
   286 # new python:   28961/s
       
   287 # old python:   18307/s
       
   288 # re:        12820/s
       
   289 # regex:     14035/s
       
   290 
       
   291 def find_prefix_at_end (haystack, needle):
       
   292     l = len(needle) - 1
       
   293     while l and not haystack.endswith(needle[:l]):
       
   294         l -= 1
       
   295     return l