17db96d56Sopenharmony_ci# -*- Mode: Python; tab-width: 4 -*- 27db96d56Sopenharmony_ci# Id: asynchat.py,v 2.26 2000/09/07 22:29:26 rushing Exp 37db96d56Sopenharmony_ci# Author: Sam Rushing <rushing@nightmare.com> 47db96d56Sopenharmony_ci 57db96d56Sopenharmony_ci# ====================================================================== 67db96d56Sopenharmony_ci# Copyright 1996 by Sam Rushing 77db96d56Sopenharmony_ci# 87db96d56Sopenharmony_ci# All Rights Reserved 97db96d56Sopenharmony_ci# 107db96d56Sopenharmony_ci# Permission to use, copy, modify, and distribute this software and 117db96d56Sopenharmony_ci# its documentation for any purpose and without fee is hereby 127db96d56Sopenharmony_ci# granted, provided that the above copyright notice appear in all 137db96d56Sopenharmony_ci# copies and that both that copyright notice and this permission 147db96d56Sopenharmony_ci# notice appear in supporting documentation, and that the name of Sam 157db96d56Sopenharmony_ci# Rushing not be used in advertising or publicity pertaining to 167db96d56Sopenharmony_ci# distribution of the software without specific, written prior 177db96d56Sopenharmony_ci# permission. 187db96d56Sopenharmony_ci# 197db96d56Sopenharmony_ci# SAM RUSHING DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, 207db96d56Sopenharmony_ci# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN 217db96d56Sopenharmony_ci# NO EVENT SHALL SAM RUSHING BE LIABLE FOR ANY SPECIAL, INDIRECT OR 227db96d56Sopenharmony_ci# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS 237db96d56Sopenharmony_ci# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, 247db96d56Sopenharmony_ci# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN 257db96d56Sopenharmony_ci# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 267db96d56Sopenharmony_ci# ====================================================================== 277db96d56Sopenharmony_ci 287db96d56Sopenharmony_cir"""A class supporting chat-style (command/response) protocols. 297db96d56Sopenharmony_ci 307db96d56Sopenharmony_ciThis class adds support for 'chat' style protocols - where one side 317db96d56Sopenharmony_cisends a 'command', and the other sends a response (examples would be 327db96d56Sopenharmony_cithe common internet protocols - smtp, nntp, ftp, etc..). 337db96d56Sopenharmony_ci 347db96d56Sopenharmony_ciThe handle_read() method looks at the input stream for the current 357db96d56Sopenharmony_ci'terminator' (usually '\r\n' for single-line responses, '\r\n.\r\n' 367db96d56Sopenharmony_cifor multi-line output), calling self.found_terminator() on its 377db96d56Sopenharmony_cireceipt. 387db96d56Sopenharmony_ci 397db96d56Sopenharmony_cifor example: 407db96d56Sopenharmony_ciSay you build an async nntp client using this class. At the start 417db96d56Sopenharmony_ciof the connection, you'll have self.terminator set to '\r\n', in 427db96d56Sopenharmony_ciorder to process the single-line greeting. Just before issuing a 437db96d56Sopenharmony_ci'LIST' command you'll set it to '\r\n.\r\n'. The output of the LIST 447db96d56Sopenharmony_cicommand will be accumulated (using your own 'collect_incoming_data' 457db96d56Sopenharmony_cimethod) up to the terminator, and then control will be returned to 467db96d56Sopenharmony_ciyou - by calling your self.found_terminator() method. 477db96d56Sopenharmony_ci""" 487db96d56Sopenharmony_ciimport asyncore 497db96d56Sopenharmony_cifrom collections import deque 507db96d56Sopenharmony_ci 517db96d56Sopenharmony_cifrom warnings import _deprecated 527db96d56Sopenharmony_ci 537db96d56Sopenharmony_ci_DEPRECATION_MSG = ('The {name} module is deprecated and will be removed in ' 547db96d56Sopenharmony_ci 'Python {remove}. The recommended replacement is asyncio') 557db96d56Sopenharmony_ci_deprecated(__name__, _DEPRECATION_MSG, remove=(3, 12)) 567db96d56Sopenharmony_ci 577db96d56Sopenharmony_ci 587db96d56Sopenharmony_ci 597db96d56Sopenharmony_ciclass async_chat(asyncore.dispatcher): 607db96d56Sopenharmony_ci """This is an abstract class. You must derive from this class, and add 617db96d56Sopenharmony_ci the two methods collect_incoming_data() and found_terminator()""" 627db96d56Sopenharmony_ci 637db96d56Sopenharmony_ci # these are overridable defaults 647db96d56Sopenharmony_ci 657db96d56Sopenharmony_ci ac_in_buffer_size = 65536 667db96d56Sopenharmony_ci ac_out_buffer_size = 65536 677db96d56Sopenharmony_ci 687db96d56Sopenharmony_ci # we don't want to enable the use of encoding by default, because that is a 697db96d56Sopenharmony_ci # sign of an application bug that we don't want to pass silently 707db96d56Sopenharmony_ci 717db96d56Sopenharmony_ci use_encoding = 0 727db96d56Sopenharmony_ci encoding = 'latin-1' 737db96d56Sopenharmony_ci 747db96d56Sopenharmony_ci def __init__(self, sock=None, map=None): 757db96d56Sopenharmony_ci # for string terminator matching 767db96d56Sopenharmony_ci self.ac_in_buffer = b'' 777db96d56Sopenharmony_ci 787db96d56Sopenharmony_ci # we use a list here rather than io.BytesIO for a few reasons... 797db96d56Sopenharmony_ci # del lst[:] is faster than bio.truncate(0) 807db96d56Sopenharmony_ci # lst = [] is faster than bio.truncate(0) 817db96d56Sopenharmony_ci self.incoming = [] 827db96d56Sopenharmony_ci 837db96d56Sopenharmony_ci # we toss the use of the "simple producer" and replace it with 847db96d56Sopenharmony_ci # a pure deque, which the original fifo was a wrapping of 857db96d56Sopenharmony_ci self.producer_fifo = deque() 867db96d56Sopenharmony_ci asyncore.dispatcher.__init__(self, sock, map) 877db96d56Sopenharmony_ci 887db96d56Sopenharmony_ci def collect_incoming_data(self, data): 897db96d56Sopenharmony_ci raise NotImplementedError("must be implemented in subclass") 907db96d56Sopenharmony_ci 917db96d56Sopenharmony_ci def _collect_incoming_data(self, data): 927db96d56Sopenharmony_ci self.incoming.append(data) 937db96d56Sopenharmony_ci 947db96d56Sopenharmony_ci def _get_data(self): 957db96d56Sopenharmony_ci d = b''.join(self.incoming) 967db96d56Sopenharmony_ci del self.incoming[:] 977db96d56Sopenharmony_ci return d 987db96d56Sopenharmony_ci 997db96d56Sopenharmony_ci def found_terminator(self): 1007db96d56Sopenharmony_ci raise NotImplementedError("must be implemented in subclass") 1017db96d56Sopenharmony_ci 1027db96d56Sopenharmony_ci def set_terminator(self, term): 1037db96d56Sopenharmony_ci """Set the input delimiter. 1047db96d56Sopenharmony_ci 1057db96d56Sopenharmony_ci Can be a fixed string of any length, an integer, or None. 1067db96d56Sopenharmony_ci """ 1077db96d56Sopenharmony_ci if isinstance(term, str) and self.use_encoding: 1087db96d56Sopenharmony_ci term = bytes(term, self.encoding) 1097db96d56Sopenharmony_ci elif isinstance(term, int) and term < 0: 1107db96d56Sopenharmony_ci raise ValueError('the number of received bytes must be positive') 1117db96d56Sopenharmony_ci self.terminator = term 1127db96d56Sopenharmony_ci 1137db96d56Sopenharmony_ci def get_terminator(self): 1147db96d56Sopenharmony_ci return self.terminator 1157db96d56Sopenharmony_ci 1167db96d56Sopenharmony_ci # grab some more data from the socket, 1177db96d56Sopenharmony_ci # throw it to the collector method, 1187db96d56Sopenharmony_ci # check for the terminator, 1197db96d56Sopenharmony_ci # if found, transition to the next state. 1207db96d56Sopenharmony_ci 1217db96d56Sopenharmony_ci def handle_read(self): 1227db96d56Sopenharmony_ci 1237db96d56Sopenharmony_ci try: 1247db96d56Sopenharmony_ci data = self.recv(self.ac_in_buffer_size) 1257db96d56Sopenharmony_ci except BlockingIOError: 1267db96d56Sopenharmony_ci return 1277db96d56Sopenharmony_ci except OSError: 1287db96d56Sopenharmony_ci self.handle_error() 1297db96d56Sopenharmony_ci return 1307db96d56Sopenharmony_ci 1317db96d56Sopenharmony_ci if isinstance(data, str) and self.use_encoding: 1327db96d56Sopenharmony_ci data = bytes(str, self.encoding) 1337db96d56Sopenharmony_ci self.ac_in_buffer = self.ac_in_buffer + data 1347db96d56Sopenharmony_ci 1357db96d56Sopenharmony_ci # Continue to search for self.terminator in self.ac_in_buffer, 1367db96d56Sopenharmony_ci # while calling self.collect_incoming_data. The while loop 1377db96d56Sopenharmony_ci # is necessary because we might read several data+terminator 1387db96d56Sopenharmony_ci # combos with a single recv(4096). 1397db96d56Sopenharmony_ci 1407db96d56Sopenharmony_ci while self.ac_in_buffer: 1417db96d56Sopenharmony_ci lb = len(self.ac_in_buffer) 1427db96d56Sopenharmony_ci terminator = self.get_terminator() 1437db96d56Sopenharmony_ci if not terminator: 1447db96d56Sopenharmony_ci # no terminator, collect it all 1457db96d56Sopenharmony_ci self.collect_incoming_data(self.ac_in_buffer) 1467db96d56Sopenharmony_ci self.ac_in_buffer = b'' 1477db96d56Sopenharmony_ci elif isinstance(terminator, int): 1487db96d56Sopenharmony_ci # numeric terminator 1497db96d56Sopenharmony_ci n = terminator 1507db96d56Sopenharmony_ci if lb < n: 1517db96d56Sopenharmony_ci self.collect_incoming_data(self.ac_in_buffer) 1527db96d56Sopenharmony_ci self.ac_in_buffer = b'' 1537db96d56Sopenharmony_ci self.terminator = self.terminator - lb 1547db96d56Sopenharmony_ci else: 1557db96d56Sopenharmony_ci self.collect_incoming_data(self.ac_in_buffer[:n]) 1567db96d56Sopenharmony_ci self.ac_in_buffer = self.ac_in_buffer[n:] 1577db96d56Sopenharmony_ci self.terminator = 0 1587db96d56Sopenharmony_ci self.found_terminator() 1597db96d56Sopenharmony_ci else: 1607db96d56Sopenharmony_ci # 3 cases: 1617db96d56Sopenharmony_ci # 1) end of buffer matches terminator exactly: 1627db96d56Sopenharmony_ci # collect data, transition 1637db96d56Sopenharmony_ci # 2) end of buffer matches some prefix: 1647db96d56Sopenharmony_ci # collect data to the prefix 1657db96d56Sopenharmony_ci # 3) end of buffer does not match any prefix: 1667db96d56Sopenharmony_ci # collect data 1677db96d56Sopenharmony_ci terminator_len = len(terminator) 1687db96d56Sopenharmony_ci index = self.ac_in_buffer.find(terminator) 1697db96d56Sopenharmony_ci if index != -1: 1707db96d56Sopenharmony_ci # we found the terminator 1717db96d56Sopenharmony_ci if index > 0: 1727db96d56Sopenharmony_ci # don't bother reporting the empty string 1737db96d56Sopenharmony_ci # (source of subtle bugs) 1747db96d56Sopenharmony_ci self.collect_incoming_data(self.ac_in_buffer[:index]) 1757db96d56Sopenharmony_ci self.ac_in_buffer = self.ac_in_buffer[index+terminator_len:] 1767db96d56Sopenharmony_ci # This does the Right Thing if the terminator 1777db96d56Sopenharmony_ci # is changed here. 1787db96d56Sopenharmony_ci self.found_terminator() 1797db96d56Sopenharmony_ci else: 1807db96d56Sopenharmony_ci # check for a prefix of the terminator 1817db96d56Sopenharmony_ci index = find_prefix_at_end(self.ac_in_buffer, terminator) 1827db96d56Sopenharmony_ci if index: 1837db96d56Sopenharmony_ci if index != lb: 1847db96d56Sopenharmony_ci # we found a prefix, collect up to the prefix 1857db96d56Sopenharmony_ci self.collect_incoming_data(self.ac_in_buffer[:-index]) 1867db96d56Sopenharmony_ci self.ac_in_buffer = self.ac_in_buffer[-index:] 1877db96d56Sopenharmony_ci break 1887db96d56Sopenharmony_ci else: 1897db96d56Sopenharmony_ci # no prefix, collect it all 1907db96d56Sopenharmony_ci self.collect_incoming_data(self.ac_in_buffer) 1917db96d56Sopenharmony_ci self.ac_in_buffer = b'' 1927db96d56Sopenharmony_ci 1937db96d56Sopenharmony_ci def handle_write(self): 1947db96d56Sopenharmony_ci self.initiate_send() 1957db96d56Sopenharmony_ci 1967db96d56Sopenharmony_ci def handle_close(self): 1977db96d56Sopenharmony_ci self.close() 1987db96d56Sopenharmony_ci 1997db96d56Sopenharmony_ci def push(self, data): 2007db96d56Sopenharmony_ci if not isinstance(data, (bytes, bytearray, memoryview)): 2017db96d56Sopenharmony_ci raise TypeError('data argument must be byte-ish (%r)', 2027db96d56Sopenharmony_ci type(data)) 2037db96d56Sopenharmony_ci sabs = self.ac_out_buffer_size 2047db96d56Sopenharmony_ci if len(data) > sabs: 2057db96d56Sopenharmony_ci for i in range(0, len(data), sabs): 2067db96d56Sopenharmony_ci self.producer_fifo.append(data[i:i+sabs]) 2077db96d56Sopenharmony_ci else: 2087db96d56Sopenharmony_ci self.producer_fifo.append(data) 2097db96d56Sopenharmony_ci self.initiate_send() 2107db96d56Sopenharmony_ci 2117db96d56Sopenharmony_ci def push_with_producer(self, producer): 2127db96d56Sopenharmony_ci self.producer_fifo.append(producer) 2137db96d56Sopenharmony_ci self.initiate_send() 2147db96d56Sopenharmony_ci 2157db96d56Sopenharmony_ci def readable(self): 2167db96d56Sopenharmony_ci "predicate for inclusion in the readable for select()" 2177db96d56Sopenharmony_ci # cannot use the old predicate, it violates the claim of the 2187db96d56Sopenharmony_ci # set_terminator method. 2197db96d56Sopenharmony_ci 2207db96d56Sopenharmony_ci # return (len(self.ac_in_buffer) <= self.ac_in_buffer_size) 2217db96d56Sopenharmony_ci return 1 2227db96d56Sopenharmony_ci 2237db96d56Sopenharmony_ci def writable(self): 2247db96d56Sopenharmony_ci "predicate for inclusion in the writable for select()" 2257db96d56Sopenharmony_ci return self.producer_fifo or (not self.connected) 2267db96d56Sopenharmony_ci 2277db96d56Sopenharmony_ci def close_when_done(self): 2287db96d56Sopenharmony_ci "automatically close this channel once the outgoing queue is empty" 2297db96d56Sopenharmony_ci self.producer_fifo.append(None) 2307db96d56Sopenharmony_ci 2317db96d56Sopenharmony_ci def initiate_send(self): 2327db96d56Sopenharmony_ci while self.producer_fifo and self.connected: 2337db96d56Sopenharmony_ci first = self.producer_fifo[0] 2347db96d56Sopenharmony_ci # handle empty string/buffer or None entry 2357db96d56Sopenharmony_ci if not first: 2367db96d56Sopenharmony_ci del self.producer_fifo[0] 2377db96d56Sopenharmony_ci if first is None: 2387db96d56Sopenharmony_ci self.handle_close() 2397db96d56Sopenharmony_ci return 2407db96d56Sopenharmony_ci 2417db96d56Sopenharmony_ci # handle classic producer behavior 2427db96d56Sopenharmony_ci obs = self.ac_out_buffer_size 2437db96d56Sopenharmony_ci try: 2447db96d56Sopenharmony_ci data = first[:obs] 2457db96d56Sopenharmony_ci except TypeError: 2467db96d56Sopenharmony_ci data = first.more() 2477db96d56Sopenharmony_ci if data: 2487db96d56Sopenharmony_ci self.producer_fifo.appendleft(data) 2497db96d56Sopenharmony_ci else: 2507db96d56Sopenharmony_ci del self.producer_fifo[0] 2517db96d56Sopenharmony_ci continue 2527db96d56Sopenharmony_ci 2537db96d56Sopenharmony_ci if isinstance(data, str) and self.use_encoding: 2547db96d56Sopenharmony_ci data = bytes(data, self.encoding) 2557db96d56Sopenharmony_ci 2567db96d56Sopenharmony_ci # send the data 2577db96d56Sopenharmony_ci try: 2587db96d56Sopenharmony_ci num_sent = self.send(data) 2597db96d56Sopenharmony_ci except OSError: 2607db96d56Sopenharmony_ci self.handle_error() 2617db96d56Sopenharmony_ci return 2627db96d56Sopenharmony_ci 2637db96d56Sopenharmony_ci if num_sent: 2647db96d56Sopenharmony_ci if num_sent < len(data) or obs < len(first): 2657db96d56Sopenharmony_ci self.producer_fifo[0] = first[num_sent:] 2667db96d56Sopenharmony_ci else: 2677db96d56Sopenharmony_ci del self.producer_fifo[0] 2687db96d56Sopenharmony_ci # we tried to send some actual data 2697db96d56Sopenharmony_ci return 2707db96d56Sopenharmony_ci 2717db96d56Sopenharmony_ci def discard_buffers(self): 2727db96d56Sopenharmony_ci # Emergencies only! 2737db96d56Sopenharmony_ci self.ac_in_buffer = b'' 2747db96d56Sopenharmony_ci del self.incoming[:] 2757db96d56Sopenharmony_ci self.producer_fifo.clear() 2767db96d56Sopenharmony_ci 2777db96d56Sopenharmony_ci 2787db96d56Sopenharmony_ciclass simple_producer: 2797db96d56Sopenharmony_ci 2807db96d56Sopenharmony_ci def __init__(self, data, buffer_size=512): 2817db96d56Sopenharmony_ci self.data = data 2827db96d56Sopenharmony_ci self.buffer_size = buffer_size 2837db96d56Sopenharmony_ci 2847db96d56Sopenharmony_ci def more(self): 2857db96d56Sopenharmony_ci if len(self.data) > self.buffer_size: 2867db96d56Sopenharmony_ci result = self.data[:self.buffer_size] 2877db96d56Sopenharmony_ci self.data = self.data[self.buffer_size:] 2887db96d56Sopenharmony_ci return result 2897db96d56Sopenharmony_ci else: 2907db96d56Sopenharmony_ci result = self.data 2917db96d56Sopenharmony_ci self.data = b'' 2927db96d56Sopenharmony_ci return result 2937db96d56Sopenharmony_ci 2947db96d56Sopenharmony_ci 2957db96d56Sopenharmony_ci# Given 'haystack', see if any prefix of 'needle' is at its end. This 2967db96d56Sopenharmony_ci# assumes an exact match has already been checked. Return the number of 2977db96d56Sopenharmony_ci# characters matched. 2987db96d56Sopenharmony_ci# for example: 2997db96d56Sopenharmony_ci# f_p_a_e("qwerty\r", "\r\n") => 1 3007db96d56Sopenharmony_ci# f_p_a_e("qwertydkjf", "\r\n") => 0 3017db96d56Sopenharmony_ci# f_p_a_e("qwerty\r\n", "\r\n") => <undefined> 3027db96d56Sopenharmony_ci 3037db96d56Sopenharmony_ci# this could maybe be made faster with a computed regex? 3047db96d56Sopenharmony_ci# [answer: no; circa Python-2.0, Jan 2001] 3057db96d56Sopenharmony_ci# new python: 28961/s 3067db96d56Sopenharmony_ci# old python: 18307/s 3077db96d56Sopenharmony_ci# re: 12820/s 3087db96d56Sopenharmony_ci# regex: 14035/s 3097db96d56Sopenharmony_ci 3107db96d56Sopenharmony_cidef find_prefix_at_end(haystack, needle): 3117db96d56Sopenharmony_ci l = len(needle) - 1 3127db96d56Sopenharmony_ci while l and not haystack.endswith(needle[:l]): 3137db96d56Sopenharmony_ci l -= 1 3147db96d56Sopenharmony_ci return l 315