]> git.ipfire.org Git - thirdparty/tornado.git/commitdiff
Add on_connection_close to the HTTPMessageDelegate interface.
authorBen Darnell <ben@bendarnell.com>
Sat, 26 Apr 2014 17:06:15 +0000 (13:06 -0400)
committerBen Darnell <ben@bendarnell.com>
Sat, 26 Apr 2014 17:06:15 +0000 (13:06 -0400)
This allows handlers to detect when the connection was closed during
the upload of the request body.

tornado/http1connection.py
tornado/httpserver.py
tornado/httputil.py
tornado/test/web_test.py
tornado/web.py

index 342d9efc859fbc2f0c2f8d317118c56f37f00d9d..a0c125376a6647696dbf95b9350b8c72a0c99b6d 100644 (file)
@@ -91,6 +91,7 @@ class HTTP1Connection(object):
 
     @gen.coroutine
     def _read_message(self, delegate):
+        need_delegate_close = False
         try:
             header_future = self.stream.read_until_regex(
                         b"\r?\n\r?\n",
@@ -117,11 +118,13 @@ class HTTP1Connection(object):
 
             self._disconnect_on_finish = not self._can_keep_alive(
                 start_line, headers)
+            need_delegate_close = True
             header_future = delegate.headers_received(start_line, headers)
             if header_future is not None:
                 yield header_future
             if self.stream is None:
                 # We've been detached.
+                need_delegate_close = False
                 raise gen.Return(False)
             skip_body = False
             if self.is_client:
@@ -155,6 +158,7 @@ class HTTP1Connection(object):
                             raise gen.Return(False)
             self._read_finished = True
             if not self._write_finished or self.is_client:
+                need_delegate_close = False
                 delegate.finish()
             yield self._finish_future
             if self.is_client and self._disconnect_on_finish:
@@ -167,6 +171,8 @@ class HTTP1Connection(object):
             self.close()
             raise gen.Return(False)
         finally:
+            if need_delegate_close:
+                delegate.on_connection_close()
             self._clear_callbacks()
         raise gen.Return(True)
 
index 2021ae4d8192fa1447bf0e20d03f97a21f52b2d9..47f3d596980318b69ee61a6fbeac6e4956278c2f 100644 (file)
@@ -264,6 +264,16 @@ class _ServerRequestAdapter(httputil.HTTPMessageDelegate):
             self.server.request_callback(self.request)
         else:
             self.delegate.finish()
+        self._cleanup()
+
+    def on_connection_close(self):
+        if self.delegate is None:
+            self._chunks = None
+        else:
+            self.delegate.on_connection_close()
+        self._cleanup()
+
+    def _cleanup(self):
         if self.server.xheaders:
             self.connection.context._unapply_xheaders()
 
index b96f360d184b0655d2f8c1ea0c47e879bb40c6ef..75a3dbf5fe6a51b32cedaae4191444c883af389a 100644 (file)
@@ -452,6 +452,9 @@ class HTTPMessageDelegate(object):
     def finish(self):
         pass
 
+    def on_connection_close(self):
+        pass
+
 
 def url_concat(url, args):
     """Concatenate url and argument dictionary regardless of whether
index 92b768f60b5dfef62dbdca04cc6ca8798a24eea6..e4ae54ce74f9a54b7a303a4c8905733d08778514 100644 (file)
@@ -1828,8 +1828,17 @@ class StreamingRequestBodyTest(WebTestCase):
                 # the (non-existent) data_received.
                 raise HTTPError(401)
 
+        @stream_request_body
+        class CloseDetectionHandler(RequestHandler):
+            def initialize(self, test):
+                self.test = test
+
+            def on_connection_close(self):
+                self.test.close_future.set_result(None)
+
         return [('/stream_body', StreamingBodyHandler, dict(test=self)),
-                ('/early_return', EarlyReturnHandler)]
+                ('/early_return', EarlyReturnHandler),
+                ('/close_detection', CloseDetectionHandler, dict(test=self))]
 
     def connect(self, url, connection_close):
         # Use a raw connection so we can control the sending of data.
@@ -1878,6 +1887,13 @@ class StreamingRequestBodyTest(WebTestCase):
         data = yield gen.Task(stream.read_until_close)
         self.assertTrue(data.startswith(b"HTTP/1.1 401"))
 
+    @gen_test
+    def test_close_during_upload(self):
+        self.close_future = Future()
+        stream = self.connect(b"/close_detection", connection_close=False)
+        stream.close()
+        yield self.close_future
+
 
 class StreamingRequestFlowControlTest(WebTestCase):
     def get_handlers(self):
index 37c5f715bcb586f3600ea26770700979fbe268be..260974190e9ff4aceb367caa71a7afe5065f53ae 100644 (file)
@@ -78,6 +78,7 @@ from tornado.concurrent import Future, is_future
 from tornado import escape
 from tornado import gen
 from tornado import httputil
+from tornado import iostream
 from tornado import locale
 from tornado.log import access_log, app_log, gen_log
 from tornado import stack_context
@@ -234,7 +235,9 @@ class RequestHandler(object):
         may not be called promptly after the end user closes their
         connection.
         """
-        pass
+        if _has_stream_request_body(self.__class__):
+            if not self.request.body.done():
+                self.request.body.set_exception(iostream.StreamClosedError())
 
     def clear(self):
         """Resets all headers and content for this response."""
@@ -1223,7 +1226,10 @@ class RequestHandler(object):
                 # the body has been completely received.  The Future has no
                 # result; the data has been passed to self.data_received
                 # instead.
-                yield self.request.body
+                try:
+                    yield self.request.body
+                except iostream.StreamClosedError:
+                    return
 
             method = getattr(self, self.request.method.lower())
             result = method(*self.path_args, **self.path_kwargs)
@@ -1767,6 +1773,12 @@ class _RequestDispatcher(httputil.HTTPMessageDelegate):
             self.request._parse_body()
             self.execute()
 
+    def on_connection_close(self):
+        if self.stream_request_body:
+            self.handler.on_connection_close()
+        else:
+            self.chunks = None
+
     def execute(self):
         # If template cache is disabled (usually in the debug mode),
         # re-compile templates and reload static files on every