Issues (229)

server/lib/medusa/asynchat.py (1 issue)

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