304 responses no longer include entity headers like Content-Length
This is required by the RFC as it may confuse caches.
This commit is contained in:
parent
dd7655964a
commit
b1df663587
|
@ -15,6 +15,16 @@ import socket
|
|||
import sys
|
||||
|
||||
|
||||
class SimpleHandlerTestCase(AsyncHTTPTestCase):
|
||||
"""Simplified base class for tests that work with a single handler class.
|
||||
|
||||
To use, define a nested class named ``Handler``.
|
||||
"""
|
||||
def get_app(self):
|
||||
return Application([('/', self.Handler)],
|
||||
log_function=lambda x: None)
|
||||
|
||||
|
||||
class CookieTestRequestHandler(RequestHandler):
|
||||
# stub out enough methods to make the secure_cookie functions work
|
||||
def __init__(self):
|
||||
|
@ -714,6 +724,14 @@ class StaticFileTest(AsyncHTTPTestCase, LogTrapTestCase):
|
|||
response = self.fetch(path % int(include_host))
|
||||
self.assertEqual(response.body, utf8(str(True)))
|
||||
|
||||
def test_static_304(self):
|
||||
response1 = self.fetch("/static/robots.txt")
|
||||
response2 = self.fetch("/static/robots.txt", headers={
|
||||
'If-Modified-Since': response1.headers['Last-Modified']})
|
||||
self.assertEqual(response2.code, 304)
|
||||
self.assertTrue('Content-Length' not in response2.headers)
|
||||
self.assertTrue('Last-Modified' not in response2.headers)
|
||||
|
||||
|
||||
class CustomStaticFileTest(AsyncHTTPTestCase, LogTrapTestCase):
|
||||
def get_app(self):
|
||||
|
@ -769,3 +787,36 @@ class NamedURLSpecGroupsTest(AsyncHTTPTestCase, LogTrapTestCase):
|
|||
|
||||
response = self.fetch("/unicode/bar")
|
||||
self.assertEqual(response.body, b("bar"))
|
||||
|
||||
class ClearHeaderTest(SimpleHandlerTestCase):
|
||||
class Handler(RequestHandler):
|
||||
def get(self):
|
||||
self.set_header("h1", "foo")
|
||||
self.set_header("h2", "bar")
|
||||
self.clear_header("h1")
|
||||
self.clear_header("nonexistent")
|
||||
|
||||
def test_clear_header(self):
|
||||
response = self.fetch("/")
|
||||
self.assertTrue("h1" not in response.headers)
|
||||
self.assertEqual(response.headers["h2"], "bar")
|
||||
|
||||
|
||||
class Header304Test(SimpleHandlerTestCase):
|
||||
class Handler(RequestHandler):
|
||||
def get(self):
|
||||
self.set_header("Content-Language", "en_US")
|
||||
self.write("hello")
|
||||
|
||||
def test_304_headers(self):
|
||||
response1 = self.fetch('/')
|
||||
self.assertEqual(response1.headers["Content-Length"], "5")
|
||||
self.assertEqual(response1.headers["Content-Language"], "en_US")
|
||||
|
||||
response2 = self.fetch('/', headers={
|
||||
'If-None-Match': response1.headers["Etag"]})
|
||||
self.assertEqual(response2.code, 304)
|
||||
self.assertTrue("Content-Length" not in response2.headers)
|
||||
self.assertTrue("Content-Language" not in response2.headers)
|
||||
# Not an entity header, but should not be added to 304s by chunking
|
||||
self.assertTrue("Transfer-Encoding" not in response2.headers)
|
||||
|
|
|
@ -264,6 +264,15 @@ class RequestHandler(object):
|
|||
"""
|
||||
self._list_headers.append((name, self._convert_header_value(value)))
|
||||
|
||||
def clear_header(self, name):
|
||||
"""Clears an outgoing header, undoing a previous `set_header` call.
|
||||
|
||||
Note that this method does not apply to multi-valued headers
|
||||
set by `add_header`.
|
||||
"""
|
||||
if name in self._headers:
|
||||
del self._headers[name]
|
||||
|
||||
def _convert_header_value(self, value):
|
||||
if isinstance(value, bytes_type):
|
||||
pass
|
||||
|
@ -673,7 +682,10 @@ class RequestHandler(object):
|
|||
if inm and inm.find(etag) != -1:
|
||||
self._write_buffer = []
|
||||
self.set_status(304)
|
||||
if "Content-Length" not in self._headers:
|
||||
if self._status_code == 304:
|
||||
assert not self._write_buffer, "Cannot send body with 304"
|
||||
self._clear_headers_for_304()
|
||||
elif "Content-Length" not in self._headers:
|
||||
content_length = sum(len(part) for part in self._write_buffer)
|
||||
self.set_header("Content-Length", content_length)
|
||||
|
||||
|
@ -1065,6 +1077,17 @@ class RequestHandler(object):
|
|||
def _ui_method(self, method):
|
||||
return lambda *args, **kwargs: method(self, *args, **kwargs)
|
||||
|
||||
def _clear_headers_for_304(self):
|
||||
# 304 responses should not contain entity headers (defined in
|
||||
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec7.html#sec7.1)
|
||||
# not explicitly allowed by
|
||||
# http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.5
|
||||
headers = ["Allow", "Content-Encoding", "Content-Language",
|
||||
"Content-Length", "Content-MD5", "Content-Range",
|
||||
"Content-Type", "Last-Modified"]
|
||||
for h in headers:
|
||||
self.clear_header(h)
|
||||
|
||||
|
||||
def asynchronous(method):
|
||||
"""Wrap request handler methods with this if they are asynchronous.
|
||||
|
@ -1729,7 +1752,9 @@ class ChunkedTransferEncoding(OutputTransform):
|
|||
self._chunking = request.supports_http_1_1()
|
||||
|
||||
def transform_first_chunk(self, status_code, headers, chunk, finishing):
|
||||
if self._chunking:
|
||||
# 304 responses have no body (not even a zero-length body), and so
|
||||
# should not have either Content-Length or Transfer-Encoding headers.
|
||||
if self._chunking and status_code != 304:
|
||||
# No need to chunk the output if a Content-Length is specified
|
||||
if "Content-Length" in headers or "Transfer-Encoding" in headers:
|
||||
self._chunking = False
|
||||
|
|
Loading…
Reference in New Issue