From b79f82aa6228e07760820f07111e8d0430a4a20c Mon Sep 17 00:00:00 2001 From: Ben Darnell Date: Sat, 7 Mar 2015 23:12:41 -0500 Subject: [PATCH] Allow TCPServer.handle_stream to be a coroutine --- tornado/tcpserver.py | 19 +++++++++++++++-- tornado/test/runtests.py | 1 + tornado/test/tcpserver_test.py | 38 ++++++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+), 2 deletions(-) create mode 100644 tornado/test/tcpserver_test.py diff --git a/tornado/tcpserver.py b/tornado/tcpserver.py index 3b59a4ed0..da5ba7bbf 100644 --- a/tornado/tcpserver.py +++ b/tornado/tcpserver.py @@ -213,7 +213,20 @@ class TCPServer(object): sock.close() def handle_stream(self, stream, address): - """Override to handle a new `.IOStream` from an incoming connection.""" + """Override to handle a new `.IOStream` from an incoming connection. + + This method may be a coroutine; if so any exceptions it raises + asynchronously will be logged. Accepting of incoming connections + will not be blocked by this coroutine. + + If this `TCPServer` is configured for SSL, ``handle_stream`` + may be called before the SSL handshake has completed. Use + `.SSLIOStream.wait_for_handshake` if you need to verify the client's + certificate or use NPN/ALPN. + + .. versionchanged:: 4.2 + Added the option for this method to be a coroutine. + """ raise NotImplementedError() def _handle_connection(self, connection, address): @@ -253,6 +266,8 @@ class TCPServer(object): stream = IOStream(connection, io_loop=self.io_loop, max_buffer_size=self.max_buffer_size, read_chunk_size=self.read_chunk_size) - self.handle_stream(stream, address) + future = self.handle_stream(stream, address) + if future is not None: + self.io_loop.add_future(future, lambda f: f.result()) except Exception: app_log.error("Error in connection callback", exc_info=True) diff --git a/tornado/test/runtests.py b/tornado/test/runtests.py index 1215cc920..20133d4e2 100644 --- a/tornado/test/runtests.py +++ b/tornado/test/runtests.py @@ -43,6 +43,7 @@ TEST_MODULES = [ 'tornado.test.simple_httpclient_test', 'tornado.test.stack_context_test', 'tornado.test.tcpclient_test', + 'tornado.test.tcpserver_test', 'tornado.test.template_test', 'tornado.test.testing_test', 'tornado.test.twisted_test', diff --git a/tornado/test/tcpserver_test.py b/tornado/test/tcpserver_test.py new file mode 100644 index 000000000..84c950769 --- /dev/null +++ b/tornado/test/tcpserver_test.py @@ -0,0 +1,38 @@ +import socket + +from tornado import gen +from tornado.iostream import IOStream +from tornado.log import app_log +from tornado.stack_context import NullContext +from tornado.tcpserver import TCPServer +from tornado.testing import AsyncTestCase, ExpectLog, bind_unused_port, gen_test + + +class TCPServerTest(AsyncTestCase): + @gen_test + def test_handle_stream_coroutine_logging(self): + # handle_stream may be a coroutine and any exception in its + # Future will be logged. + class TestServer(TCPServer): + @gen.coroutine + def handle_stream(self, stream, address): + yield gen.moment + stream.close() + 1/0 + + server = client = None + try: + sock, port = bind_unused_port() + with NullContext(): + server = TestServer() + server.add_socket(sock) + client = IOStream(socket.socket()) + with ExpectLog(app_log, "Exception in callback"): + yield client.connect(('localhost', port)) + yield client.read_until_close() + yield gen.moment + finally: + if server is not None: + server.stop() + if client is not None: + client.close() -- 2.47.2