[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.
- '''
+ """