[Zope-Checkins] SVN: Zope/trunk/lib/python/ZServer/ Fix Collector #1866: 304 responses should not have a content-length

Martijn Pieters mj at zopatista.com
Tue Mar 27 14:46:08 EDT 2007


Log message for revision 73803:
  Fix Collector #1866: 304 responses should not have a content-length

Changed:
  U   Zope/trunk/lib/python/ZServer/HTTPResponse.py
  U   Zope/trunk/lib/python/ZServer/tests/test_responses.py

-=-
Modified: Zope/trunk/lib/python/ZServer/HTTPResponse.py
===================================================================
--- Zope/trunk/lib/python/ZServer/HTTPResponse.py	2007-03-27 18:45:58 UTC (rev 73802)
+++ Zope/trunk/lib/python/ZServer/HTTPResponse.py	2007-03-27 18:46:07 UTC (rev 73803)
@@ -71,16 +71,17 @@
                 self.status == 200:
             self.setStatus('nocontent')
 
-        # add content length if not streaming
-        if not headers.has_key('content-length') and \
-                not self._streaming:
-            self.setHeader('content-length',len(body))
+        if self.status in (100, 101, 102, 204, 304):
+            # These responses should not have any body or Content-Length.
+            # See RFC 2616 4.4 "Message Length".
+            body = ''
+            if 'content-length' in headers:
+                del headers['content-length']
+            if 'content-type' in headers:
+                del headers['content-type']
+        elif not headers.has_key('content-length') and not self._streaming:
+            self.setHeader('content-length', len(body))
 
-
-        content_length= headers.get('content-length', None)
-        if content_length>0 :
-            self.setHeader('content-length', content_length)
-
         headersl=[]
         append=headersl.append
 
@@ -96,8 +97,7 @@
         append('Date: %s' % build_http_date(time.time()))
 
         if self._http_version=='1.0':
-            if self._http_connection=='keep-alive' and \
-                    self.headers.has_key('content-length'):
+            if self._http_connection=='keep-alive':
                 self.setHeader('Connection','Keep-Alive')
             else:
                 self.setHeader('Connection','close')
@@ -107,12 +107,10 @@
         if self._http_version=='1.1':
             if self._http_connection=='close':
                 self.setHeader('Connection','close')
-            elif not self.headers.has_key('content-length'):
-                if self.http_chunk and self._streaming:
-                    self.setHeader('Transfer-Encoding','chunked')
-                    self._chunking=1
-                else:
-                    self.setHeader('Connection','close')
+            elif (not self.headers.has_key('content-length') and 
+                  self.http_chunk and self._streaming):
+                self.setHeader('Transfer-Encoding','chunked')
+                self._chunking=1
                     
         headers = headers.items()
         for line in self.accumulated_headers.splitlines():

Modified: Zope/trunk/lib/python/ZServer/tests/test_responses.py
===================================================================
--- Zope/trunk/lib/python/ZServer/tests/test_responses.py	2007-03-27 18:45:58 UTC (rev 73802)
+++ Zope/trunk/lib/python/ZServer/tests/test_responses.py	2007-03-27 18:46:07 UTC (rev 73803)
@@ -124,7 +124,149 @@
 
         self.assertTrue('Multilined: eggs\r\n\tham\r\n' in headers)
         self.assertTrue('Foo-Bar: bar\r\n\tbaz\r\n' in headers)
