Fix issue 6706: adds new handle_accepted() method to asyncore.dispatcher

This commit is contained in:
Giampaolo Rodolà 2010-10-04 21:08:36 +00:00
parent 4c94c53630
commit 977c707b42
10 changed files with 126 additions and 42 deletions

View File

@ -86,7 +86,7 @@ any that have been added to the map during asynchronous service) is closed.
| ``handle_close()`` | Implied by a read event with no data | | ``handle_close()`` | Implied by a read event with no data |
| | available | | | available |
+----------------------+----------------------------------------+ +----------------------+----------------------------------------+
| ``handle_accept()`` | Implied by a read event on a listening | | ``handle_accepted()``| Implied by a read event on a listening |
| | socket | | | socket |
+----------------------+----------------------------------------+ +----------------------+----------------------------------------+
@ -144,8 +144,20 @@ any that have been added to the map during asynchronous service) is closed.
Called on listening channels (passive openers) when a connection can be Called on listening channels (passive openers) when a connection can be
established with a new remote endpoint that has issued a :meth:`connect` established with a new remote endpoint that has issued a :meth:`connect`
call for the local endpoint. call for the local endpoint. Deprecated in version 3.2; use
:meth:`handle_accepted` instead.
.. deprecated:: 3.2
.. method:: handle_accepted(sock, addr)
Called on listening channels (passive openers) when a connection has been
established with a new remote endpoint that has issued a :meth:`connect`
call for the local endpoint. *conn* is a *new* socket object usable to
send and receive data on the connection, and *address* is the address
bound to the socket on the other end of the connection.
.. versionadded:: 3.2
.. method:: readable() .. method:: readable()
@ -210,10 +222,13 @@ any that have been added to the map during asynchronous service) is closed.
.. method:: accept() .. method:: accept()
Accept a connection. The socket must be bound to an address and listening Accept a connection. The socket must be bound to an address and listening
for connections. The return value is a pair ``(conn, address)`` where for connections. The return value can be either ``None`` or a pair
*conn* is a *new* socket object usable to send and receive data on the ``(conn, address)`` where *conn* is a *new* socket object usable to send
connection, and *address* is the address bound to the socket on the other and receive data on the connection, and *address* is the address bound to
end of the connection. the socket on the other end of the connection.
When ``None`` is returned it means the connection didn't take place, in
which case the server should just ignore this event and keep listening
for further incoming connections.
.. method:: close() .. method:: close()
@ -223,6 +238,13 @@ 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:: dispatcher_with_send()
A :class:`dispatcher` subclass which adds simple buffered output capability,
useful for simple clients. For more sophisticated usage use
:class:`asynchat.async_chat`.
.. class:: file_dispatcher() .. class:: file_dispatcher()
A file_dispatcher takes a file descriptor or :term:`file object` along A file_dispatcher takes a file descriptor or :term:`file object` along
@ -239,7 +261,7 @@ any that have been added to the map during asynchronous service) is closed.
socket for use by the :class:`file_dispatcher` class. Availability: UNIX. socket for use by the :class:`file_dispatcher` class. Availability: UNIX.
.. _asyncore-example: .. _asyncore-example-1:
asyncore Example basic HTTP client asyncore Example basic HTTP client
---------------------------------- ----------------------------------
@ -249,7 +271,7 @@ implement its socket handling::
import asyncore, socket import asyncore, socket
class http_client(asyncore.dispatcher): class HTTPClient(asyncore.dispatcher):
def __init__(self, host, path): def __init__(self, host, path):
asyncore.dispatcher.__init__(self) asyncore.dispatcher.__init__(self)
@ -273,6 +295,40 @@ implement its socket handling::
sent = self.send(self.buffer) sent = self.send(self.buffer)
self.buffer = self.buffer[sent:] self.buffer = self.buffer[sent:]
c = http_client('www.python.org', '/')
asyncore.loop() client = HTTPClient('www.python.org', '/')
asyncore.loop()
.. _asyncore-example-2:
asyncore Example basic echo server
----------------------------------
Here is abasic echo server that uses the :class:`dispatcher` class to accept
connections and dispatches the incoming connections to a handler::
import asyncore
import socket
class EchoHandler(asyncore.dispatcher_with_send):
def handle_read(self):
data = self.recv(8192)
self.send(data)
class EchoServer(asyncore.dispatcher):
def __init__(self, host, port):
asyncore.dispatcher.__init__(self)
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
self.set_reuse_addr()
self.bind((host, port))
self.listen(5)
def handle_accepted(self, sock, addr):
print('Incoming connection from %s' % repr(addr))
handler = EchoHandler(sock)
server = EchoServer('localhost', 8080)
asyncore.loop()

