+++ /dev/null
-# -*- Mode: Python; tab-width: 4 -*-\r
-# Id: asynchat.py,v 2.26 2000/09/07 22:29:26 rushing Exp\r
-# Author: Sam Rushing <rushing@nightmare.com>\r
-\r
-# ======================================================================\r
-# Copyright 1996 by Sam Rushing\r
-#\r
-# All Rights Reserved\r
-#\r
-# Permission to use, copy, modify, and distribute this software and\r
-# its documentation for any purpose and without fee is hereby\r
-# granted, provided that the above copyright notice appear in all\r
-# copies and that both that copyright notice and this permission\r
-# notice appear in supporting documentation, and that the name of Sam\r
-# Rushing not be used in advertising or publicity pertaining to\r
-# distribution of the software without specific, written prior\r
-# permission.\r
-#\r
-# SAM RUSHING DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,\r
-# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN\r
-# NO EVENT SHALL SAM RUSHING BE LIABLE FOR ANY SPECIAL, INDIRECT OR\r
-# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS\r
-# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,\r
-# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN\r
-# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.\r
-# ======================================================================\r
-\r
-r"""A class supporting chat-style (command/response) protocols.\r
-\r
-This class adds support for 'chat' style protocols - where one side\r
-sends a 'command', and the other sends a response (examples would be\r
-the common internet protocols - smtp, nntp, ftp, etc..).\r
-\r
-The handle_read() method looks at the input stream for the current\r
-'terminator' (usually '\r\n' for single-line responses, '\r\n.\r\n'\r
-for multi-line output), calling self.found_terminator() on its\r
-receipt.\r
-\r
-for example:\r
-Say you build an async nntp client using this class. At the start\r
-of the connection, you'll have self.terminator set to '\r\n', in\r
-order to process the single-line greeting. Just before issuing a\r
-'LIST' command you'll set it to '\r\n.\r\n'. The output of the LIST\r
-command will be accumulated (using your own 'collect_incoming_data'\r
-method) up to the terminator, and then control will be returned to\r
-you - by calling your self.found_terminator() method.\r
-"""\r
-\r
-import socket\r
-import asyncore\r
-from collections import deque\r
-from sys import py3kwarning\r
-from warnings import filterwarnings, catch_warnings\r
-\r
-class async_chat (asyncore.dispatcher):\r
- """This is an abstract class. You must derive from this class, and add\r
- the two methods collect_incoming_data() and found_terminator()"""\r
-\r
- # these are overridable defaults\r
-\r
- ac_in_buffer_size = 4096\r
- ac_out_buffer_size = 4096\r
-\r
- def __init__ (self, sock=None, map=None):\r
- # for string terminator matching\r
- self.ac_in_buffer = ''\r
-\r
- # we use a list here rather than cStringIO for a few reasons...\r
- # del lst[:] is faster than sio.truncate(0)\r
- # lst = [] is faster than sio.truncate(0)\r
- # cStringIO will be gaining unicode support in py3k, which\r
- # will negatively affect the performance of bytes compared to\r
- # a ''.join() equivalent\r
- self.incoming = []\r
-\r
- # we toss the use of the "simple producer" and replace it with\r
- # a pure deque, which the original fifo was a wrapping of\r
- self.producer_fifo = deque()\r
- asyncore.dispatcher.__init__ (self, sock, map)\r
-\r
- def collect_incoming_data(self, data):\r
- raise NotImplementedError("must be implemented in subclass")\r
-\r
- def _collect_incoming_data(self, data):\r
- self.incoming.append(data)\r
-\r
- def _get_data(self):\r
- d = ''.join(self.incoming)\r
- del self.incoming[:]\r
- return d\r
-\r
- def found_terminator(self):\r
- raise NotImplementedError("must be implemented in subclass")\r
-\r
- def set_terminator (self, term):\r
- "Set the input delimiter. Can be a fixed string of any length, an integer, or None"\r
- self.terminator = term\r
-\r
- def get_terminator (self):\r
- return self.terminator\r
-\r
- # grab some more data from the socket,\r
- # throw it to the collector method,\r
- # check for the terminator,\r
- # if found, transition to the next state.\r
-\r
- def handle_read (self):\r
-\r
- try:\r
- data = self.recv (self.ac_in_buffer_size)\r
- except socket.error, why:\r
- self.handle_error()\r
- return\r
-\r
- self.ac_in_buffer = self.ac_in_buffer + data\r
-\r
- # Continue to search for self.terminator in self.ac_in_buffer,\r
- # while calling self.collect_incoming_data. The while loop\r
- # is necessary because we might read several data+terminator\r
- # combos with a single recv(4096).\r
-\r
- while self.ac_in_buffer:\r
- lb = len(self.ac_in_buffer)\r
- terminator = self.get_terminator()\r
- if not terminator:\r
- # no terminator, collect it all\r
- self.collect_incoming_data (self.ac_in_buffer)\r
- self.ac_in_buffer = ''\r
- elif isinstance(terminator, int) or isinstance(terminator, long):\r
- # numeric terminator\r
- n = terminator\r
- if lb < n:\r
- self.collect_incoming_data (self.ac_in_buffer)\r
- self.ac_in_buffer = ''\r
- self.terminator = self.terminator - lb\r
- else:\r
- self.collect_incoming_data (self.ac_in_buffer[:n])\r
- self.ac_in_buffer = self.ac_in_buffer[n:]\r
- self.terminator = 0\r
- self.found_terminator()\r
- else:\r
- # 3 cases:\r
- # 1) end of buffer matches terminator exactly:\r
- # collect data, transition\r
- # 2) end of buffer matches some prefix:\r
- # collect data to the prefix\r
- # 3) end of buffer does not match any prefix:\r
- # collect data\r
- terminator_len = len(terminator)\r
- index = self.ac_in_buffer.find(terminator)\r
- if index != -1:\r
- # we found the terminator\r
- if index > 0:\r
- # don't bother reporting the empty string (source of subtle bugs)\r
- self.collect_incoming_data (self.ac_in_buffer[:index])\r
- self.ac_in_buffer = self.ac_in_buffer[index+terminator_len:]\r
- # This does the Right Thing if the terminator is changed here.\r
- self.found_terminator()\r
- else:\r
- # check for a prefix of the terminator\r
- index = find_prefix_at_end (self.ac_in_buffer, terminator)\r
- if index:\r
- if index != lb:\r
- # we found a prefix, collect up to the prefix\r
- self.collect_incoming_data (self.ac_in_buffer[:-index])\r
- self.ac_in_buffer = self.ac_in_buffer[-index:]\r
- break\r
- else:\r
- # no prefix, collect it all\r
- self.collect_incoming_data (self.ac_in_buffer)\r
- self.ac_in_buffer = ''\r
-\r
- def handle_write (self):\r
- self.initiate_send()\r
-\r
- def handle_close (self):\r
- self.close()\r
-\r
- def push (self, data):\r
- sabs = self.ac_out_buffer_size\r
- if len(data) > sabs:\r
- for i in xrange(0, len(data), sabs):\r
- self.producer_fifo.append(data[i:i+sabs])\r
- else:\r
- self.producer_fifo.append(data)\r
- self.initiate_send()\r
-\r
- def push_with_producer (self, producer):\r
- self.producer_fifo.append(producer)\r
- self.initiate_send()\r
-\r
- def readable (self):\r
- "predicate for inclusion in the readable for select()"\r
- # cannot use the old predicate, it violates the claim of the\r
- # set_terminator method.\r
-\r
- # return (len(self.ac_in_buffer) <= self.ac_in_buffer_size)\r
- return 1\r
-\r
- def writable (self):\r
- "predicate for inclusion in the writable for select()"\r
- return self.producer_fifo or (not self.connected)\r
-\r
- def close_when_done (self):\r
- "automatically close this channel once the outgoing queue is empty"\r
- self.producer_fifo.append(None)\r
-\r
- def initiate_send(self):\r
- while self.producer_fifo and self.connected:\r
- first = self.producer_fifo[0]\r
- # handle empty string/buffer or None entry\r
- if not first:\r
- del self.producer_fifo[0]\r
- if first is None:\r
- self.handle_close()\r
- return\r
-\r
- # handle classic producer behavior\r
- obs = self.ac_out_buffer_size\r
- try:\r
- with catch_warnings():\r
- if py3kwarning:\r
- filterwarnings("ignore", ".*buffer", DeprecationWarning)\r
- data = buffer(first, 0, obs)\r
- except TypeError:\r
- data = first.more()\r
- if data:\r
- self.producer_fifo.appendleft(data)\r
- else:\r
- del self.producer_fifo[0]\r
- continue\r
-\r
- # send the data\r
- try:\r
- num_sent = self.send(data)\r
- except socket.error:\r
- self.handle_error()\r
- return\r
-\r
- if num_sent:\r
- if num_sent < len(data) or obs < len(first):\r
- self.producer_fifo[0] = first[num_sent:]\r
- else:\r
- del self.producer_fifo[0]\r
- # we tried to send some actual data\r
- return\r
-\r
- def discard_buffers (self):\r
- # Emergencies only!\r
- self.ac_in_buffer = ''\r
- del self.incoming[:]\r
- self.producer_fifo.clear()\r
-\r
-class simple_producer:\r
-\r
- def __init__ (self, data, buffer_size=512):\r
- self.data = data\r
- self.buffer_size = buffer_size\r
-\r
- def more (self):\r
- if len (self.data) > self.buffer_size:\r
- result = self.data[:self.buffer_size]\r
- self.data = self.data[self.buffer_size:]\r
- return result\r
- else:\r
- result = self.data\r
- self.data = ''\r
- return result\r
-\r
-class fifo:\r
- def __init__ (self, list=None):\r
- if not list:\r
- self.list = deque()\r
- else:\r
- self.list = deque(list)\r
-\r
- def __len__ (self):\r
- return len(self.list)\r
-\r
- def is_empty (self):\r
- return not self.list\r
-\r
- def first (self):\r
- return self.list[0]\r
-\r
- def push (self, data):\r
- self.list.append(data)\r
-\r
- def pop (self):\r
- if self.list:\r
- return (1, self.list.popleft())\r
- else:\r
- return (0, None)\r
-\r
-# Given 'haystack', see if any prefix of 'needle' is at its end. This\r
-# assumes an exact match has already been checked. Return the number of\r
-# characters matched.\r
-# for example:\r
-# f_p_a_e ("qwerty\r", "\r\n") => 1\r
-# f_p_a_e ("qwertydkjf", "\r\n") => 0\r
-# f_p_a_e ("qwerty\r\n", "\r\n") => <undefined>\r
-\r
-# this could maybe be made faster with a computed regex?\r
-# [answer: no; circa Python-2.0, Jan 2001]\r
-# new python: 28961/s\r
-# old python: 18307/s\r
-# re: 12820/s\r
-# regex: 14035/s\r
-\r
-def find_prefix_at_end (haystack, needle):\r
- l = len(needle) - 1\r
- while l and not haystack.endswith(needle[:l]):\r
- l -= 1\r
- return l\r