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
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)
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
url("/hang", HangHandler),
url("/hello", HelloWorldHandler),
url("/content_length", ContentLengthHandler),
+ url("/head", HeadHandler),
+ url("/no_content", NoContentHandler),
], gzip=True)
def test_singleton(self):
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)