View File

@ -434,6 +434,14 @@ New, Improved, and Deprecated Modules
(Contributed by Giampaolo Rodolà; :issue:`9794`.) (Contributed by Giampaolo Rodolà; :issue:`9794`.)
* :class:`asyncore.dispatcher` now provides a
:meth:`~asyncore.dispatcher.handle_accepted()` method
returning a `(sock, addr)` pair which is called when a connection has actually
been established with a new remote endpoint. This is supposed to be used as a
replacement for old :meth:`~asyncore.dispatcher.handle_accept()` and avoids
the user to call :meth:`~asyncore.dispatcher.accept()` directly.
(Contributed by Giampaolo Rodolà; :issue:`6706`.)
Multi-threading Multi-threading
=============== ===============

View File

@ -352,12 +352,15 @@ class dispatcher:
# XXX can return either an address pair or None # XXX can return either an address pair or None
try: try:
conn, addr = self.socket.accept() conn, addr = self.socket.accept()
return conn, addr except TypeError:
return None
except socket.error as why: except socket.error as why:
if why.args[0] == EWOULDBLOCK: if why.args[0] in (EWOULDBLOCK, ECONNABORTED):
pass return None
else: else:
raise raise
else:
return conn, addr
def send(self, data): def send(self, data):
try: try:
@ -506,7 +509,13 @@ class dispatcher:
self.log_info('unhandled connect event', 'warning') self.log_info('unhandled connect event', 'warning')
def handle_accept(self): def handle_accept(self):
self.log_info('unhandled accept event', 'warning') pair = self.accept()
if pair is not None:
self.handle_accepted(*pair)
def handle_accepted(self, sock, addr):
sock.close()
self.log_info('unhandled accepted event', 'warning')
def handle_close(self): def handle_close(self):
self.log_info('unhandled close event', 'warning') self.log_info('unhandled close event', 'warning')

View File

@ -421,21 +421,7 @@ class SMTPServer(asyncore.dispatcher):
self.__class__.__name__, time.ctime(time.time()), self.__class__.__name__, time.ctime(time.time()),
localaddr, remoteaddr), file=DEBUGSTREAM) localaddr, remoteaddr), file=DEBUGSTREAM)
def handle_accept(self): def handle_accepted(self, conn, addr):
try:
conn, addr = self.accept()
except TypeError:
# sometimes accept() might return None
return
except socket.error as err:
# ECONNABORTED might be thrown
if err.args[0] != errno.ECONNABORTED:
raise
return
else:
# sometimes addr == None instead of (ip, port)
if addr == None:
return
print('Incoming connection from %s' % repr(addr), file=DEBUGSTREAM) print('Incoming connection from %s' % repr(addr), file=DEBUGSTREAM)
channel = self.channel_class(self, conn, addr) channel = self.channel_class(self, conn, addr)

View File

