]> git.ipfire.org Git - thirdparty/tornado.git/commitdiff
Allow TCPServer.handle_stream to be a coroutine
authorBen Darnell <ben@bendarnell.com>
Sun, 8 Mar 2015 04:12:41 +0000 (23:12 -0500)
committerBen Darnell <ben@bendarnell.com>
Sun, 8 Mar 2015 04:12:41 +0000 (23:12 -0500)
tornado/tcpserver.py
tornado/test/runtests.py
tornado/test/tcpserver_test.py [new file with mode: 0644]

index 3b59a4ed0f178df7d1dff60a2e1afb21659b9314..da5ba7bbf1efae364880e20c0ccf172c0963cb11 100644 (file)
@@ -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)
index 1215cc920f0c3f7d4b007f53db43a4af98f511f5..20133d4e2921f6a8654de8dfd74e3fbf87b98c70 100644 (file)
@@ -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 (file)
index 0000000..84c9507
--- /dev/null
@@ -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()