Committing Py3k version of changelist 64080 and 64257, along with updated tests
for smtpd, which required updating with the new semantics.
This commit is contained in:
parent
d51ee54a23
commit
d74900ebb5
|
@ -81,6 +81,12 @@ connection requests.
|
||||||
:exc:`NotImplementedError` exception.
|
:exc:`NotImplementedError` exception.
|
||||||
|
|
||||||
|
|
||||||
|
.. method:: async_chat._collect_incoming_data(data)
|
||||||
|
|
||||||
|
Sample implementation of a data collection rutine to be used in conjunction
|
||||||
|
with :meth:`_get_data` in a user-specified :meth:`found_terminator`.
|
||||||
|
|
||||||
|
|
||||||
.. method:: async_chat.discard_buffers()
|
.. method:: async_chat.discard_buffers()
|
||||||
|
|
||||||
In emergencies this method will discard any data held in the input and/or
|
In emergencies this method will discard any data held in the input and/or
|
||||||
|
@ -95,6 +101,12 @@ connection requests.
|
||||||
should be available via an instance attribute.
|
should be available via an instance attribute.
|
||||||
|
|
||||||
|
|
||||||
|
.. method:: async_chat._get_data()
|
||||||
|
|
||||||
|
Will return and clear the data received with the sample
|
||||||
|
:meth:`_collect_incoming_data` implementation.
|
||||||
|
|
||||||
|
|
||||||
.. method:: async_chat.get_terminator()
|
.. method:: async_chat.get_terminator()
|
||||||
|
|
||||||
Returns the current terminator for the channel.
|
Returns the current terminator for the channel.
|
||||||
|
|
|
@ -222,6 +222,21 @@ any that have been added to the map during asynchronous service) is closed.
|
||||||
flushed). Sockets are automatically closed when they are
|
flushed). Sockets are automatically closed when they are
|
||||||
garbage-collected.
|
garbage-collected.
|
||||||
|
|
||||||
|
.. class:: file_dispatcher()
|
||||||
|
|
||||||
|
A file_dispatcher takes a file descriptor or file object along with an
|
||||||
|
optional map argument and wraps it for use with the :cfunc:`poll` or
|
||||||
|
:cfunc:`loop` functions. If provided a file object or anything with a
|
||||||
|
:cfunc:`fileno` method, that method will be called and passed to the
|
||||||
|
:class:`file_wrapper` constructor. Availability: UNIX.
|
||||||
|
|
||||||
|
.. class:: file_wrapper()
|
||||||
|
|
||||||
|
A file_wrapper takes an integer file descriptor and calls :func:`os.dup` to
|
||||||
|
duplicate the handle so that the original handle may be closed independently
|
||||||
|
of the file_wrapper. This class implements sufficient methods to emulate a
|
||||||
|
socket for use by the :class:`file_dispatcher` class. Availability: UNIX.
|
||||||
|
|
||||||
|
|
||||||
.. _asyncore-example:
|
.. _asyncore-example:
|
||||||
|
|
||||||
|
|
164
Lib/asynchat.py
164
Lib/asynchat.py
|
@ -45,12 +45,23 @@ command will be accumulated (using your own 'collect_incoming_data'
|
||||||
method) up to the terminator, and then control will be returned to
|
method) up to the terminator, and then control will be returned to
|
||||||
you - by calling your self.found_terminator() method.
|
you - by calling your self.found_terminator() method.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import sys
|
|
||||||
import socket
|
import socket
|
||||||
import asyncore
|
import asyncore
|
||||||
from collections import deque
|
from collections import deque
|
||||||
|
|
||||||
|
def buffer(obj, start=None, stop=None):
|
||||||
|
# if memoryview objects gain slicing semantics,
|
||||||
|
# this function will change for the better
|
||||||
|
# memoryview used for the TypeError
|
||||||
|
memoryview(obj)
|
||||||
|
if start == None:
|
||||||
|
start = 0
|
||||||
|
if stop == None:
|
||||||
|
stop = len(obj)
|
||||||
|
x = obj[start:stop]
|
||||||
|
## print("buffer type is: %s"%(type(x),))
|
||||||
|
return x
|
||||||
|
|
||||||
class async_chat (asyncore.dispatcher):
|
class async_chat (asyncore.dispatcher):
|
||||||
"""This is an abstract class. You must derive from this class, and add
|
"""This is an abstract class. You must derive from this class, and add
|
||||||
the two methods collect_incoming_data() and found_terminator()"""
|
the two methods collect_incoming_data() and found_terminator()"""
|
||||||
|
@ -60,20 +71,47 @@ class async_chat (asyncore.dispatcher):
|
||||||
ac_in_buffer_size = 4096
|
ac_in_buffer_size = 4096
|
||||||
ac_out_buffer_size = 4096
|
ac_out_buffer_size = 4096
|
||||||
|
|
||||||
|
# we don't want to enable the use of encoding by default, because that is a
|
||||||
|
# sign of an application bug that we don't want to pass silently
|
||||||
|
|
||||||
|
use_encoding = 0
|
||||||
|
encoding = 'latin1'
|
||||||
|
|
||||||
def __init__ (self, conn=None):
|
def __init__ (self, conn=None):
|
||||||
|
# for string terminator matching
|
||||||
self.ac_in_buffer = b''
|
self.ac_in_buffer = b''
|
||||||
self.ac_out_buffer = b''
|
|
||||||
self.producer_fifo = fifo()
|
# we use a list here rather than cStringIO for a few reasons...
|
||||||
|
# del lst[:] is faster than sio.truncate(0)
|
||||||
|
# lst = [] is faster than sio.truncate(0)
|
||||||
|
# cStringIO will be gaining unicode support in py3k, which
|
||||||
|
# will negatively affect the performance of bytes compared to
|
||||||
|
# a ''.join() equivalent
|
||||||
|
self.incoming = []
|
||||||
|
|
||||||
|
# we toss the use of the "simple producer" and replace it with
|
||||||
|
# a pure deque, which the original fifo was a wrapping of
|
||||||
|
self.producer_fifo = deque()
|
||||||
asyncore.dispatcher.__init__ (self, conn)
|
asyncore.dispatcher.__init__ (self, conn)
|
||||||
|
|
||||||
def collect_incoming_data(self, data):
|
def collect_incoming_data(self, data):
|
||||||
raise NotImplementedError("must be implemented in subclass")
|
raise NotImplementedError("must be implemented in subclass")
|
||||||
|
|
||||||
|
def _collect_incoming_data(self, data):
|
||||||
|
self.incoming.append(data)
|
||||||
|
|
||||||
|
def _get_data(self):
|
||||||
|
d = b''.join(self.incoming)
|
||||||
|
del self.incoming[:]
|
||||||
|
return d
|
||||||
|
|
||||||
def found_terminator(self):
|
def found_terminator(self):
|
||||||
raise NotImplementedError("must be implemented in subclass")
|
raise NotImplementedError("must be implemented in subclass")
|
||||||
|
|
||||||
def set_terminator (self, term):
|
def set_terminator (self, term):
|
||||||
"Set the input delimiter. Can be a fixed string of any length, an integer, or None"
|
"Set the input delimiter. Can be a fixed string of any length, an integer, or None"
|
||||||
|
if isinstance(term, str) and self.use_encoding:
|
||||||
|
term = bytes(term, self.encoding)
|
||||||
self.terminator = term
|
self.terminator = term
|
||||||
|
|
||||||
def get_terminator (self):
|
def get_terminator (self):
|
||||||
|
@ -92,14 +130,14 @@ class async_chat (asyncore.dispatcher):
|
||||||
self.handle_error()
|
self.handle_error()
|
||||||
return
|
return
|
||||||
|
|
||||||
if isinstance(data, str):
|
if isinstance(data, str) and self.use_encoding:
|
||||||
data = data.encode('ascii')
|
data = bytes(str, self.encoding)
|
||||||
self.ac_in_buffer = self.ac_in_buffer + bytes(data)
|
self.ac_in_buffer = self.ac_in_buffer + data
|
||||||
|
|
||||||
# Continue to search for self.terminator in self.ac_in_buffer,
|
# Continue to search for self.terminator in self.ac_in_buffer,
|
||||||
# while calling self.collect_incoming_data. The while loop
|
# while calling self.collect_incoming_data. The while loop
|
||||||
# is necessary because we might read several data+terminator
|
# is necessary because we might read several data+terminator
|
||||||
# combos with a single recv(1024).
|
# combos with a single recv(4096).
|
||||||
|
|
||||||
while self.ac_in_buffer:
|
while self.ac_in_buffer:
|
||||||
lb = len(self.ac_in_buffer)
|
lb = len(self.ac_in_buffer)
|
||||||
|
@ -108,7 +146,7 @@ class async_chat (asyncore.dispatcher):
|
||||||
# no terminator, collect it all
|
# no terminator, collect it all
|
||||||
self.collect_incoming_data (self.ac_in_buffer)
|
self.collect_incoming_data (self.ac_in_buffer)
|
||||||
self.ac_in_buffer = b''
|
self.ac_in_buffer = b''
|
||||||
elif isinstance(terminator, int) or isinstance(terminator, int):
|
elif isinstance(terminator, int):
|
||||||
# numeric terminator
|
# numeric terminator
|
||||||
n = terminator
|
n = terminator
|
||||||
if lb < n:
|
if lb < n:
|
||||||
|
@ -129,8 +167,6 @@ class async_chat (asyncore.dispatcher):
|
||||||
# 3) end of buffer does not match any prefix:
|
# 3) end of buffer does not match any prefix:
|
||||||
# collect data
|
# collect data
|
||||||
terminator_len = len(terminator)
|
terminator_len = len(terminator)
|
||||||
if isinstance(terminator, str):
|
|
||||||
terminator = terminator.encode('ascii')
|
|
||||||
index = self.ac_in_buffer.find(terminator)
|
index = self.ac_in_buffer.find(terminator)
|
||||||
if index != -1:
|
if index != -1:
|
||||||
# we found the terminator
|
# we found the terminator
|
||||||
|
@ -161,85 +197,81 @@ class async_chat (asyncore.dispatcher):
|
||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
def push (self, data):
|
def push (self, data):
|
||||||
self.producer_fifo.push (simple_producer (data))
|
sabs = self.ac_out_buffer_size
|
||||||
|
if len(data) > sabs:
|
||||||
|
for i in range(0, len(data), sabs):
|
||||||
|
self.producer_fifo.append(data[i:i+sabs])
|
||||||
|
else:
|
||||||
|
self.producer_fifo.append(data)
|
||||||
self.initiate_send()
|
self.initiate_send()
|
||||||
|
|
||||||
def push_with_producer (self, producer):
|
def push_with_producer (self, producer):
|
||||||
self.producer_fifo.push (producer)
|
self.producer_fifo.append(producer)
|
||||||
self.initiate_send()
|
self.initiate_send()
|
||||||
|
|
||||||
def readable (self):
|
def readable (self):
|
||||||
"predicate for inclusion in the readable for select()"
|
"predicate for inclusion in the readable for select()"
|
||||||
return (len(self.ac_in_buffer) <= self.ac_in_buffer_size)
|
# cannot use the old predicate, it violates the claim of the
|
||||||
|
# set_terminator method.
|
||||||
|
|
||||||
|
# return (len(self.ac_in_buffer) <= self.ac_in_buffer_size)
|
||||||
|
return 1
|
||||||
|
|
||||||
def writable (self):
|
def writable (self):
|
||||||
"predicate for inclusion in the writable for select()"
|
"predicate for inclusion in the writable for select()"
|
||||||
# return len(self.ac_out_buffer) or len(self.producer_fifo) or (not self.connected)
|
return self.producer_fifo or (not self.connected)
|
||||||
# this is about twice as fast, though not as clear.
|
|
||||||
return not (
|
|
||||||
(self.ac_out_buffer == b'') and
|
|
||||||
self.producer_fifo.is_empty() and
|
|
||||||
self.connected
|
|
||||||
)
|
|
||||||
|
|
||||||
def close_when_done (self):
|
def close_when_done (self):
|
||||||
"automatically close this channel once the outgoing queue is empty"
|
"automatically close this channel once the outgoing queue is empty"
|
||||||
self.producer_fifo.push (None)
|
self.producer_fifo.append(None)
|
||||||
|
|
||||||
# refill the outgoing buffer by calling the more() method
|
|
||||||
# of the first producer in the queue
|
|
||||||
def refill_buffer (self):
|
|
||||||
while 1:
|
|
||||||
if len(self.producer_fifo):
|
|
||||||
p = self.producer_fifo.first()
|
|
||||||
# a 'None' in the producer fifo is a sentinel,
|
|
||||||
# telling us to close the channel.
|
|
||||||
if p is None:
|
|
||||||
if not self.ac_out_buffer:
|
|
||||||
self.producer_fifo.pop()
|
|
||||||
self.close()
|
|
||||||
return
|
|
||||||
elif isinstance(p, str) or isinstance(p, bytes):
|
|
||||||
if isinstance(p, str):
|
|
||||||
p = p.encode('ascii')
|
|
||||||
self.producer_fifo.pop()
|
|
||||||
self.ac_out_buffer = self.ac_out_buffer + p
|
|
||||||
return
|
|
||||||
data = p.more()
|
|
||||||
if data:
|
|
||||||
if isinstance(data, str):
|
|
||||||
data = data.encode('ascii')
|
|
||||||
self.ac_out_buffer = self.ac_out_buffer + bytes(data)
|
|
||||||
return
|
|
||||||
else:
|
|
||||||
self.producer_fifo.pop()
|
|
||||||
else:
|
|
||||||
return
|
|
||||||
|
|
||||||
def initiate_send(self):
|
def initiate_send(self):
|
||||||
|
while self.producer_fifo and self.connected:
|
||||||
|
first = self.producer_fifo[0]
|
||||||
|
# handle empty string/buffer or None entry
|
||||||
|
if not first:
|
||||||
|
del self.producer_fifo[0]
|
||||||
|
if first is None:
|
||||||
|
## print("first is None")
|
||||||
|
self.handle_close()
|
||||||
|
return
|
||||||
|
## print("first is not None")
|
||||||
|
|
||||||
|
# handle classic producer behavior
|
||||||
obs = self.ac_out_buffer_size
|
obs = self.ac_out_buffer_size
|
||||||
# try to refill the buffer
|
|
||||||
if (len (self.ac_out_buffer) < obs):
|
|
||||||
self.refill_buffer()
|
|
||||||
|
|
||||||
if self.ac_out_buffer and self.connected:
|
|
||||||
# try to send the buffer
|
|
||||||
try:
|
try:
|
||||||
num_sent = self.send (self.ac_out_buffer[:obs])
|
data = buffer(first, 0, obs)
|
||||||
if num_sent:
|
except TypeError:
|
||||||
self.ac_out_buffer = self.ac_out_buffer[num_sent:]
|
data = first.more()
|
||||||
|
if data:
|
||||||
|
self.producer_fifo.appendleft(data)
|
||||||
|
else:
|
||||||
|
del self.producer_fifo[0]
|
||||||
|
continue
|
||||||
|
|
||||||
except socket.error as why:
|
if isinstance(data, str) and self.use_encoding:
|
||||||
|
data = bytes(data, self.encoding)
|
||||||
|
|
||||||
|
# send the data
|
||||||
|
try:
|
||||||
|
num_sent = self.send(data)
|
||||||
|
except socket.error:
|
||||||
self.handle_error()
|
self.handle_error()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
if num_sent:
|
||||||
|
if num_sent < len(data) or obs < len(first):
|
||||||
|
self.producer_fifo[0] = first[num_sent:]
|
||||||
|
else:
|
||||||
|
del self.producer_fifo[0]
|
||||||
|
# we tried to send some actual data
|
||||||
|
return
|
||||||
|
|
||||||
def discard_buffers (self):
|
def discard_buffers (self):
|
||||||
# Emergencies only!
|
# Emergencies only!
|
||||||
self.ac_in_buffer = b''
|
self.ac_in_buffer = b''
|
||||||
self.ac_out_buffer = b''
|
del self.incoming[:]
|
||||||
while self.producer_fifo:
|
self.producer_fifo.clear()
|
||||||
self.producer_fifo.pop()
|
|
||||||
|
|
||||||
|
|
||||||
class simple_producer:
|
class simple_producer:
|
||||||
|
|
||||||
|
|
157
Lib/asyncore.py
157
Lib/asyncore.py
|
@ -50,23 +50,28 @@ import select
|
||||||
import socket
|
import socket
|
||||||
import sys
|
import sys
|
||||||
import time
|
import time
|
||||||
|
|
||||||
import os
|
import os
|
||||||
from errno import EALREADY, EINPROGRESS, EWOULDBLOCK, ECONNRESET, \
|
from errno import EALREADY, EINPROGRESS, EWOULDBLOCK, ECONNRESET, \
|
||||||
ENOTCONN, ESHUTDOWN, EINTR, EISCONN, errorcode
|
ENOTCONN, ESHUTDOWN, EINTR, EISCONN, EBADF, ECONNABORTED, errorcode
|
||||||
|
|
||||||
try:
|
try:
|
||||||
socket_map
|
socket_map
|
||||||
except NameError:
|
except NameError:
|
||||||
socket_map = {}
|
socket_map = {}
|
||||||
|
|
||||||
|
def _strerror(err):
|
||||||
|
res = os.strerror(err)
|
||||||
|
if res == 'Unknown error':
|
||||||
|
res = errorcode[err]
|
||||||
|
return res
|
||||||
|
|
||||||
class ExitNow(Exception):
|
class ExitNow(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
def read(obj):
|
def read(obj):
|
||||||
try:
|
try:
|
||||||
obj.handle_read_event()
|
obj.handle_read_event()
|
||||||
except ExitNow:
|
except (ExitNow, KeyboardInterrupt, SystemExit):
|
||||||
raise
|
raise
|
||||||
except:
|
except:
|
||||||
obj.handle_error()
|
obj.handle_error()
|
||||||
|
@ -74,7 +79,7 @@ def read(obj):
|
||||||
def write(obj):
|
def write(obj):
|
||||||
try:
|
try:
|
||||||
obj.handle_write_event()
|
obj.handle_write_event()
|
||||||
except ExitNow:
|
except (ExitNow, KeyboardInterrupt, SystemExit):
|
||||||
raise
|
raise
|
||||||
except:
|
except:
|
||||||
obj.handle_error()
|
obj.handle_error()
|
||||||
|
@ -82,7 +87,7 @@ def write(obj):
|
||||||
def _exception(obj):
|
def _exception(obj):
|
||||||
try:
|
try:
|
||||||
obj.handle_expt_event()
|
obj.handle_expt_event()
|
||||||
except ExitNow:
|
except (ExitNow, KeyboardInterrupt, SystemExit):
|
||||||
raise
|
raise
|
||||||
except:
|
except:
|
||||||
obj.handle_error()
|
obj.handle_error()
|
||||||
|
@ -95,7 +100,7 @@ def readwrite(obj, flags):
|
||||||
obj.handle_write_event()
|
obj.handle_write_event()
|
||||||
if flags & (select.POLLERR | select.POLLHUP | select.POLLNVAL):
|
if flags & (select.POLLERR | select.POLLHUP | select.POLLNVAL):
|
||||||
obj.handle_expt_event()
|
obj.handle_expt_event()
|
||||||
except ExitNow:
|
except (ExitNow, KeyboardInterrupt, SystemExit):
|
||||||
raise
|
raise
|
||||||
except:
|
except:
|
||||||
obj.handle_error()
|
obj.handle_error()
|
||||||
|
@ -105,7 +110,7 @@ def poll(timeout=0.0, map=None):
|
||||||
map = socket_map
|
map = socket_map
|
||||||
if map:
|
if map:
|
||||||
r = []; w = []; e = []
|
r = []; w = []; e = []
|
||||||
for fd, obj in map.items():
|
for fd, obj in list(map.items()):
|
||||||
is_r = obj.readable()
|
is_r = obj.readable()
|
||||||
is_w = obj.writable()
|
is_w = obj.writable()
|
||||||
if is_r:
|
if is_r:
|
||||||
|
@ -116,11 +121,12 @@ def poll(timeout=0.0, map=None):
|
||||||
e.append(fd)
|
e.append(fd)
|
||||||
if [] == r == w == e:
|
if [] == r == w == e:
|
||||||
time.sleep(timeout)
|
time.sleep(timeout)
|
||||||
else:
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
r, w, e = select.select(r, w, e, timeout)
|
r, w, e = select.select(r, w, e, timeout)
|
||||||
except select.error as err:
|
except select.error as err:
|
||||||
if err.args[0] != EINTR:
|
if err[0] != EINTR:
|
||||||
raise
|
raise
|
||||||
else:
|
else:
|
||||||
return
|
return
|
||||||
|
@ -152,7 +158,7 @@ def poll2(timeout=0.0, map=None):
|
||||||
timeout = int(timeout*1000)
|
timeout = int(timeout*1000)
|
||||||
pollster = select.poll()
|
pollster = select.poll()
|
||||||
if map:
|
if map:
|
||||||
for fd, obj in map.items():
|
for fd, obj in list(map.items()):
|
||||||
flags = 0
|
flags = 0
|
||||||
if obj.readable():
|
if obj.readable():
|
||||||
flags |= select.POLLIN | select.POLLPRI
|
flags |= select.POLLIN | select.POLLPRI
|
||||||
|
@ -166,7 +172,7 @@ def poll2(timeout=0.0, map=None):
|
||||||
try:
|
try:
|
||||||
r = pollster.poll(timeout)
|
r = pollster.poll(timeout)
|
||||||
except select.error as err:
|
except select.error as err:
|
||||||
if err.args[0] != EINTR:
|
if err[0] != EINTR:
|
||||||
raise
|
raise
|
||||||
r = []
|
r = []
|
||||||
for fd, flags in r:
|
for fd, flags in r:
|
||||||
|
@ -209,18 +215,29 @@ class dispatcher:
|
||||||
else:
|
else:
|
||||||
self._map = map
|
self._map = map
|
||||||
|
|
||||||
|
self._fileno = None
|
||||||
|
|
||||||
if sock:
|
if sock:
|
||||||
|
# Set to nonblocking just to make sure for cases where we
|
||||||
|
# get a socket from a blocking source.
|
||||||
|
sock.setblocking(0)
|
||||||
self.set_socket(sock, map)
|
self.set_socket(sock, map)
|
||||||
# I think it should inherit this anyway
|
|
||||||
self.socket.setblocking(0)
|
|
||||||
self.connected = True
|
self.connected = True
|
||||||
# XXX Does the constructor require that the socket passed
|
# The constructor no longer requires that the socket
|
||||||
# be connected?
|
# passed be connected.
|
||||||
try:
|
try:
|
||||||
self.addr = sock.getpeername()
|
self.addr = sock.getpeername()
|
||||||
except socket.error:
|
except socket.error as err:
|
||||||
# The addr isn't crucial
|
if err[0] == ENOTCONN:
|
||||||
pass
|
# To handle the case where we got an unconnected
|
||||||
|
# socket.
|
||||||
|
self.connected = False
|
||||||
|
else:
|
||||||
|
# The socket is broken in some unknown way, alert
|
||||||
|
# the user and remove it from the map (to prevent
|
||||||
|
# polling of broken sockets).
|
||||||
|
self.del_channel(map)
|
||||||
|
raise
|
||||||
else:
|
else:
|
||||||
self.socket = None
|
self.socket = None
|
||||||
|
|
||||||
|
@ -254,10 +271,9 @@ class dispatcher:
|
||||||
|
|
||||||
def create_socket(self, family, type):
|
def create_socket(self, family, type):
|
||||||
self.family_and_type = family, type
|
self.family_and_type = family, type
|
||||||
self.socket = socket.socket(family, type)
|
sock = socket.socket(family, type)
|
||||||
self.socket.setblocking(0)
|
sock.setblocking(0)
|
||||||
self._fileno = self.socket.fileno()
|
self.set_socket(sock)
|
||||||
self.add_channel()
|
|
||||||
|
|
||||||
def set_socket(self, sock, map=None):
|
def set_socket(self, sock, map=None):
|
||||||
self.socket = sock
|
self.socket = sock
|
||||||
|
@ -295,7 +311,7 @@ class dispatcher:
|
||||||
def listen(self, num):
|
def listen(self, num):
|
||||||
self.accepting = True
|
self.accepting = True
|
||||||
if os.name == 'nt' and num > 5:
|
if os.name == 'nt' and num > 5:
|
||||||
num = 1
|
num = 5
|
||||||
return self.socket.listen(num)
|
return self.socket.listen(num)
|
||||||
|
|
||||||
def bind(self, addr):
|
def bind(self, addr):
|
||||||
|
@ -310,8 +326,7 @@ class dispatcher:
|
||||||
return
|
return
|
||||||
if err in (0, EISCONN):
|
if err in (0, EISCONN):
|
||||||
self.addr = address
|
self.addr = address
|
||||||
self.connected = True
|
self.handle_connect_event()
|
||||||
self.handle_connect()
|
|
||||||
else:
|
else:
|
||||||
raise socket.error(err, errorcode[err])
|
raise socket.error(err, errorcode[err])
|
||||||
|
|
||||||
|
@ -321,7 +336,7 @@ class dispatcher:
|
||||||
conn, addr = self.socket.accept()
|
conn, addr = self.socket.accept()
|
||||||
return conn, addr
|
return conn, addr
|
||||||
except socket.error as why:
|
except socket.error as why:
|
||||||
if why.args[0] == EWOULDBLOCK:
|
if why[0] == EWOULDBLOCK:
|
||||||
pass
|
pass
|
||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
|
@ -331,11 +346,13 @@ class dispatcher:
|
||||||
result = self.socket.send(data)
|
result = self.socket.send(data)
|
||||||
return result
|
return result
|
||||||
except socket.error as why:
|
except socket.error as why:
|
||||||
if why.args[0] == EWOULDBLOCK:
|
if why[0] == EWOULDBLOCK:
|
||||||
|
return 0
|
||||||
|
elif why[0] in (ECONNRESET, ENOTCONN, ESHUTDOWN, ECONNABORTED):
|
||||||
|
self.handle_close()
|
||||||
return 0
|
return 0
|
||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
return 0
|
|
||||||
|
|
||||||
def recv(self, buffer_size):
|
def recv(self, buffer_size):
|
||||||
try:
|
try:
|
||||||
|
@ -349,15 +366,21 @@ class dispatcher:
|
||||||
return data
|
return data
|
||||||
except socket.error as why:
|
except socket.error as why:
|
||||||
# winsock sometimes throws ENOTCONN
|
# winsock sometimes throws ENOTCONN
|
||||||
if why.args[0] in [ECONNRESET, ENOTCONN, ESHUTDOWN]:
|
if why[0] in [ECONNRESET, ENOTCONN, ESHUTDOWN, ECONNABORTED]:
|
||||||
self.handle_close()
|
self.handle_close()
|
||||||
return b''
|
return b''
|
||||||
else:
|
else:
|
||||||
raise
|
raise
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
|
self.connected = False
|
||||||
|
self.accepting = False
|
||||||
self.del_channel()
|
self.del_channel()
|
||||||
|
try:
|
||||||
self.socket.close()
|
self.socket.close()
|
||||||
|
except socket.error as why:
|
||||||
|
if why[0] not in (ENOTCONN, EBADF):
|
||||||
|
raise
|
||||||
|
|
||||||
# cheap inheritance, used to pass all other attribute
|
# cheap inheritance, used to pass all other attribute
|
||||||
# references to the underlying socket object.
|
# references to the underlying socket object.
|
||||||
|
@ -377,26 +400,52 @@ class dispatcher:
|
||||||
|
|
||||||
def handle_read_event(self):
|
def handle_read_event(self):
|
||||||
if self.accepting:
|
if self.accepting:
|
||||||
# for an accepting socket, getting a read implies
|
# accepting sockets are never connected, they "spawn" new
|
||||||
# that we are connected
|
# sockets that are connected
|
||||||
if not self.connected:
|
|
||||||
self.connected = True
|
|
||||||
self.handle_accept()
|
self.handle_accept()
|
||||||
elif not self.connected:
|
elif not self.connected:
|
||||||
self.handle_connect()
|
self.handle_connect_event()
|
||||||
self.connected = True
|
|
||||||
self.handle_read()
|
self.handle_read()
|
||||||
else:
|
else:
|
||||||
self.handle_read()
|
self.handle_read()
|
||||||
|
|
||||||
def handle_write_event(self):
|
def handle_connect_event(self):
|
||||||
# getting a write implies that we are connected
|
|
||||||
if not self.connected:
|
|
||||||
self.handle_connect()
|
|
||||||
self.connected = True
|
self.connected = True
|
||||||
|
self.handle_connect()
|
||||||
|
|
||||||
|
def handle_write_event(self):
|
||||||
|
if self.accepting:
|
||||||
|
# Accepting sockets shouldn't get a write event.
|
||||||
|
# We will pretend it didn't happen.
|
||||||
|
return
|
||||||
|
|
||||||
|
if not self.connected:
|
||||||
|
#check for errors
|
||||||
|
err = self.socket.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR)
|
||||||
|
if err != 0:
|
||||||
|
raise socket.error(err, _strerror(err))
|
||||||
|
|
||||||
|
self.handle_connect_event()
|
||||||
self.handle_write()
|
self.handle_write()
|
||||||
|
|
||||||
def handle_expt_event(self):
|
def handle_expt_event(self):
|
||||||
|
# if the handle_expt is the same default worthless method,
|
||||||
|
# we'll not even bother calling it, we'll instead generate
|
||||||
|
# a useful error
|
||||||
|
x = True
|
||||||
|
try:
|
||||||
|
y1 = self.handle_expt.__func__
|
||||||
|
y2 = dispatcher.handle_expt
|
||||||
|
x = y1 is y2
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if x:
|
||||||
|
err = self.socket.getsockopt(socket.SOL_SOCKET, socket.SO_ERROR)
|
||||||
|
msg = _strerror(err)
|
||||||
|
|
||||||
|
raise socket.error(err, msg)
|
||||||
|
else:
|
||||||
self.handle_expt()
|
self.handle_expt()
|
||||||
|
|
||||||
def handle_error(self):
|
def handle_error(self):
|
||||||
|
@ -461,7 +510,6 @@ class dispatcher_with_send(dispatcher):
|
||||||
return (not self.connected) or len(self.out_buffer)
|
return (not self.connected) or len(self.out_buffer)
|
||||||
|
|
||||||
def send(self, data):
|
def send(self, data):
|
||||||
assert isinstance(data, bytes)
|
|
||||||
if self.debug:
|
if self.debug:
|
||||||
self.log_info('sending %s' % repr(data))
|
self.log_info('sending %s' % repr(data))
|
||||||
self.out_buffer = self.out_buffer + data
|
self.out_buffer = self.out_buffer + data
|
||||||
|
@ -474,7 +522,8 @@ class dispatcher_with_send(dispatcher):
|
||||||
def compact_traceback():
|
def compact_traceback():
|
||||||
t, v, tb = sys.exc_info()
|
t, v, tb = sys.exc_info()
|
||||||
tbinfo = []
|
tbinfo = []
|
||||||
assert tb # Must have a traceback
|
if not tb: # Must have a traceback
|
||||||
|
raise AssertionError("traceback does not exist")
|
||||||
while tb:
|
while tb:
|
||||||
tbinfo.append((
|
tbinfo.append((
|
||||||
tb.tb_frame.f_code.co_filename,
|
tb.tb_frame.f_code.co_filename,
|
||||||
|
@ -490,11 +539,22 @@ def compact_traceback():
|
||||||
info = ' '.join(['[%s|%s|%s]' % x for x in tbinfo])
|
info = ' '.join(['[%s|%s|%s]' % x for x in tbinfo])
|
||||||
return (file, function, line), t, v, info
|
return (file, function, line), t, v, info
|
||||||
|
|
||||||
def close_all(map=None):
|
def close_all(map=None, ignore_all=False):
|
||||||
if map is None:
|
if map is None:
|
||||||
map = socket_map
|
map = socket_map
|
||||||
for x in map.values():
|
for x in list(map.values()):
|
||||||
x.socket.close()
|
try:
|
||||||
|
x.close()
|
||||||
|
except OSError as x:
|
||||||
|
if x[0] == EBADF:
|
||||||
|
pass
|
||||||
|
elif not ignore_all:
|
||||||
|
raise
|
||||||
|
except (ExitNow, KeyboardInterrupt, SystemExit):
|
||||||
|
raise
|
||||||
|
except:
|
||||||
|
if not ignore_all:
|
||||||
|
raise
|
||||||
map.clear()
|
map.clear()
|
||||||
|
|
||||||
# Asynchronous File I/O:
|
# Asynchronous File I/O:
|
||||||
|
@ -514,11 +574,12 @@ if os.name == 'posix':
|
||||||
import fcntl
|
import fcntl
|
||||||
|
|
||||||
class file_wrapper:
|
class file_wrapper:
|
||||||
# here we override just enough to make a file
|
# Here we override just enough to make a file
|
||||||
# look like a socket for the purposes of asyncore.
|
# look like a socket for the purposes of asyncore.
|
||||||
|
# The passed fd is automatically os.dup()'d
|
||||||
|
|
||||||
def __init__(self, fd):
|
def __init__(self, fd):
|
||||||
self.fd = fd
|
self.fd = os.dup(fd)
|
||||||
|
|
||||||
def recv(self, *args):
|
def recv(self, *args):
|
||||||
return os.read(self.fd, *args)
|
return os.read(self.fd, *args)
|
||||||
|
@ -540,6 +601,10 @@ if os.name == 'posix':
|
||||||
def __init__(self, fd, map=None):
|
def __init__(self, fd, map=None):
|
||||||
dispatcher.__init__(self, None, map)
|
dispatcher.__init__(self, None, map)
|
||||||
self.connected = True
|
self.connected = True
|
||||||
|
try:
|
||||||
|
fd = fd.fileno()
|
||||||
|
except AttributeError:
|
||||||
|
pass
|
||||||
self.set_file(fd)
|
self.set_file(fd)
|
||||||
# set it to non-blocking mode
|
# set it to non-blocking mode
|
||||||
flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0)
|
flags = fcntl.fcntl(fd, fcntl.F_GETFL, 0)
|
||||||
|
|
|
@ -124,11 +124,11 @@ class SMTPChannel(asynchat.async_chat):
|
||||||
self.__peer = conn.getpeername()
|
self.__peer = conn.getpeername()
|
||||||
print('Peer:', repr(self.__peer), file=DEBUGSTREAM)
|
print('Peer:', repr(self.__peer), file=DEBUGSTREAM)
|
||||||
self.push('220 %s %s' % (self.__fqdn, __version__))
|
self.push('220 %s %s' % (self.__fqdn, __version__))
|
||||||
self.set_terminator('\r\n')
|
self.set_terminator(b'\r\n')
|
||||||
|
|
||||||
# Overrides base class for convenience
|
# Overrides base class for convenience
|
||||||
def push(self, msg):
|
def push(self, msg):
|
||||||
asynchat.async_chat.push(self, msg + '\r\n')
|
asynchat.async_chat.push(self, bytes(msg + '\r\n', 'ascii'))
|
||||||
|
|
||||||
# Implementation of base class abstract method
|
# Implementation of base class abstract method
|
||||||
def collect_incoming_data(self, data):
|
def collect_incoming_data(self, data):
|
||||||
|
@ -177,7 +177,7 @@ class SMTPChannel(asynchat.async_chat):
|
||||||
self.__rcpttos = []
|
self.__rcpttos = []
|
||||||
self.__mailfrom = None
|
self.__mailfrom = None
|
||||||
self.__state = self.COMMAND
|
self.__state = self.COMMAND
|
||||||
self.set_terminator('\r\n')
|
self.set_terminator(b'\r\n')
|
||||||
if not status:
|
if not status:
|
||||||
self.push('250 Ok')
|
self.push('250 Ok')
|
||||||
else:
|
else:
|
||||||
|
@ -264,7 +264,7 @@ class SMTPChannel(asynchat.async_chat):
|
||||||
self.push('501 Syntax: DATA')
|
self.push('501 Syntax: DATA')
|
||||||
return
|
return
|
||||||
self.__state = self.DATA
|
self.__state = self.DATA
|
||||||
self.set_terminator('\r\n.\r\n')
|
self.set_terminator(b'\r\n.\r\n')
|
||||||
self.push('354 End data with <CR><LF>.<CR><LF>')
|
self.push('354 End data with <CR><LF>.<CR><LF>')
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -105,8 +105,8 @@ class TestAsynchat(unittest.TestCase):
|
||||||
time.sleep(0.01) # Give server time to start accepting.
|
time.sleep(0.01) # Give server time to start accepting.
|
||||||
c = echo_client(term, s.port)
|
c = echo_client(term, s.port)
|
||||||
c.push(b"hello ")
|
c.push(b"hello ")
|
||||||
c.push(bytes("world%s" % term, "ascii"))
|
c.push(b"world" + term)
|
||||||
c.push(bytes("I'm not dead yet!%s" % term, "ascii"))
|
c.push(b"I'm not dead yet!" + term)
|
||||||
c.push(SERVER_QUIT)
|
c.push(SERVER_QUIT)
|
||||||
asyncore.loop(use_poll=self.usepoll, count=300, timeout=.01)
|
asyncore.loop(use_poll=self.usepoll, count=300, timeout=.01)
|
||||||
s.join()
|
s.join()
|
||||||
|
@ -120,17 +120,17 @@ class TestAsynchat(unittest.TestCase):
|
||||||
def test_line_terminator1(self):
|
def test_line_terminator1(self):
|
||||||
# test one-character terminator
|
# test one-character terminator
|
||||||
for l in (1,2,3):
|
for l in (1,2,3):
|
||||||
self.line_terminator_check('\n', l)
|
self.line_terminator_check(b'\n', l)
|
||||||
|
|
||||||
def test_line_terminator2(self):
|
def test_line_terminator2(self):
|
||||||
# test two-character terminator
|
# test two-character terminator
|
||||||
for l in (1,2,3):
|
for l in (1,2,3):
|
||||||
self.line_terminator_check('\r\n', l)
|
self.line_terminator_check(b'\r\n', l)
|
||||||
|
|
||||||
def test_line_terminator3(self):
|
def test_line_terminator3(self):
|
||||||
# test three-character terminator
|
# test three-character terminator
|
||||||
for l in (1,2,3):
|
for l in (1,2,3):
|
||||||
self.line_terminator_check('qqq', l)
|
self.line_terminator_check(b'qqq', l)
|
||||||
|
|
||||||
def numeric_terminator_check(self, termlen):
|
def numeric_terminator_check(self, termlen):
|
||||||
# Try reading a fixed number of bytes
|
# Try reading a fixed number of bytes
|
||||||
|
@ -190,7 +190,7 @@ class TestAsynchat(unittest.TestCase):
|
||||||
# checks that empty lines are handled correctly
|
# checks that empty lines are handled correctly
|
||||||
s, event = start_echo_server()
|
s, event = start_echo_server()
|
||||||
c = echo_client(b'\n', s.port)
|
c = echo_client(b'\n', s.port)
|
||||||
c.push("hello world\n\nI'm not dead yet!\n")
|
c.push(b"hello world\n\nI'm not dead yet!\n")
|
||||||
c.push(SERVER_QUIT)
|
c.push(SERVER_QUIT)
|
||||||
asyncore.loop(use_poll=self.usepoll, count=300, timeout=.01)
|
asyncore.loop(use_poll=self.usepoll, count=300, timeout=.01)
|
||||||
s.join()
|
s.join()
|
||||||
|
@ -201,7 +201,7 @@ class TestAsynchat(unittest.TestCase):
|
||||||
def test_close_when_done(self):
|
def test_close_when_done(self):
|
||||||
s, event = start_echo_server()
|
s, event = start_echo_server()
|
||||||
c = echo_client(b'\n', s.port)
|
c = echo_client(b'\n', s.port)
|
||||||
c.push("hello world\nI'm not dead yet!\n")
|
c.push(b"hello world\nI'm not dead yet!\n")
|
||||||
c.push(SERVER_QUIT)
|
c.push(SERVER_QUIT)
|
||||||
c.close_when_done()
|
c.close_when_done()
|
||||||
asyncore.loop(use_poll=self.usepoll, count=300, timeout=.01)
|
asyncore.loop(use_poll=self.usepoll, count=300, timeout=.01)
|
||||||
|
|
|
@ -28,6 +28,9 @@ class dummychannel:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.socket = dummysocket()
|
self.socket = dummysocket()
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
self.socket.close()
|
||||||
|
|
||||||
class exitingdummy:
|
class exitingdummy:
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
pass
|
pass
|
||||||
|
@ -382,8 +385,8 @@ if hasattr(asyncore, 'file_wrapper'):
|
||||||
fd = os.open(TESTFN, os.O_RDONLY)
|
fd = os.open(TESTFN, os.O_RDONLY)
|
||||||
w = asyncore.file_wrapper(fd)
|
w = asyncore.file_wrapper(fd)
|
||||||
|
|
||||||
self.assertEqual(w.fd, fd)
|
self.assertNotEqual(w.fd, fd)
|
||||||
self.assertEqual(w.fileno(), fd)
|
self.assertNotEqual(w.fileno(), fd)
|
||||||
self.assertEqual(w.recv(13), b"It's not dead")
|
self.assertEqual(w.recv(13), b"It's not dead")
|
||||||
self.assertEqual(w.read(6), b", it's")
|
self.assertEqual(w.read(6), b", it's")
|
||||||
w.close()
|
w.close()
|
||||||
|
|
|
@ -14,6 +14,14 @@ from test import support
|
||||||
|
|
||||||
HOST = support.HOST
|
HOST = support.HOST
|
||||||
|
|
||||||
|
if sys.platform == 'darwin':
|
||||||
|
# select.poll returns a select.POLLHUP at the end of the tests
|
||||||
|
# on darwin, so just ignore it
|
||||||
|
def handle_expt(self):
|
||||||
|
pass
|
||||||
|
smtpd.SMTPChannel.handle_expt = handle_expt
|
||||||
|
|
||||||
|
|
||||||
def server(evt, buf, serv):
|
def server(evt, buf, serv):
|
||||||
serv.listen(5)
|
serv.listen(5)
|
||||||
evt.set()
|
evt.set()
|
||||||
|
|
Loading…
Reference in New Issue