@ -296,7 +296,6 @@ class DispatcherTests(unittest.TestCase):
d.handle_read() d.handle_read()
d.handle_write() d.handle_write()
d.handle_connect() d.handle_connect()
d.handle_accept()
finally: finally:
sys.stdout = stdout sys.stdout = stdout
@ -304,8 +303,7 @@ class DispatcherTests(unittest.TestCase):
expected = ['warning: unhandled incoming priority event', expected = ['warning: unhandled incoming priority event',
'warning: unhandled read event', 'warning: unhandled read event',
'warning: unhandled write event', 'warning: unhandled write event',
'warning: unhandled connect event', 'warning: unhandled connect event']
'warning: unhandled accept event']
self.assertEqual(lines, expected) self.assertEqual(lines, expected)
def test_issue_8594(self): def test_issue_8594(self):
@ -451,6 +449,9 @@ class BaseTestHandler(asyncore.dispatcher):
def handle_accept(self): def handle_accept(self):
raise Exception("handle_accept not supposed to be called") raise Exception("handle_accept not supposed to be called")
def handle_accepted(self):
raise Exception("handle_accepted not supposed to be called")
def handle_connect(self): def handle_connect(self):
raise Exception("handle_connect not supposed to be called") raise Exception("handle_connect not supposed to be called")
@ -481,8 +482,7 @@ class TCPServer(asyncore.dispatcher):
def address(self): def address(self):
return self.socket.getsockname()[:2] return self.socket.getsockname()[:2]
def handle_accept(self): def handle_accepted(self, sock, addr):
sock, addr = self.accept()
self.handler(sock) self.handler(sock)
def handle_error(self): def handle_error(self):
@ -546,6 +546,29 @@ class BaseTestAPI(unittest.TestCase):
client = BaseClient(server.address) client = BaseClient(server.address)
self.loop_waiting_for_flag(server) self.loop_waiting_for_flag(server)
def test_handle_accepted(self):
# make sure handle_accepted() is called when a client connects
class TestListener(BaseTestHandler):
def __init__(self):
BaseTestHandler.__init__(self)
self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
self.bind((HOST, 0))
self.listen(5)
self.address = self.socket.getsockname()[:2]
def handle_accept(self):
asyncore.dispatcher.handle_accept(self)
def handle_accepted(self, sock, addr):
self.flag = True
server = TestListener()
client = BaseClient(server.address)
self.loop_waiting_for_flag(server)
def test_handle_read(self): def test_handle_read(self):
# make sure handle_read is called on data received # make sure handle_read is called on data received

View File

@ -244,8 +244,7 @@ class DummyFTPServer(asyncore.dispatcher, threading.Thread):
self.active = False self.active = False
self.join() self.join()
def handle_accept(self): def handle_accepted(self, conn, addr):
conn, addr = self.accept()
self.handler_instance = self.handler(conn) self.handler_instance = self.handler(conn)
def handle_connect(self): def handle_connect(self):

View File

@ -144,8 +144,7 @@ class DummyPOP3Server(asyncore.dispatcher, threading.Thread):
self.active = False self.active = False
self.join() self.join()
def handle_accept(self): def handle_accepted(self, conn, addr):
conn, addr = self.accept()
self.handler_instance = self.handler(conn) self.handler_instance = self.handler(conn)
def handle_connect(self): def handle_connect(self):

View File

@ -379,8 +379,7 @@ class SimSMTPServer(smtpd.SMTPServer):
self._extra_features = [] self._extra_features = []
smtpd.SMTPServer.__init__(self, *args, **kw) smtpd.SMTPServer.__init__(self, *args, **kw)
def handle_accept(self): def handle_accepted(self, conn, addr):
conn, addr = self.accept()
self._SMTPchannel = SimSMTPChannel(self._extra_features, self._SMTPchannel = SimSMTPChannel(self._extra_features,
self, conn, addr) self, conn, addr)

View File

@ -838,8 +838,7 @@ else:
asyncore.dispatcher.__init__(self, sock) asyncore.dispatcher.__init__(self, sock)
self.listen(5) self.listen(5)
def handle_accept(self): def handle_accepted(self, sock_obj, addr):
sock_obj, addr = self.accept()
if support.verbose: if support.verbose:
sys.stdout.write(" server: new connection from %s:%s\n" %addr) sys.stdout.write(" server: new connection from %s:%s\n" %addr)
self.ConnectionHandler(sock_obj, self.certfile) self.ConnectionHandler(sock_obj, self.certfile)

View File

@ -88,6 +88,12 @@ Core and Builtins
Library Library
------- -------
- Issue 6706: asyncore.dispatcher now provides a handle_accepted() method
returning a (sock, addr) pair which is called when a connection has been
established with a new remote endpoint. This is supposed to be used as a
replacement for old handle_accept() and avoids the user to call accept()
directly.
- Issue #9065: tarfile no longer uses "root" as the default for the uname and - Issue #9065: tarfile no longer uses "root" as the default for the uname and
gname field. gname field.