From 977c707b425ee753d54f3e9010f07ec77ef61274 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Giampaolo=20Rodol=C3=A0?= Date: Mon, 4 Oct 2010 21:08:36 +0000 Subject: [PATCH] Fix issue 6706: adds new handle_accepted() method to asyncore.dispatcher --- Doc/library/asyncore.rst | 76 +++++++++++++++++++++++++++++++++------ Doc/whatsnew/3.2.rst | 8 +++++ Lib/asyncore.py | 17 ++++++--- Lib/smtpd.py | 16 +-------- Lib/test/test_asyncore.py | 33 ++++++++++++++--- Lib/test/test_ftplib.py | 3 +- Lib/test/test_poplib.py | 3 +- Lib/test/test_smtplib.py | 3 +- Lib/test/test_ssl.py | 3 +- Misc/NEWS | 6 ++++ 10 files changed, 126 insertions(+), 42 deletions(-) diff --git a/Doc/library/asyncore.rst b/Doc/library/asyncore.rst index 1ba8e958b0e..1ffa132d435 100644 --- a/Doc/library/asyncore.rst +++ b/Doc/library/asyncore.rst @@ -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 | | | available | +----------------------+----------------------------------------+ - | ``handle_accept()`` | Implied by a read event on a listening | + | ``handle_accepted()``| Implied by a read event on a listening | | | 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 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() @@ -210,10 +222,13 @@ any that have been added to the map during asynchronous service) is closed. .. method:: accept() Accept a connection. The socket must be bound to an address and listening - for connections. The return value is a pair ``(conn, address)`` where - *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. + for connections. The return value can be either ``None`` or a pair + ``(conn, address)`` where *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. + 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() @@ -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 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() 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. -.. _asyncore-example: +.. _asyncore-example-1: asyncore Example basic HTTP client ---------------------------------- @@ -249,7 +271,7 @@ implement its socket handling:: import asyncore, socket - class http_client(asyncore.dispatcher): + class HTTPClient(asyncore.dispatcher): def __init__(self, host, path): asyncore.dispatcher.__init__(self) @@ -273,6 +295,40 @@ implement its socket handling:: sent = self.send(self.buffer) 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() + diff --git a/Doc/whatsnew/3.2.rst b/Doc/whatsnew/3.2.rst index 609511bfdde..533d038cfc7 100644 --- a/Doc/whatsnew/3.2.rst +++ b/Doc/whatsnew/3.2.rst @@ -434,6 +434,14 @@ New, Improved, and Deprecated Modules (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 =============== diff --git a/Lib/asyncore.py b/Lib/asyncore.py index 8745276a80d..a277bddc86a 100644 --- a/Lib/asyncore.py +++ b/Lib/asyncore.py @@ -352,12 +352,15 @@ class dispatcher: # XXX can return either an address pair or None try: conn, addr = self.socket.accept() - return conn, addr + except TypeError: + return None except socket.error as why: - if why.args[0] == EWOULDBLOCK: - pass + if why.args[0] in (EWOULDBLOCK, ECONNABORTED): + return None else: raise + else: + return conn, addr def send(self, data): try: @@ -506,7 +509,13 @@ class dispatcher: self.log_info('unhandled connect event', 'warning') 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): self.log_info('unhandled close event', 'warning') diff --git a/Lib/smtpd.py b/Lib/smtpd.py index 179a1b9d63a..23787fd0c34 100755 --- a/Lib/smtpd.py +++ b/Lib/smtpd.py @@ -421,21 +421,7 @@ class SMTPServer(asyncore.dispatcher): self.__class__.__name__, time.ctime(time.time()), localaddr, remoteaddr), file=DEBUGSTREAM) - def handle_accept(self): - 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 + def handle_accepted(self, conn, addr): print('Incoming connection from %s' % repr(addr), file=DEBUGSTREAM) channel = self.channel_class(self, conn, addr) diff --git a/Lib/test/test_asyncore.py b/Lib/test/test_asyncore.py index ce0a84fa7a5..205efb96420 100644 --- a/Lib/test/test_asyncore.py +++ b/Lib/test/test_asyncore.py @@ -296,7 +296,6 @@ class DispatcherTests(unittest.TestCase): d.handle_read() d.handle_write() d.handle_connect() - d.handle_accept() finally: sys.stdout = stdout @@ -304,8 +303,7 @@ class DispatcherTests(unittest.TestCase): expected = ['warning: unhandled incoming priority event', 'warning: unhandled read event', 'warning: unhandled write event', - 'warning: unhandled connect event', - 'warning: unhandled accept event'] + 'warning: unhandled connect event'] self.assertEqual(lines, expected) def test_issue_8594(self): @@ -451,6 +449,9 @@ class BaseTestHandler(asyncore.dispatcher): def handle_accept(self): 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): raise Exception("handle_connect not supposed to be called") @@ -481,8 +482,7 @@ class TCPServer(asyncore.dispatcher): def address(self): return self.socket.getsockname()[:2] - def handle_accept(self): - sock, addr = self.accept() + def handle_accepted(self, sock, addr): self.handler(sock) def handle_error(self): @@ -546,6 +546,29 @@ class BaseTestAPI(unittest.TestCase): client = BaseClient(server.address) 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): # make sure handle_read is called on data received diff --git a/Lib/test/test_ftplib.py b/Lib/test/test_ftplib.py index 8164ede9767..c9bb06e7c27 100644 --- a/Lib/test/test_ftplib.py +++ b/Lib/test/test_ftplib.py @@ -244,8 +244,7 @@ class DummyFTPServer(asyncore.dispatcher, threading.Thread): self.active = False self.join() - def handle_accept(self): - conn, addr = self.accept() + def handle_accepted(self, conn, addr): self.handler_instance = self.handler(conn) def handle_connect(self): diff --git a/Lib/test/test_poplib.py b/Lib/test/test_poplib.py index 2adc8492823..81af56989fe 100644 --- a/Lib/test/test_poplib.py +++ b/Lib/test/test_poplib.py @@ -144,8 +144,7 @@ class DummyPOP3Server(asyncore.dispatcher, threading.Thread): self.active = False self.join() - def handle_accept(self): - conn, addr = self.accept() + def handle_accepted(self, conn, addr): self.handler_instance = self.handler(conn) def handle_connect(self): diff --git a/Lib/test/test_smtplib.py b/Lib/test/test_smtplib.py index 831f2d78650..230899b3e35 100644 --- a/Lib/test/test_smtplib.py +++ b/Lib/test/test_smtplib.py @@ -379,8 +379,7 @@ class SimSMTPServer(smtpd.SMTPServer): self._extra_features = [] smtpd.SMTPServer.__init__(self, *args, **kw) - def handle_accept(self): - conn, addr = self.accept() + def handle_accepted(self, conn, addr): self._SMTPchannel = SimSMTPChannel(self._extra_features, self, conn, addr) diff --git a/Lib/test/test_ssl.py b/Lib/test/test_ssl.py index 6b79615f284..4f29a647b88 100644 --- a/Lib/test/test_ssl.py +++ b/Lib/test/test_ssl.py @@ -838,8 +838,7 @@ else: asyncore.dispatcher.__init__(self, sock) self.listen(5) - def handle_accept(self): - sock_obj, addr = self.accept() + def handle_accepted(self, sock_obj, addr): if support.verbose: sys.stdout.write(" server: new connection from %s:%s\n" %addr) self.ConnectionHandler(sock_obj, self.certfile) diff --git a/Misc/NEWS b/Misc/NEWS index 1bf3acd67a5..3dfff9b25e9 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -88,6 +88,12 @@ Core and Builtins 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 gname field.