[Checkins] SVN: Zope/branches/tseaver-fix_wsgi/src/ZPublisher/ Factor out computation of the list of response headers from stringifying them.

Tres Seaver tseaver at palladion.com
Sat May 29 00:24:45 EDT 2010


Log message for revision 112831:
  Factor out computation of the list of response headers from stringifying them.
  
  Allows WSGIHTTPResponse do reuse them as tuples.
  

Changed:
  U   Zope/branches/tseaver-fix_wsgi/src/ZPublisher/HTTPResponse.py
  U   Zope/branches/tseaver-fix_wsgi/src/ZPublisher/tests/testHTTPResponse.py

-=-
Modified: Zope/branches/tseaver-fix_wsgi/src/ZPublisher/HTTPResponse.py
===================================================================
--- Zope/branches/tseaver-fix_wsgi/src/ZPublisher/HTTPResponse.py	2010-05-28 21:55:20 UTC (rev 112830)
+++ Zope/branches/tseaver-fix_wsgi/src/ZPublisher/HTTPResponse.py	2010-05-29 04:24:44 UTC (rev 112831)
@@ -887,9 +887,9 @@
             # of name=value pairs may be quoted.
 
             if attrs.get('quoted', True):
-                cookie = 'Set-Cookie: %s="%s"' % (name, quote(attrs['value']))
+                cookie = '%s="%s"' % (name, quote(attrs['value']))
             else:
-                cookie = 'Set-Cookie: %s=%s' % (name, quote(attrs['value']))
+                cookie = '%s=%s' % (name, quote(attrs['value']))
             for name, v in attrs.items():
                 name = name.lower()
                 if name == 'expires':
@@ -908,32 +908,27 @@
                 # and block read/write access via JavaScript
                 elif name == 'http_only' and v:
                     cookie = '%s; HTTPOnly' % cookie
-            cookie_list.append(cookie)
+            cookie_list.append(('Set-Cookie', cookie))
 
         # Should really check size of cookies here!
 
         return cookie_list
 
-    def __str__(self,
-                html_search=re.compile('<html>',re.I).search,
-                ):
-        if self._wrote:
-            return ''       # Streaming output was used.
+    def listHeaders(self):
+        """ Return a list of (key, value) pairs for our headers.
 
-        headers = self.headers
+        o Do appropriate case normalization.
+        """
         body = self.body
+        if (not 'content-length' in self.headers and 
+            not 'transfer-encoding' in self.headers):
+            self.setHeader('content-length', len(body))
 
-        if not headers.has_key('content-length') and \
-                not headers.has_key('transfer-encoding'):
-            self.setHeader('content-length',len(body))
+        result = [
+          ('X-Powered-By', 'Zope (www.zope.org), Python (www.python.org)')
+        ]
 
-        chunks = []
-        append = chunks.append
-
-        # status header must come first.
-        append("Status: %d %s" % (self.status, self.errmsg))
-        append("X-Powered-By: Zope (www.zope.org), Python (www.python.org)")
-        for key, value in headers.items():
+        for key, value in self.headers.items():
             if key.lower() == key:
                 # only change non-literal header names
                 key = "%s%s" % (key[:1].upper(), key[1:])
@@ -943,12 +938,31 @@
                     key = "%s-%s%s" % (key[:l],key[l+1:l+2].upper(),key[l+2:])
                     start = l + 1
                     l = key.find('-', start)
-            append("%s: %s" % (key, value))
-        chunks.extend(self._cookie_list())
-        for key, value in self.accumulated_headers:
-            append("%s: %s" % (key, value))
-        append('') # RFC 2616 mandates empty line between headers and payload
-        append(body) 
+            result.append((key, value))
+
+        result.extend(self._cookie_list())
+        result.extend(self.accumulated_headers)
+        return result
+
+    def __str__(self,
+                html_search=re.compile('<html>',re.I).search,
+                ):
+        if self._wrote:
+            return ''       # Streaming output was used.
+
+        headers = self.headers
+        body = self.body
+
+        chunks = []
+
+        # status header must come first.
+        chunks.append("Status: %d %s" % (self.status, self.errmsg))
+
+        for key, value in self.listHeaders():
+            chunks.append("%s: %s" % (key, value))
+        # RFC 2616 mandates empty line between headers and payload
+        chunks.append('')
+        chunks.append(body) 
         return '\r\n'.join(chunks)
 
     def write(self,data):

Modified: Zope/branches/tseaver-fix_wsgi/src/ZPublisher/tests/testHTTPResponse.py
===================================================================
--- Zope/branches/tseaver-fix_wsgi/src/ZPublisher/tests/testHTTPResponse.py	2010-05-28 21:55:20 UTC (rev 112830)
+++ Zope/branches/tseaver-fix_wsgi/src/ZPublisher/tests/testHTTPResponse.py	2010-05-29 04:24:44 UTC (rev 112831)
@@ -206,7 +206,7 @@
         self.assertEqual(cookie.get('quoted'), True)
         cookies = response._cookie_list()
         self.assertEqual(len(cookies), 1)
