]> git.ipfire.org Git - thirdparty/tornado.git/commitdiff
Fix hang on HEAD requests and on 204/304 responses with no content-length.
authorBen Darnell <ben@bendarnell.com>
Sun, 23 Oct 2011 21:43:20 +0000 (14:43 -0700)
committerBen Darnell <ben@bendarnell.com>
Sun, 23 Oct 2011 21:43:20 +0000 (14:43 -0700)
Closes #386.

tornado/simple_httpclient.py
tornado/test/simple_httpclient_test.py

index a98eb54484bda91ef34f6efd7788b7a3b4f2d31d..65a241f5c51757e36fb7a3f7a890c4e612e8aee8 100644 (file)
@@ -306,9 +306,36 @@ class _HTTPConnection(object):
         assert match
         self.code = int(match.group(1))
         self.headers = HTTPHeaders.parse(header_data)
+
+        if "Content-Length" in self.headers:
+            if "," in self.headers["Content-Length"]:
+                # Proxies sometimes cause Content-Length headers to get
+                # duplicated.  If all the values are identical then we can
+                # use them but if they differ it's an error.
+                pieces = re.split(r',\s*', self.headers["Content-Length"])
+                if any(i != pieces[0] for i in pieces):
+                    raise ValueError("Multiple unequal Content-Lengths: %r" % 
+                                     self.headers["Content-Length"])
+                self.headers["Content-Length"] = pieces[0]
+            content_length = int(self.headers["Content-Length"])
+        else:
+            content_length = None
+
         if self.request.header_callback is not None:
             for k, v in self.headers.get_all():
                 self.request.header_callback("%s: %s\r\n" % (k, v))
+
+        if self.request.method == "HEAD":
+            # HEAD requests never have content, even though they may have
+            # content-length headers
+            self._on_body(b(""))
+        if 100 <= self.code < 200 or self.code in (204, 304):
+            # These response codes never have bodies
+            # http://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.3
+            assert "Transfer-Encoding" not in self.headers
+            assert content_length in (None, 0)
+            self._on_body(b(""))
+
         if (self.request.use_gzip and
             self.headers.get("Content-Encoding") == "gzip"):
             # Magic parameter makes zlib module understand gzip header
@@ -317,18 +344,8 @@ class _HTTPConnection(object):
         if self.headers.get("Transfer-Encoding") == "chunked":
             self.chunks = []
             self.stream.read_until(b("\r\n"), self._on_chunk_length)
-        elif "Content-Length" in self.headers:
-            if "," in self.headers["Content-Length"]:
-                # Proxies sometimes cause Content-Length headers to get
-                # duplicated.  If all the values are identical then we can
-                # use them but if they differ it's an error.
-                pieces = re.split(r',\s*', self.headers["Content-Length"])
-                if any(i != pieces[0] for i in pieces):
-                    raise ValueError("Multiple unequal Content-Lengths: %r" % 
-                                     self.headers["Content-Length"])
-                self.headers["Content-Length"] = pieces[0]
-            self.stream.read_bytes(int(self.headers["Content-Length"]),
-                                   self._on_body)
+        elif content_length is not None:
+            self.stream.read_bytes(content_length, self._on_body)
         else:
             self.stream.read_until_close(self._on_body)
 
index cc23d620c5962b1d2e302e464d4a7747d21bcdc0..b8c8b3fe163effbc9ad4da3124f70268be98a957 100644 (file)
@@ -44,6 +44,16 @@ class ContentLengthHandler(RequestHandler):
         self.set_header("Content-Length", self.get_argument("value"))
         self.write("ok")
 
+class HeadHandler(RequestHandler):
+    def head(self):
+        self.set_header("Content-Length", "7")
+
+class NoContentHandler(RequestHandler):
+    def get(self):
+        if self.get_argument("error", None):
+            self.set_header("Content-Length", "7")
+        self.set_status(204)
+
 class SimpleHTTPClientTestCase(AsyncHTTPTestCase, LogTrapTestCase):
     def get_app(self):
         # callable objects to finish pending /trigger requests
@@ -56,6 +66,8 @@ class SimpleHTTPClientTestCase(AsyncHTTPTestCase, LogTrapTestCase):
             url("/hang", HangHandler),
             url("/hello", HelloWorldHandler),
             url("/content_length", ContentLengthHandler),
+            url("/head", HeadHandler),
+            url("/no_content", NoContentHandler),
             ], gzip=True)
 
     def test_singleton(self):
@@ -173,3 +185,20 @@ class SimpleHTTPClientTestCase(AsyncHTTPTestCase, LogTrapTestCase):
         self.assertEqual(response.code, 599)
         response = self.fetch("/content_length?value=2,%202,3")
         self.assertEqual(response.code, 599)
+
+    def test_head_request(self):
+        response = self.fetch("/head", method="HEAD")
+        self.assertEqual(response.code, 200)
+        self.assertEqual(response.headers["content-length"], "7")
+        self.assertFalse(response.body)
+
+    def test_no_content(self):
+        response = self.fetch("/no_content")
+        self.assertEqual(response.code, 204)
+        # 204 status doesn't need a content-length, but tornado will
+        # add a zero content-length anyway.
+        self.assertEqual(response.headers["Content-length"], "0")
+
+        # 204 status with non-zero content length is malformed
+        response = self.fetch("/no_content?error=1")
+        self.assertEqual(response.code, 599)