[Zope-Checkins] CVS: Zope3/lib/python/Zope/Publisher/HTTP - IHTTPApplicationResponse.py:1.1.2.1 HTTPRequest.py:1.1.2.21.2.4 HTTPResponse.py:1.1.2.13.4.4 IHTTPResponse.py:1.1.2.3

Stephan Richter srichter@cbu.edu
Sun, 24 Mar 2002 13:34:49 -0500


Update of /cvs-repository/Zope3/lib/python/Zope/Publisher/HTTP
In directory cvs.zope.org:/tmp/cvs-serv15801/HTTP

Modified Files:
      Tag: Zope3-publisher-refactor-branch
	HTTPRequest.py HTTPResponse.py IHTTPResponse.py 
Added Files:
      Tag: Zope3-publisher-refactor-branch
	IHTTPApplicationResponse.py 
Log Message:
Continued refactoring:

- I think everything in ./HTTP and ./Browser is now refactored and I 
  created some interfaces to support that.

- Jim should probably look over at the refactoring again.

- Tests are still needed everywhere and the code in general is probably 
  horribly broken.

Note: This is being committed, so that other people (i.e. Jim) can also 
      keep working on this branch.



=== Added File Zope3/lib/python/Zope/Publisher/HTTP/IHTTPApplicationResponse.py ===
##############################################################################
#
# Copyright (c) 2001, 2002 Zope Corporation and Contributors.
# All Rights Reserved.
# 
# This software is subject to the provisions of the Zope Public License,
# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
# FOR A PARTICULAR PURPOSE
# 
##############################################################################
"""

$Id: IHTTPApplicationResponse.py,v 1.1.2.1 2002/03/24 18:34:48 srichter Exp $
"""

from Zope.Publisher.IApplicationResponse import IApplicationResponse


class IHTTPApplicationResponse(IApplicationResponse):
    """This interface expands IHTTPResponse interface by methods that
    will be used by the application.
    """

    def redirect(self, location, status=302):
        """Causes a redirection without raising an error.
        """



=== Zope3/lib/python/Zope/Publisher/HTTP/HTTPRequest.py 1.1.2.21.2.3 => 1.1.2.21.2.4 ===
 # 
 # This software is subject to the provisions of the Zope Public License,
-# Version 1.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
 # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
-# FOR A PARTICULAR PURPOSE.
+# FOR A PARTICULAR PURPOSE
+# 
 ##############################################################################
 """
 
@@ -16,7 +17,7 @@
 """
 
 import re, sys, os, time, whrandom, cgi
-from urllib import quote, unquote, splittype, splitport
+from urllib import quote, unquote
 from types import StringType
 
 from Zope.Publisher.BaseRequest import BaseRequest
@@ -103,7 +104,7 @@
             self._auth = environ['HTTP_AUTHORIZATION']
             del environ['HTTP_AUTHORIZATION']
 
-        self._environ=environ
+        self._environ = environ
 
         self.__setupPath()
         self.__setupCookies()
@@ -125,6 +126,7 @@
 
         self.__cookies = cookies
 
+
     def __setupPath(self):
         path = self.get('PATH_INFO', '').strip()
         if path.startswith('/'):  path = path[1:] # XXX Why? Not sure
@@ -139,6 +141,7 @@
         clean.reverse()
         self.setTraversalStack(clean)
 
+
     ######################################
     # from: Zope.Publisher.IPublisherRequest.IPublisherRequest
 
@@ -149,17 +152,19 @@
                 time.sleep(whrandom.uniform(0, 2**(self.retry_count)))
             return 1
 
+
     def retry(self):
         'See Zope.Publisher.IPublisherRequest.IPublisherRequest'
-        self.retry_count=self.retry_count+1
+        self.retry_count = self.retry_count + 1
         self.body_instream.seek(0)