-    
+
+    def _assertResponsesAreEqual(self, got, expected):
+        got = got.split('\r\n')
+        # Sort the headers into alphabetical order.
+        headers = got[1:got.index('')]
+        headers.sort()
+        got[1:len(headers)+1] = headers
+        # Compare line by line.
+        for n in range(len(expected)):
+            if expected[n].endswith('...'):
+                m = len(expected[n]) - 3
+                self.assertEqual(got[n][:m], expected[n][:m])
+            else:
+                self.assertEqual(got[n], expected[n])
+        self.assertEqual(len(got), len(expected))
+
+    def test_emptyResponse(self):
+        # Empty repsonses have no Content-Length.
+        response = self._makeOne()
+        self._assertResponsesAreEqual(str(response),
+                                      ('HTTP/1.0 204 No Content',
+                                       'Connection: close',
+                                       'Date: ...',
+                                       'Server: ...',
+                                       '',
+                                       ''))
+
+    def test_304(self):
+        # Now we set the status to 304. 304 responses, according to RFC 2616,
+        # should not have a content-length header. __str__ should not add it
+        # back in if it is missing.
+        response = self._makeOne()
+        response.setStatus(304)
+        self._assertResponsesAreEqual(str(response),
+                                      ('HTTP/1.0 304 Not Modified',
+                                       'Connection: close',
+                                       'Date: ...',
+                                       'Server: ...',
+                                       '',
+                                       ''))
+
+    def test_304ContentLength(self):
+        # __str__ should strip out Content-Length
+        response = self._makeOne()
+        response.setStatus(304)
+        response.setHeader('content-length', '123')
+        self._assertResponsesAreEqual(str(response),
+                                      ('HTTP/1.0 304 Not Modified',
+                                       'Connection: close',
+                                       'Date: ...',
+                                       'Server: ...',
+                                       '',
+                                       ''))
+
+    def test_304ContentType(self):
+        # __str__ should strip out Content-Type
+        response = self._makeOne()
+        response.setStatus(304)
+        response.setHeader('content-type', 'text/plain')
+        self._assertResponsesAreEqual(str(response),
+                                      ('HTTP/1.0 304 Not Modified',
+                                       'Connection: close',
+                                       'Date: ...',
+                                       'Server: ...',
+                                       '',
+                                       ''))
+        
+    def test_304ExplicitKeepAlive(self):
+        # Explicit keep-alive connection header for HTTP 1.0.
+        response = self._makeOne()
+        response._http_connection = 'keep-alive'
+        response.setStatus(304)
+        self._assertResponsesAreEqual(str(response),
+                                      ('HTTP/1.0 304 Not Modified',
+                                       'Connection: Keep-Alive',
+                                       'Date: ...',
+                                       'Server: ...',
+                                       '',
+                                       ''))
+
+    def test_304ImplicitKeepAlive(self):
+        # Keep-alive is implicit for HTTP 1.1.
+        response = self._makeOne()
+        response._http_version = '1.1'
+        response._http_connection = 'keep-alive'
+        response.setStatus(304)
+        self._assertResponsesAreEqual(str(response),
+                                      ('HTTP/1.1 304 Not Modified',
+                                       'Date: ...',
+                                       'Server: ...',
+                                       '',
+                                       ''))
+
+    def test_contentLength(self):
+        # Check that __str__ adds in the correct Content-Length header.
+        response = self._makeOne()
+        response._http_version = '1.1'
+        response._http_connection = 'keep-alive'
+        response.body = '123456789'
+        response.setHeader('Content-Type', 'text/plain')
+        self._assertResponsesAreEqual(str(response),
+                                      ('HTTP/1.1 200 OK',
+                                       'Content-Length: 9',
+                                       'Content-Type: text/plain',
+                                       'Date: ...',
+                                       'Server: ...',
+                                       '',
+                                       '123456789'))
+
+    def test_emptyBody(self):
+        # Check that a response with an empty message body returns a
+        # Content-Length of 0. A common example of this is a 302 redirect.
+        response = self._makeOne()
+        response._http_version = '1.1'
+        response._http_connection = 'keep-alive'
+        response.redirect('somewhere')
+        self._assertResponsesAreEqual(str(response),
+                                      ('HTTP/1.1 302 Moved Temporarily',
+                                       'Content-Length: 0',
+                                       'Date: ...',
+                                       'Location: somewhere',
+                                       'Server: ...',
+                                       '',
+                                       ''))
+
+    def test_HEAD(self):
+        # A response to a HEAD request will have a non zero content
+        # length and an empty body.
+        response = self._makeOne()
+        response._http_version = '1.1'
+        response._http_connection = 'keep-alive'
+        response.setHeader('Content-Type', 'text/plain')
+        response.setHeader('Content-Length', 123)
+        self._assertResponsesAreEqual(str(response),
+                                      ('HTTP/1.1 200 OK',
+                                       'Content-Length: 123',
+                                       'Content-Type: text/plain',
+                                       'Date: ...',
+                                       'Server: ...',
+                                       '',
+                                       ''))
+
+
 def test_suite():
     suite = unittest.TestSuite()
     suite.addTests((



More information about the Zope-Checkins mailing list