-        self.assertEqual(cookies[0], 'Set-Cookie: foo="bar"')
+        self.assertEqual(cookies[0], ('Set-Cookie', 'foo="bar"'))
 
     def test_setCookie_w_expires(self):
         EXPIRES = 'Wed, 31-Dec-97 23:59:59 GMT'
@@ -221,7 +221,7 @@
         cookies = response._cookie_list()
         self.assertEqual(len(cookies), 1)
         self.assertEqual(cookies[0],
-                         'Set-Cookie: foo="bar"; Expires=%s' % EXPIRES)
+                         ('Set-Cookie', 'foo="bar"; Expires=%s' % EXPIRES))
 
     def test_setCookie_w_domain(self):
         response = self._makeOne()
@@ -235,7 +235,7 @@
         cookies = response._cookie_list()
         self.assertEqual(len(cookies), 1)
         self.assertEqual(cookies[0],
-                         'Set-Cookie: foo="bar"; Domain=example.com')
+                         ('Set-Cookie', 'foo="bar"; Domain=example.com'))
 
     def test_setCookie_w_path(self):
         response = self._makeOne()
@@ -248,7 +248,7 @@
 
         cookies = response._cookie_list()
         self.assertEqual(len(cookies), 1)
-        self.assertEqual(cookies[0], 'Set-Cookie: foo="bar"; Path=/')
+        self.assertEqual(cookies[0], ('Set-Cookie', 'foo="bar"; Path=/'))
 
     def test_setCookie_w_comment(self):
         response = self._makeOne()
@@ -261,7 +261,8 @@
 
         cookies = response._cookie_list()
         self.assertEqual(len(cookies), 1)
-        self.assertEqual(cookies[0], 'Set-Cookie: foo="bar"; Comment=COMMENT')
+        self.assertEqual(cookies[0],
+                         ('Set-Cookie', 'foo="bar"; Comment=COMMENT'))
 
     def test_setCookie_w_secure_true_value(self):
         response = self._makeOne()
@@ -274,7 +275,7 @@
 
         cookies = response._cookie_list()
         self.assertEqual(len(cookies), 1)
-        self.assertEqual(cookies[0], 'Set-Cookie: foo="bar"; Secure')
+        self.assertEqual(cookies[0], ('Set-Cookie','foo="bar"; Secure'))
 
     def test_setCookie_w_secure_false_value(self):
         response = self._makeOne()
@@ -287,7 +288,7 @@
 
         cookies = response._cookie_list()
         self.assertEqual(len(cookies), 1)
-        self.assertEqual(cookies[0], 'Set-Cookie: foo="bar"')
+        self.assertEqual(cookies[0], ('Set-Cookie', 'foo="bar"'))
 
     def test_setCookie_w_httponly_true_value(self):
         response = self._makeOne()
@@ -300,7 +301,7 @@
 
         cookie_list = response._cookie_list()
         self.assertEqual(len(cookie_list), 1)
-        self.assertEqual(cookie_list[0], 'Set-Cookie: foo="bar"; HTTPOnly')
+        self.assertEqual(cookie_list[0], ('Set-Cookie', 'foo="bar"; HTTPOnly'))
 
     def test_setCookie_w_httponly_false_value(self):
         response = self._makeOne()
@@ -313,7 +314,7 @@
 
         cookie_list = response._cookie_list()
         self.assertEqual(len(cookie_list), 1)
-        self.assertEqual(cookie_list[0], 'Set-Cookie: foo="bar"')
+        self.assertEqual(cookie_list[0], ('Set-Cookie', 'foo="bar"'))
 
     def test_setCookie_unquoted(self):
         response = self._makeOne()
@@ -325,7 +326,7 @@
 
         cookie_list = response._cookie_list()
         self.assertEqual(len(cookie_list), 1)
-        self.assertEqual(cookie_list[0], 'Set-Cookie: foo=bar')
+        self.assertEqual(cookie_list[0], ('Set-Cookie', 'foo=bar'))
 
     def test_appendCookie_w_existing(self):
         response = self._makeOne()
@@ -928,6 +929,130 @@
         else:
             self.fail("Didn't raise Unauthorized")
 