-        r=self.__class__(
-            body_instream=self.body_instream,
-            response=self.getResponse().getOutputStream(),
-            environ=self.__orig_env
+        request = self.__class__(
+            body_instream = self.__body_instream,
+            outstream = self.getResponse().getOutputStream(),
+            environ = self.__orig_env
             )
-        r.retry_count=self.retry_count
-        return r
+        request.retry_count = self.retry_count
+        return request
+
 
     def traverse(self, object):
         'See Zope.Publisher.IPublisherRequest.IPublisherRequest'
@@ -168,13 +173,16 @@
         
         traversal_altered = 0 # flag for adding traversal steps
         add_steps = None
+        path_str = self.get('PATH_INFO', '').strip()
+
+        traversal_stack = self._splitPath(path_str)
+        traversal_stack.reverse()
+        self.setTraversalStack(traversal_stack)
 
         self.traversed = traversed = []
         traversed.append(object)
         steps = self.steps
         self.quoted_steps = quoted_steps = map(pc_quote, steps)
-        to_traverse.reverse()
-        self.to_traverse = to_traverse
 
         prev_object = None
         while 1:
@@ -182,12 +190,13 @@
                 # Invoke hooks (but not more than once).
                 publication.callTraversalHooks(self, object)
                 # A hook may have called changeTraversalStack().
-                to_traverse = self.to_traverse
+                traversal_stack = self.getTraversalStack()
+
             prev_object = object
 
-            if to_traverse:
+            if traversal_stack:
                 # Traverse to the next step.
-                entry_name = to_traverse.pop()
+                entry_name = traversal_stack.pop()
                 if entry_name:
                     qstep = pc_quote(entry_name)
                     quoted_steps.append(qstep)
@@ -219,25 +228,49 @@
                     
                 if add_steps:
                     traversal_altered = 1
-                    to_traverse.extend(add_steps)
+                    traversal_stack.extend(add_steps)
                 else:
                     # Finished traversal.
                     break
         
         if traversal_altered:
             eurl = self.effective_url
-            l = eurl.rfind('/')
-            if l >= 0: eurl = eurl[:l+1] # XXX Quick bug fix, need better impl
+            loc = eurl.rfind('/')
+            # XXX Quick bug fix, need better impl
+            if loc >= 0:
+                eurl = eurl[:loc+1] 
             self.response.setBase(eurl)
 
-        self.traversed = tuple(traversed)  # No more changes allowed
-        parents = traversed[:]
-        parents.pop()
-        parents.reverse()
-        self.other['PARENTS'] = tuple(parents)
-        self.other['PUBLISHED'] = object
 
+        self.traversed = tuple(traversed)  # No more changes allowed
+        self._afterTraveral() # Sometimes we want to do something here
         return object
+
+
+    # This method is not part of the interface.
+    def _afterTraversal(self):
+        '''Do whatever needs to be done after an object traveral'''
+        pass
+
+
+    # This method is not part of the interface.
+    def _splitPath(self, path):
+        # Split and clean up the path.
+        if path.startswith('/'):
+            path = path[1:]
+
+        if path.endswith('/'):
+            path = path[:-1]
+
+        clean = []
+        for item in path.split('/'):
+            if not item or item == '.':
+                continue
+            elif item == '..':
+                del clean[-1]
+            else: clean.append(item)
+
+        return clean
 
     #
     ######################################


=== Zope3/lib/python/Zope/Publisher/HTTP/HTTPResponse.py 1.1.2.13.4.3 => 1.1.2.13.4.4 ===
 # 
 # This software is subject to the provisions of the Zope Public License,
-# Version 1.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# Version 2.0 (ZPL).  A copy of the ZPL should accompany this distribution.
 # THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
 # WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
 # WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
-# FOR A PARTICULAR PURPOSE.
+# FOR A PARTICULAR PURPOSE
+# 
 ##############################################################################
 '''HTTP Response Output formatter
 
@@ -77,9 +78,11 @@
 for key, val in status_reasons.items():
     status_codes[val.replace(' ', '').lower()] = key
     status_codes[val.lower()] = key
-    status_codes[key]=key
-    status_codes[str(key)]=key
+    status_codes[key] = key
+    status_codes[str(key)] = key
+
 en = [n.lower() for n in dir(__builtins__) if n.endswith('Error')]
+
 for name in en:
     status_codes[name] = 500
 
@@ -89,29 +92,10 @@
 
 
 class HTTPResponse (BaseResponse):
-    """
-    An object representation of an HTTP response.
-    
-    The Response type encapsulates all possible responses to HTTP
-    requests.  Responses are normally created by the object publisher.
-    A published object may recieve the response object as an argument
-    named 'RESPONSE'.  A published object may also create it's own
-    response object.  Normally, published objects use response objects
-    to:
-
-    - Provide specific control over output headers,
-
-    - Set cookies, or
 
-    - Provide stream-oriented output.
+    __implements__ = IHTTPResponse, IHTTPApplicationResponse, \
+                     BaseResponse.__implements__
 
-    If stream oriented output is used, then the response object
-    passed into the object must be used.
-    """
-
-    __implements__ =  IHTTPResponse
-
-    accumulated_headers = None
     base = None
     realm = 'Zope'
     _error_format = 'text/html'
@@ -127,6 +111,7 @@
         super(HTTPResponse, self).__init__(outstream)
         self.__headers = {}
         self.__cookies = {}
+        self.__accumulated_headers = []
 
 
     ############################################################
@@ -144,7 +129,7 @@
                 status = status_codes[status]
             else:
                 status=500
-        self.status=status
+        self.__status = status
 
         if reason is None:
             if status == 200:
@@ -158,7 +143,7 @@
 
     def getStatus(self):
         'See Zope.Publisher.HTTP.IHTTPResponse.IHTTPResponse'
-        return self.status
+        return self.__status
 
 
     def setHeader(self, name, value, literal=0):
@@ -168,17 +153,47 @@
             self.addHeader(name, value)
         else:
             name = literal and name or key
-            self.headers[name]=value
+            self.__headers[name]=value
 
 
     def addHeader(self, name, value):
         'See Zope.Publisher.HTTP.IHTTPResponse.IHTTPResponse'
         accum = self.accumulated_headers
-        if not accum:
-            self.accumulated_headers = accum = []
         accum.append('%s: %s' % (name, value))
 
 
+    def getHeader(self, name, default=None):
+        'See Zope.Publisher.HTTP.IHTTPResponse.IHTTPResponse'
+        return self.__headers.get(name, default)
+
+
+    def getHeaders(self):
+        'See Zope.Publisher.HTTP.IHTTPResponse.IHTTPResponse'
+        result = {}
+        headers = self.__headers
+
+        if (not self._streaming and not headers.has_key('content-length')
+            and not headers.has_key('transfer-encoding')):
+            self._updateContentLength()
+
+        result["X-Powered-By"] = "Zope (www.zope.org), Python (www.python.org)"
+
+        for key, val in headers.items():
+            if key.lower() == key:
+                # only change non-literal header names
+                key = key.capitalize()
+                start = 0
+                location = key.find('-', start)
+                while location >= start:
+                    key = "%s-%s" % (key[:location],
+                                     key[location+1:].capitalize())
+                    start = location + 1
+                    location = key.find('-', start)
+            result[key] = val
+
+        return result
+
+
     def appendToHeader(self, name, value, delimiter=','):
         'See Zope.Publisher.HTTP.IHTTPResponse.IHTTPResponse'
         headers = self.headers
@@ -224,6 +239,32 @@
     #
     ############################################################
 
+    ############################################################
+    # Implementation methods for interface
+    # Zope.Publisher.BaseResponse.IResponse
+
+    ######################################
+    # from: Zope.Publisher.IPublisherResponse.IPublisherResponse
+
+
+    def handleException(self, exc_info):
+        """
+        """
+        t, v = exc_info[:2]
+        if isinstance(t, ClassType):
+            title = tname = t.__name__
+            if issubclass(t, Redirect):
+                self.redirect(v.getLocation())
+                return
+        else:
+            title = tname = str(t)
+
+        # Throwing non-protocol-specific exceptions is a good way
+        # for apps to control the status code.
+        self.setStatus(tname)
+
+        tb = escape(traceback_string(t, v, exc_info[2]))
+        self.setBody(tb)
 
     def retry(self):
         """
@@ -232,7 +273,7 @@
         return self.__class__(self.outstream,
                               self.header_output)
 
-    def __updateContentLength(self):
+    def _updateContentLength(self):
         blen = str(len(self.body))
         if blen.endswith('L'):
             blen = blen[:-1]
@@ -244,13 +285,8 @@
         self.setHeader('Location', location)
         return location
 
-    def handleException(self, exc_info):
-        """
-        """
-        self.payload.handleException(self, exc_info)
-
     def _cookie_list(self):
-        cookie_list=[]
+        cookie_list = []
         for name, attrs in self.cookies.items():
 
             # Note that as of May 98, IE4 ignores cookies with
@@ -258,90 +294,59 @@
             # of name=value pairs may be quoted.
 
             cookie='Set-Cookie: %s="%s"' % (name, attrs['value'])
-            for name, v in attrs.items():
+            for name, value in attrs.items():
                 name = name.lower()
-                if name=='expires': cookie = '%s; Expires=%s' % (cookie,v)
-                elif name=='domain': cookie = '%s; Domain=%s' % (cookie,v)
-                elif name=='path': cookie = '%s; Path=%s' % (cookie,v)
-                elif name=='max_age': cookie = '%s; Max-Age=%s' % (cookie,v)
-                elif name=='comment': cookie = '%s; Comment=%s' % (cookie,v)
-                elif name=='secure' and v: cookie = '%s; Secure' % cookie
+                if name == 'expires':
+                    cookie = '%s; Expires=%s' % (cookie,value)
+                elif name == 'domain':
+                    cookie = '%s; Domain=%s' % (cookie,value)
+                elif name == 'path':
+                    cookie = '%s; Path=%s' % (cookie,value)
+                elif name == 'max_age':
+                    cookie = '%s; Max-Age=%s' % (cookie,value)
+                elif name == 'comment':
+                    cookie = '%s; Comment=%s' % (cookie,value)
+                elif name == 'secure' and value:
+                    cookie = '%s; Secure' % cookie
             cookie_list.append(cookie)
 
-        # Should really check size of cookies here!
+        # XXX: Should really check size of cookies here!
         
         return cookie_list
 
-    def getHeader(self, name):
-         '''
-         Gets a header value
-         
-         Returns the value associated with a HTTP return header, or
-         "None" if no such header has been set in the response
-         yet.
-         '''
-         return self.headers.get(name, None)
-
-
-    def getHeaders(self):
-        """
-        Returns a mapping of correctly-cased header names to values.
-        """
-        res = {}
-        headers = self.headers
-
-        if (not self._streaming and not headers.has_key('content-length')
-            and not headers.has_key('transfer-encoding')):
-            self.__updateContentLength()
-
-        res["X-Powered-By"] = "Zope (www.zope.org), Python (www.python.org)"
-
-        for key, val in headers.items():
-            if key.lower() == key:
-                # only change non-literal header names
-                key="%s%s" % (key[:1].upper(), key[1:])
-                start=0
-                l=key.find('-',start)
-                while l >= start:
-                    key="%s-%s%s" % (key[:l],key[l+1:l+2].upper(),key[l+2:])
-                    start=l+1
-                    l=key.find('-',start)
-            res[key] = val
-
-        return res
-
 
     def getHeaderText(self, m):
         lst = ['Status: %s %s' % (self.status, self.reason)]
         lst.extend(map(lambda x: '%s: %s' % x, m.items()))
         lst.extend(self._cookie_list())
-        accum = self.accumulated_headers
-        if accum:
-            lst.extend(accum)
+        lst.extend(self.__accumulated_headers)
         return ('%s\r\n\r\n' % '\r\n'.join(lst))
 
 
     def outputHeaders(self):
-        m = self.getHeaders()
+        headers = self.getHeaders()
         header_output = self.header_output
         if header_output is not None:
             # Use the IHeaderOutput interface.
             header_output.setResponseStatus(self.status, self.reason)
-            header_output.setResponseHeaders(m)
-            cookies = self._cookie_list()
-            if cookies:
-                header_output.appendResponseHeaders(cookies)
-            accum = self.accumulated_headers
-            if accum:
-                header_output.appendResponseHeaders(accum)
+            header_output.setResponseHeaders(headers)
+            header_output.appendResponseHeaders(self._cookie_list())
+            header_output.appendResponseHeaders(self.__accumulated_headers)
         else:
             # Write directly to outstream.
-            s = self.getHeaderText(m)
-            self.outstream.write(s)
+            headers_text = self.getHeaderText(headers)
+            self.outstream.write(headers_text)
 
 
-    def write(self, data):
-        """
+    ######################################
+    # from: Zope.Publisher.IApplicationResponse.IApplicationResponse
+
+    # XXX: Mmh, it seems that write has a different meaning here
+    #      compared to BaseResponse
+
+    def write(self, string):
+        """See Zope.Publisher.IApplicationResponse.IApplicationResponse
+
         Return data as a stream
 
         HTML data may be returned using a stream-oriented interface.
@@ -357,7 +362,11 @@
         """
         if streaming:
             self._streaming = 1
-        self.output(data)
+        self.output(string)
+
+    #
+    ############################################################
+
 
     def output(self, data):
         if not self._wrote_headers:
@@ -371,3 +380,41 @@
         """
         self.output(self.body)
 
+
+    def _formatException(etype, value, tb, limit=None):
+        import traceback
+        result=['Traceback (innermost last):']
+        if limit is None:
+            if hasattr(sys, 'tracebacklimit'):
+                limit = sys.tracebacklimit
+        n = 0
+        while tb is not None and (limit is None or n < limit):
+            frame = tb.tb_frame
+            lineno = tb.tb_lineno
+            co = frame.f_code
+            filename = co.co_filename
+            name = co.co_name
+            locals = frame.f_locals
+            globals = frame.f_globals
+            modname = globals.get('__name__', filename)
+            result.append('  Module %s, line %d, in %s'
+                          % (modname,lineno,name))
+            try:
+                result.append('    (Object: %s)' %
+                               locals[co.co_varnames[0]].__name__)
+            except:
+                pass
+
+            try:
+                result.append('    (Info: %s)' %
+                               str(locals['__traceback_info__']))
+            except: pass
+            tb = tb.tb_next
+            n = n+1
+        result.append(' '.join(traceback.format_exception_only(etype, value)))
+        return result
+
+
+    def _createTracebackString(self, t, v, tb):
+        tb = self._formatException(t, v, tb, 200)
+        return '\n'.join(tb)


=== Zope3/lib/python/Zope/Publisher/HTTP/IHTTPResponse.py 1.1.2.2 => 1.1.2.3 ===
 
 class IHTTPResponse(Interface):
+    """An object representation of an HTTP response.
+    
+    The Response type encapsulates all possible responses to HTTP
+    requests.  Responses are normally created by the object publisher.
+    A published object may recieve the response object as an argument
+    named 'RESPONSE'.  A published object may also create it's own
+    response object.  Normally, published objects use response objects
+    to:
+
+    - Provide specific control over output headers,
+
+    - Set cookies, or
+
+    - Provide stream-oriented output.
+
+    If stream oriented output is used, then the response object
+    passed into the object must be used.
+    """
 
     def getStatus():
-        'Returns the current HTTP status code as an integer. '
+        """Returns the current HTTP status code as an integer.
+        """
+
 
     def setStatus(status, reason=None):
-        '''Sets the HTTP status code of the response
+        """Sets the HTTP status code of the response
 
         The argument may either be an integer or a string from { OK,
         Created, Accepted, NoContent, MovedPermanently,
@@ -33,35 +53,52 @@
         Forbidden, NotFound, InternalError, NotImplemented,
         BadGateway, ServiceUnavailable } that will be converted to the
         correct integer value.
-        '''
+        """
+
 
     def setHeader(name, value, literal=0):
-        '''Sets an HTTP return header "name" with value "value"
+        """Sets an HTTP return header "name" with value "value"
 
         The previous value is cleared. If the literal flag is true,
         the case of the header name is preserved, otherwise
         word-capitalization will be performed on the header name on
         output.        
-        '''
+        """
+
 
     def addHeader(name, value):
-        '''Add an HTTP Header
+        """Add an HTTP Header
         
         Sets a new HTTP return header with the given value, while retaining
         any previously set headers with the same name.
 
-        '''
+        """
+
+
+    def getHeader(name, default=None):
+         """Gets a header value
+         
+         Returns the value associated with a HTTP return header, or
+         'default' if no such header has been set in the response
+         yet.
+         """
+
+
+    def getHeaders():
+        """Returns a mapping of correctly-cased header names to values.
+        """
+        
 
     def appendToCookie(name, value):
-        '''Append text to a cookie value
+        """Append text to a cookie value
         
         If a value for the cookie has previously been set, the new
         value is appended to the old one separated by a colon.
-        '''
+        """
 
 
     def expireCookie(name, **kw):
-        '''Causes an HTTP cookie to be removed from the browser
+        """Causes an HTTP cookie to be removed from the browser
         
         The response will include an HTTP header that will remove the cookie
         corresponding to "name" on the client, if one exists. This is
@@ -70,23 +107,23 @@
         to be specified - this path must exactly match the path given
         when creating the cookie. The path can be specified as a keyword
         argument.
-        '''
+        """
 
 
     def setCookie(name, value, **kw):
-        '''Sets an HTTP cookie on the browser
+        """Sets an HTTP cookie on the browser
 
         The response will include an HTTP header that sets a cookie on
         cookie-enabled browsers with a key "name" and value
         "value". This overwrites any previously set value for the
         cookie in the Response object.
-        '''
+        """
 
     def appendToHeader(name, value, delimiter=","):
-        '''Appends a value to a header
+        """Appends a value to a header
         
         Sets an HTTP return header "name" with value "value",
         appending it following a comma if there was a previous value
         set for the header.
 
-        '''
+        """