ยปCore Development>Code coverage>Lib/asynchat.py

Python code coverage for Lib/asynchat.py

#countcontent
1n/a# -*- Mode: Python; tab-width: 4 -*-
2n/a# Id: asynchat.py,v 2.26 2000/09/07 22:29:26 rushing Exp
3n/a# Author: Sam Rushing <rushing@nightmare.com>
4n/a
5n/a# ======================================================================
6n/a# Copyright 1996 by Sam Rushing
7n/a#
8n/a# All Rights Reserved
9n/a#
10n/a# Permission to use, copy, modify, and distribute this software and
11n/a# its documentation for any purpose and without fee is hereby
12n/a# granted, provided that the above copyright notice appear in all
13n/a# copies and that both that copyright notice and this permission
14n/a# notice appear in supporting documentation, and that the name of Sam
15n/a# Rushing not be used in advertising or publicity pertaining to
16n/a# distribution of the software without specific, written prior
17n/a# permission.
18n/a#
19n/a# SAM RUSHING DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
20n/a# INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN
21n/a# NO EVENT SHALL SAM RUSHING BE LIABLE FOR ANY SPECIAL, INDIRECT OR
22n/a# CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
23n/a# OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
24n/a# NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
25n/a# CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
26n/a# ======================================================================
27n/a
28n/ar"""A class supporting chat-style (command/response) protocols.
29n/a
30n/aThis class adds support for 'chat' style protocols - where one side
31n/asends a 'command', and the other sends a response (examples would be
32n/athe common internet protocols - smtp, nntp, ftp, etc..).
33n/a
34n/aThe handle_read() method looks at the input stream for the current
35n/a'terminator' (usually '\r\n' for single-line responses, '\r\n.\r\n'
36n/afor multi-line output), calling self.found_terminator() on its
37n/areceipt.
38n/a
39n/afor example:
40n/aSay you build an async nntp client using this class. At the start
41n/aof the connection, you'll have self.terminator set to '\r\n', in
42n/aorder to process the single-line greeting. Just before issuing a
43n/a'LIST' command you'll set it to '\r\n.\r\n'. The output of the LIST
44n/acommand will be accumulated (using your own 'collect_incoming_data'
45n/amethod) up to the terminator, and then control will be returned to
46n/ayou - by calling your self.found_terminator() method.
47n/a"""
48n/aimport asyncore
49n/afrom collections import deque
50n/a
51n/a
52n/aclass async_chat(asyncore.dispatcher):
53n/a """This is an abstract class. You must derive from this class, and add
54n/a the two methods collect_incoming_data() and found_terminator()"""
55n/a
56n/a # these are overridable defaults
57n/a
58n/a ac_in_buffer_size = 65536
59n/a ac_out_buffer_size = 65536
60n/a
61n/a # we don't want to enable the use of encoding by default, because that is a
62n/a # sign of an application bug that we don't want to pass silently
63n/a
64n/a use_encoding = 0
65n/a encoding = 'latin-1'
66n/a
67n/a def __init__(self, sock=None, map=None):
68n/a # for string terminator matching
69n/a self.ac_in_buffer = b''
70n/a
71n/a # we use a list here rather than io.BytesIO for a few reasons...
72n/a # del lst[:] is faster than bio.truncate(0)
73n/a # lst = [] is faster than bio.truncate(0)
74n/a self.incoming = []
75n/a
76n/a # we toss the use of the "simple producer" and replace it with
77n/a # a pure deque, which the original fifo was a wrapping of
78n/a self.producer_fifo = deque()
79n/a asyncore.dispatcher.__init__(self, sock, map)
80n/a
81n/a def collect_incoming_data(self, data):
82n/a raise NotImplementedError("must be implemented in subclass")
83n/a
84n/a def _collect_incoming_data(self, data):
85n/a self.incoming.append(data)
86n/a
87n/a def _get_data(self):
88n/a d = b''.join(self.incoming)
89n/a del self.incoming[:]
90n/a return d
91n/a
92n/a def found_terminator(self):
93n/a raise NotImplementedError("must be implemented in subclass")
94n/a
95n/a def set_terminator(self, term):
96n/a """Set the input delimiter.
97n/a
98n/a Can be a fixed string of any length, an integer, or None.
99n/a """
100n/a if isinstance(term, str) and self.use_encoding:
101n/a term = bytes(term, self.encoding)
102n/a elif isinstance(term, int) and term < 0:
103n/a raise ValueError('the number of received bytes must be positive')
104n/a self.terminator = term
105n/a
106n/a def get_terminator(self):
107n/a return self.terminator
108n/a
109n/a # grab some more data from the socket,
110n/a # throw it to the collector method,
111n/a # check for the terminator,
112n/a # if found, transition to the next state.
113n/a
114n/a def handle_read(self):
115n/a
116n/a try:
117n/a data = self.recv(self.ac_in_buffer_size)
118n/a except BlockingIOError:
119n/a return
120n/a except OSError as why:
121n/a self.handle_error()
122n/a return
123n/a
124n/a if isinstance(data, str) and self.use_encoding:
125n/a data = bytes(str, self.encoding)
126n/a self.ac_in_buffer = self.ac_in_buffer + data
127n/a
128n/a # Continue to search for self.terminator in self.ac_in_buffer,
129n/a # while calling self.collect_incoming_data. The while loop
130n/a # is necessary because we might read several data+terminator
131n/a # combos with a single recv(4096).
132n/a
133n/a while self.ac_in_buffer:
134n/a lb = len(self.ac_in_buffer)
135n/a terminator = self.get_terminator()
136n/a if not terminator:
137n/a # no terminator, collect it all
138n/a self.collect_incoming_data(self.ac_in_buffer)
139n/a self.ac_in_buffer = b''
140n/a elif isinstance(terminator, int):
141n/a # numeric terminator
142n/a n = terminator
143n/a if lb < n:
144n/a self.collect_incoming_data(self.ac_in_buffer)
145n/a self.ac_in_buffer = b''
146n/a self.terminator = self.terminator - lb
147n/a else:
148n/a self.collect_incoming_data(self.ac_in_buffer[:n])
149n/a self.ac_in_buffer = self.ac_in_buffer[n:]
150n/a self.terminator = 0
151n/a self.found_terminator()
152n/a else:
153n/a # 3 cases:
154n/a # 1) end of buffer matches terminator exactly:
155n/a # collect data, transition
156n/a # 2) end of buffer matches some prefix:
157n/a # collect data to the prefix
158n/a # 3) end of buffer does not match any prefix:
159n/a # collect data
160n/a terminator_len = len(terminator)
161n/a index = self.ac_in_buffer.find(terminator)
162n/a if index != -1:
163n/a # we found the terminator
164n/a if index > 0:
165n/a # don't bother reporting the empty string
166n/a # (source of subtle bugs)
167n/a self.collect_incoming_data(self.ac_in_buffer[:index])
168n/a self.ac_in_buffer = self.ac_in_buffer[index+terminator_len:]
169n/a # This does the Right Thing if the terminator
170n/a # is changed here.
171n/a self.found_terminator()
172n/a else:
173n/a # check for a prefix of the terminator
174n/a index = find_prefix_at_end(self.ac_in_buffer, terminator)
175n/a if index:
176n/a if index != lb:
177n/a # we found a prefix, collect up to the prefix
178n/a self.collect_incoming_data(self.ac_in_buffer[:-index])
179n/a self.ac_in_buffer = self.ac_in_buffer[-index:]
180n/a break
181n/a else:
182n/a # no prefix, collect it all
183n/a self.collect_incoming_data(self.ac_in_buffer)
184n/a self.ac_in_buffer = b''
185n/a
186n/a def handle_write(self):
187n/a self.initiate_send()
188n/a
189n/a def handle_close(self):
190n/a self.close()
191n/a
192n/a def push(self, data):
193n/a if not isinstance(data, (bytes, bytearray, memoryview)):
194n/a raise TypeError('data argument must be byte-ish (%r)',
195n/a type(data))
196n/a sabs = self.ac_out_buffer_size
197n/a if len(data) > sabs:
198n/a for i in range(0, len(data), sabs):
199n/a self.producer_fifo.append(data[i:i+sabs])
200n/a else:
201n/a self.producer_fifo.append(data)
202n/a self.initiate_send()
203n/a
204n/a def push_with_producer(self, producer):
205n/a self.producer_fifo.append(producer)
206n/a self.initiate_send()
207n/a
208n/a def readable(self):
209n/a "predicate for inclusion in the readable for select()"
210n/a # cannot use the old predicate, it violates the claim of the
211n/a # set_terminator method.
212n/a
213n/a # return (len(self.ac_in_buffer) <= self.ac_in_buffer_size)
214n/a return 1
215n/a
216n/a def writable(self):
217n/a "predicate for inclusion in the writable for select()"
218n/a return self.producer_fifo or (not self.connected)
219n/a
220n/a def close_when_done(self):
221n/a "automatically close this channel once the outgoing queue is empty"
222n/a self.producer_fifo.append(None)
223n/a
224n/a def initiate_send(self):
225n/a while self.producer_fifo and self.connected:
226n/a first = self.producer_fifo[0]
227n/a # handle empty string/buffer or None entry
228n/a if not first:
229n/a del self.producer_fifo[0]
230n/a if first is None:
231n/a self.handle_close()
232n/a return
233n/a
234n/a # handle classic producer behavior
235n/a obs = self.ac_out_buffer_size
236n/a try:
237n/a data = first[:obs]
238n/a except TypeError:
239n/a data = first.more()
240n/a if data:
241n/a self.producer_fifo.appendleft(data)
242n/a else:
243n/a del self.producer_fifo[0]
244n/a continue
245n/a
246n/a if isinstance(data, str) and self.use_encoding:
247n/a data = bytes(data, self.encoding)
248n/a
249n/a # send the data
250n/a try:
251n/a num_sent = self.send(data)
252n/a except OSError:
253n/a self.handle_error()
254n/a return
255n/a
256n/a if num_sent:
257n/a if num_sent < len(data) or obs < len(first):
258n/a self.producer_fifo[0] = first[num_sent:]
259n/a else:
260n/a del self.producer_fifo[0]
261n/a # we tried to send some actual data
262n/a return
263n/a
264n/a def discard_buffers(self):
265n/a # Emergencies only!
266n/a self.ac_in_buffer = b''
267n/a del self.incoming[:]
268n/a self.producer_fifo.clear()
269n/a
270n/a
271n/aclass simple_producer:
272n/a
273n/a def __init__(self, data, buffer_size=512):
274n/a self.data = data
275n/a self.buffer_size = buffer_size
276n/a
277n/a def more(self):
278n/a if len(self.data) > self.buffer_size:
279n/a result = self.data[:self.buffer_size]
280n/a self.data = self.data[self.buffer_size:]
281n/a return result
282n/a else:
283n/a result = self.data
284n/a self.data = b''
285n/a return result
286n/a
287n/a
288n/a# Given 'haystack', see if any prefix of 'needle' is at its end. This
289n/a# assumes an exact match has already been checked. Return the number of
290n/a# characters matched.
291n/a# for example:
292n/a# f_p_a_e("qwerty\r", "\r\n") => 1
293n/a# f_p_a_e("qwertydkjf", "\r\n") => 0
294n/a# f_p_a_e("qwerty\r\n", "\r\n") => <undefined>
295n/a
296n/a# this could maybe be made faster with a computed regex?
297n/a# [answer: no; circa Python-2.0, Jan 2001]
298n/a# new python: 28961/s
299n/a# old python: 18307/s
300n/a# re: 12820/s
301n/a# regex: 14035/s
302n/a
303n/adef find_prefix_at_end(haystack, needle):
304n/a l = len(needle) - 1
305n/a while l and not haystack.endswith(needle[:l]):
306n/a l -= 1
307n/a return l