+    def test_listHeaders_empty(self):
+        response = self._makeOne()
+        headers = response.listHeaders()
+        self.assertEqual(headers,
+                         [('X-Powered-By', 'Zope (www.zope.org), '
+                                           'Python (www.python.org)'),
+                          ('Content-Length', '0'),
+                         ])
+
+    def test_listHeaders_already_wrote(self):
+        # listHeaders doesn't do the short-circuit on _wrote.
+        response = self._makeOne()
+        response._wrote = True
+        headers = response.listHeaders()
+        self.assertEqual(headers,
+                         [('X-Powered-By', 'Zope (www.zope.org), '
+                                           'Python (www.python.org)'),
+                          ('Content-Length', '0'),
+                         ])
+
+    def test_listHeaders_existing_content_length(self):
+        response = self._makeOne()
+        response.setHeader('Content-Length', 42)
+        headers = response.listHeaders()
+        self.assertEqual(headers,
+                         [('X-Powered-By', 'Zope (www.zope.org), '
+                                           'Python (www.python.org)'),
+                          ('Content-Length', '42'),
+                         ])
+
+    def test_listHeaders_existing_transfer_encoding(self):
+        # If 'Transfer-Encoding' is set, don't force 'Content-Length'.
+        response = self._makeOne()
+        response.setHeader('Transfer-Encoding', 'slurry')
+        headers = response.listHeaders()
+        self.assertEqual(headers,
+                         [('X-Powered-By', 'Zope (www.zope.org), '
+                                           'Python (www.python.org)'),
+                          ('Transfer-Encoding', 'slurry'),
+                         ])
+
+    def test_listHeaders_after_setHeader(self):
+        response = self._makeOne()
+        response.setHeader('x-consistency', 'Foolish')
+        headers = response.listHeaders()
+        self.assertEqual(headers,
+                         [('X-Powered-By', 'Zope (www.zope.org), '
+                                           'Python (www.python.org)'),
+                          ('Content-Length', '0'),
+                          ('X-Consistency', 'Foolish'),
+                         ])
+
+    def test_listHeaders_after_setHeader_literal(self):
+        response = self._makeOne()
+        response.setHeader('X-consistency', 'Foolish', literal=True)
+        headers = response.listHeaders()
+        self.assertEqual(headers,
+                         [('X-Powered-By', 'Zope (www.zope.org), '
+                                           'Python (www.python.org)'),
+                          ('Content-Length', '0'),
+                          ('X-consistency', 'Foolish'),
+                         ])
+
+    def test_listHeaders_after_redirect(self):
+        response = self._makeOne()
+        response.redirect('http://example.com/')
+        headers = response.listHeaders()
+        self.assertEqual(headers,
+                         [('X-Powered-By', 'Zope (www.zope.org), '
+                                           'Python (www.python.org)'),
+                          ('Content-Length', '0'),
+                          ('Location', 'http://example.com/'),
+                         ])
+
+    def test_listHeaders_after_setCookie_appendCookie(self):
+        response = self._makeOne()
+        response.setCookie('foo', 'bar', path='/')
+        response.appendCookie('foo', 'baz')
+        headers = response.listHeaders()
+        self.assertEqual(headers,
+                         [('X-Powered-By', 'Zope (www.zope.org), '
+                                           'Python (www.python.org)'),
+                          ('Content-Length', '0'),
+                          ('Set-Cookie', 'foo="bar%3Abaz"; Path=/'),
+                         ])
+
+    def test_listHeaders_after_expireCookie(self):
+        response = self._makeOne()
+        response.expireCookie('qux', path='/')
+        headers = response.listHeaders()
+        self.assertEqual(headers,
+                         [('X-Powered-By', 'Zope (www.zope.org), '
+                                           'Python (www.python.org)'),
+                          ('Content-Length', '0'),
+                          ('Set-Cookie', 'qux="deleted"; '
+                                         'Path=/; '
+                                         'Expires=Wed, 31-Dec-97 23:59:59 GMT; '
+                                         'Max-Age=0'),
+                         ])
+
+    def test_listHeaders_after_addHeader(self):
+        response = self._makeOne()
+        response.addHeader('X-Consistency', 'Foolish')
+        response.addHeader('X-Consistency', 'Oatmeal')
+        headers = response.listHeaders()
+        self.assertEqual(headers,
+                         [('X-Powered-By', 'Zope (www.zope.org), '
+                                           'Python (www.python.org)'),
+                          ('Content-Length', '0'),
+                          ('X-Consistency', 'Foolish'),
+                          ('X-Consistency', 'Oatmeal'),
+                         ])
+
+    def test_listHeaders_w_body(self):
+        response = self._makeOne()
+        response.setBody('BLAH')
+        headers = response.listHeaders()
+        self.assertEqual(headers,
+                         [('X-Powered-By', 'Zope (www.zope.org), '
+                                           'Python (www.python.org)'),
+                          ('Content-Length', '4'),
+                          ('Content-Type', 'text/plain; charset=iso-8859-15'),
+                         ])
+
     def test___str__already_wrote(self):
         response = self._makeOne()
         response._wrote = True



More information about the checkins mailing list