Commit e7d1dcad authored by Jim Fulton's avatar Jim Fulton

Hold on to your butts!

This is a pretty significant restructuring of ZPublisher to
allow a new module hook for handling errors.  There are two goals
in this:

  - To allow Zope to retry a request when ZODB raises a ConflictError, and

  - To move reformatting of exception using standard_error_message
    out to the publisher level so that:

    - Errors don't get obfuscated by error handling done by
      sub-documents and external methods, and

    - Errors raised by the publisher (e.g. for input errors,
      not-found errors, system exit, etc) get prettified.
parent a9f2bb81
......@@ -82,7 +82,7 @@
# attributions are listed in the accompanying credits file.
#
##############################################################################
__version__='$Revision: 1.12 $'[11:-2]
__version__='$Revision: 1.13 $'[11:-2]
from string import join, split, find, rfind, lower, upper
from urllib import quote
......@@ -246,136 +246,135 @@ class BaseRequest:
parents=request['PARENTS']
object=parents[-1]
del parents[-1]
if hasattr(object,'__roles__'): roles=object.__roles__
else: roles=UNSPECIFIED_ROLES
# if the top object has a __bobo_traverse__ method, then use it
# to possibly traverse to an alternate top-level object.
if hasattr(object,'__bobo_traverse__'):
try: object=object.__bobo_traverse__(request)
except: pass
# Get default object if no path was specified:
if not path:
if not method: return response.forbiddenError(entry_name)
entry_name=method
try:
if hasattr(object,entry_name):
response.setBase(URL)
path=[entry_name]
else:
try:
if object.has_key(entry_name):
path=[entry_name]
except: pass
except: pass
# Traverse the URL to find the object:
if hasattr(object, '__of__'):
# Try to bind the top-level object to the request
object=object.__of__(RequestContainer(REQUEST=request))
steps=[]
path.reverse()
while path:
entry_name=path[-1]
del path[-1]
URL="%s/%s" % (URL,quote(entry_name))
got=0
if entry_name:
if entry_name[:1]=='_':
if debug_mode:
return response.debugError(
"Object name begins with an underscore at: %s"
% URL)
else: return response.forbiddenError(entry_name)
if hasattr(object,'__bobo_traverse__'):
request['URL']=URL
subobject=object.__bobo_traverse__(request,entry_name)
if type(subobject) is type(()) and len(subobject) > 1:
while len(subobject) > 2:
parents.append(subobject[0])
subobject=subobject[1:]
object, subobject = subobject
else:
try:
# Note - this is necessary to support things like DAV.
# We have to make sure that the target object is not
# acquired if the request_method is other than GET or
# POST. Otherwise, you could never use PUT to add a new
# object named 'test' if an object 'test' existed above
# it in the heirarchy -- you'd always get the existing
# object :(
if baseflag and hasattr(object, 'aq_base'):
if hasattr(object.aq_base, entry_name):
subobject=getattr(object, entry_name)
else: raise AttributeError, entry_name
else: subobject=getattr(object, entry_name)
except AttributeError:
got=1
try: subobject=object[entry_name]
except (KeyError, IndexError,
TypeError, AttributeError):
if debug_mode:
return response.debugError(
"Cannot locate object at: %s" %URL)
else: return response.notFoundError(URL)
del parents[:]
try:
# We build parents in the wrong order, so we
# need to make sure we reverse it when we're doe.
if hasattr(object,'__roles__'): roles=object.__roles__
else: roles=UNSPECIFIED_ROLES
# if the top object has a __bobo_traverse__ method, then use it
# to possibly traverse to an alternate top-level object.
if hasattr(object,'__bobo_traverse__'):
try: object=object.__bobo_traverse__(request)
except: pass
# Get default object if no path was specified:
if not path:
if not method: return response.forbiddenError(entry_name)
entry_name=method
try:
try: doc=subobject.__doc__
except: doc=getattr(object, entry_name+'__doc__')
if not doc: raise AttributeError, entry_name
except:
if debug_mode:
return response.debugError("Missing doc string at: %s"
% URL)
else: return response.notFoundError("%s" % (URL))
if hasattr(subobject,'__roles__'):
roles=subobject.__roles__
else:
if not got:
roleshack=entry_name+'__roles__'
if hasattr(object, roleshack):
roles=getattr(object, roleshack)
# Promote subobject to object
parents.append(object)
object=subobject
steps.append(entry_name)
# Check for method:
if not path:
if (method and hasattr(object,method)
and entry_name != method
and getattr(object, method) is not None
):
request._hacked_path=1
path=[method]
if hasattr(object,entry_name):
response.setBase(URL)
path=[entry_name]
else:
if (hasattr(object, '__call__') and
hasattr(object.__call__,'__roles__')):
roles=object.__call__.__roles__
if request._hacked_path:
i=rfind(URL,'/')
if i > 0: response.setBase(URL[:i])
try:
if object.has_key(entry_name):
path=[entry_name]
except: pass
except: pass
# Traverse the URL to find the object:
if hasattr(object, '__of__'):
# Try to bind the top-level object to the request
object=object.__of__(RequestContainer(REQUEST=request))
steps=self.steps
path.reverse()
while path:
entry_name=path[-1]
del path[-1]
URL="%s/%s" % (URL,quote(entry_name))
got=0
if entry_name:
if entry_name[:1]=='_':
if debug_mode:
return response.debugError(
"Object name begins with an underscore at: %s"
% URL)
else: return response.forbiddenError(entry_name)
if hasattr(object,'__bobo_traverse__'):
request['URL']=URL
subobject=object.__bobo_traverse__(request,entry_name)
if type(subobject) is type(()) and len(subobject) > 1:
while len(subobject) > 2:
parents.append(subobject[0])
subobject=subobject[1:]
object, subobject = subobject
else:
try:
# Note - this is necessary to support
# things like DAV. We have to make sure
# that the target object is not acquired
# if the request_method is other than GET
# or POST. Otherwise, you could never use
# PUT to add a new object named 'test' if
# an object 'test' existed above it in the
# heirarchy -- you'd always get the
# existing object :(
if baseflag and hasattr(object, 'aq_base'):
if hasattr(object.aq_base, entry_name):
subobject=getattr(object, entry_name)
else: raise AttributeError, entry_name
else: subobject=getattr(object, entry_name)
except AttributeError:
got=1
try: subobject=object[entry_name]
except (KeyError, IndexError,
TypeError, AttributeError):
if debug_mode:
return response.debugError(
"Cannot locate object at: %s" %URL)
else: return response.notFoundError(URL)
try:
try: doc=subobject.__doc__
except: doc=getattr(object, entry_name+'__doc__')
if not doc: raise AttributeError, entry_name
except:
if debug_mode:
return response.debugError(
"Missing doc string at: %s" % URL)
else: return response.notFoundError("%s" % (URL))
if hasattr(subobject,'__roles__'):
roles=subobject.__roles__
else:
if not got:
roleshack=entry_name+'__roles__'
if hasattr(object, roleshack):
roles=getattr(object, roleshack)
# THIS LOOKS WRONG!
# if entry_name != method and method != 'index_html':
# if debug_mode:
# response.debugError("Method %s not found at: %s"
# % (method,URL))
# else: response.notFoundError(method)
# Promote subobject to object
parents.append(object)
object=subobject
request.steps=steps
parents.reverse()
steps.append(entry_name)
# Check for method:
if not path:
if (method and hasattr(object,method)
and entry_name != method
and getattr(object, method) is not None
):
request._hacked_path=1
path=[method]
else:
if (hasattr(object, '__call__') and
hasattr(object.__call__,'__roles__')):
roles=object.__call__.__roles__
if request._hacked_path:
i=rfind(URL,'/')
if i > 0: response.setBase(URL[:i])
finally:
# We need to MAKE SURE this happens due to new error handling
parents.reverse()
# Do authorization checks
user=groups=None
......@@ -448,6 +447,16 @@ class BaseRequest:
return object
retry_count=0
def supports_retry(self): return 0
_held=()
def _hold(self, object):
"""Hold a reference to an object to delay it's destruction until mine
"""
self._held=self._held+(object,)
def old_validation(groups, request, auth,
roles=UNSPECIFIED_ROLES):
......
......@@ -84,8 +84,8 @@
##############################################################################
'''CGI Response Output formatter
$Id: BaseResponse.py,v 1.3 1999/03/18 22:34:53 jim Exp $'''
__version__='$Revision: 1.3 $'[11:-2]
$Id: BaseResponse.py,v 1.4 1999/08/04 18:05:26 jim Exp $'''
__version__='$Revision: 1.4 $'[11:-2]
import string, types, sys, regex
from string import find, rfind, lower, upper, strip, split, join, translate
......@@ -98,6 +98,7 @@ class BaseResponse:
"""
debug_mode=None
_auth=None
_error_format='text/plain'
def __init__(self, stdout, stderr,
body='', headers=None, status=None, cookies=None):
......
......@@ -83,7 +83,7 @@
#
##############################################################################
__version__='$Revision: 1.19 $'[11:-2]
__version__='$Revision: 1.20 $'[11:-2]
import regex, sys, os, string
from string import lower, atoi, rfind, split, strip, join, upper, find
......@@ -170,8 +170,19 @@ class HTTPRequest(BaseRequest):
_hacked_path=None
args=()
retry_max_count=3
def supports_retry(self): return self.retry_count < self.retry_max_coun
def retry(self):
r=self.__class__(stdin=self.stdin,
environ=self._orig_env,
response=self.response.retry()
)
r._held=self._held
return r
def __init__(self, stdin, environ, response, clean=0):
self._orig_env=environ
# Avoid the overhead of scrubbing the environment in the
# case of request cloning for traversal purposes. If the
# clean flag is set, we know we can use the passed in
......@@ -180,8 +191,64 @@ class HTTPRequest(BaseRequest):
self.stdin=stdin
self.environ=environ
have_env=environ.has_key
get_env=environ.get
self.response=response
other=self.other={}
other=self.other={'RESPONSE': response}
self.form={}
self.steps=[]
################################################################
# Get base info first. This isn't likely to cause
# errors and might be useful to error handlers.
b=script=strip(get_env('SCRIPT_NAME',''))
while b and b[-1]=='/': b=b[:-1]
p = rfind(b,'/')
if p >= 0: b=b[:p+1]
else: b=''
while b and b[0]=='/': b=b[1:]
server_url=get_env('SERVER_URL',None)
if server_url is not None:
server_url=strip(server_url)
else:
if have_env('HTTPS') and (
environ['HTTPS'] == "on" or environ['HTTPS'] == "ON"):
server_url='https://'
elif (have_env('SERVER_PORT_SECURE') and
environ['SERVER_PORT_SECURE'] == "1"):
server_url='https://'
else: server_url='http://'
if have_env('HTTP_HOST'):
server_url=server_url+strip(environ['HTTP_HOST'])
else:
server_url=server_url+strip(environ['SERVER_NAME'])
server_port=environ['SERVER_PORT']
if server_port!='80': server_url=server_url+':'+server_port
other['SERVER_URL']=server_url
if server_url[-1:]=='/': server_url=server_url[:-1]
if b: self.base="%s/%s" % (server_url,b)
else: self.base=server_url
while script[:1]=='/': script=script[1:]
if script: script="%s/%s" % (server_url,script)
else: script=server_url
other['URL']=self.script=script
################################################################
# Cookie values should *not* be appended to existing form
# vars with the same name - they are more like default values
# for names not otherwise specified in the form.
cookies={}
k=get_env('HTTP_COOKIE','')
if k:
parse_cookie(k, cookies)
for k,item in cookies.items():
if not other.has_key(k):
other[k]=item
self.cookies=cookies
def processInputs(
self,
......@@ -201,8 +268,8 @@ class HTTPRequest(BaseRequest):
):
"""Process request inputs
We need to delay input parsing so that it is done under publisher control for
error handling prposes.
We need to delay input parsing so that it is done under
publisher control for error handling purposes.
"""
response=self.response
environ=self.environ
......@@ -216,8 +283,9 @@ class HTTPRequest(BaseRequest):
response._auth=1
del environ['HTTP_AUTHORIZATION']
form={}
form=self.form
other=self.other
meth=None
fs=FieldStorage(fp=fp,environ=environ,keep_blank_values=1)
if not hasattr(fs,'list') or fs.list is None:
......@@ -229,7 +297,8 @@ class HTTPRequest(BaseRequest):
global xmlrpc
if xmlrpc is None: import xmlrpc
meth, self.args = xmlrpc.parse_input(fs.value)
response=self.response=xmlrpc.response(response)
response=xmlrpc.response(response)
other['RESPONSE']=self.response=response
other['REQUEST_METHOD']='' # We don't want index_html!
else:
self._file=fs.file
......@@ -461,23 +530,31 @@ class HTTPRequest(BaseRequest):
if getattr(x, '__class__',0) is record:
# if the x is a record
for k, v in x.__dict__.items():
# loop through each attribute and value in
# loop through each
# attribute and value in
# the record
for y in l:
# loop through each record in the form
# list if it doesn't have the attributes
# in the default dictionary, set them
# loop through each
# record in the form
# list if it doesn't
# have the attributes
# in the default
# dictionary, set them
if not hasattr(y, k):
setattr(y, k, v)
else:
# x is not a record
# x is not a record
if not a in l:
l.append(a)
form[keys] = l
else:
# The form has the key, the key is not mapped
# to a record or sequence so do nothing
pass
pass
# Convert to tuples
if tuple_items:
......@@ -529,57 +606,6 @@ class HTTPRequest(BaseRequest):
other['PATH_INFO']=path="%s/%s" % (path,meth)
self._hacked_path=1
# Cookie values should *not* be appended to existing form
# vars with the same name - they are more like default values
# for names not otherwise specified in the form.
cookies={}
if environ.has_key('HTTP_COOKIE'):
parse_cookie(environ['HTTP_COOKIE'],cookies)
for k,item in cookies.items():
if not other.has_key(k):
other[k]=item
self.form=form
self.cookies=cookies
other['RESPONSE']=response
have_env=environ.has_key
b=script=strip(environ['SCRIPT_NAME'])
while b and b[-1]=='/': b=b[:-1]
p = rfind(b,'/')
if p >= 0: b=b[:p+1]
else: b=''
while b and b[0]=='/': b=b[1:]
if have_env('SERVER_URL'):
server_url=strip(environ['SERVER_URL'])
else:
if have_env('HTTPS') and (
environ['HTTPS'] == "on" or environ['HTTPS'] == "ON"):
server_url='https://'
elif (have_env('SERVER_PORT_SECURE') and
environ['SERVER_PORT_SECURE'] == "1"):
server_url='https://'
else: server_url='http://'
if have_env('HTTP_HOST'):
server_url=server_url+strip(environ['HTTP_HOST'])
else:
server_url=server_url+strip(environ['SERVER_NAME'])
server_port=environ['SERVER_PORT']
if server_port!='80': server_url=server_url+':'+server_port
other['SERVER_URL']=server_url
if server_url[-1:]=='/': server_url=server_url[:-1]
if b: self.base="%s/%s" % (server_url,b)
else: self.base=server_url
while script[:1]=='/': script=script[1:]
if script: script="%s/%s" % (server_url,script)
else: script=server_url
other['URL']=self.script=script
def resolve_url(self, url):
# Attempt to resolve a url into an object in the Zope
# namespace. The url must be a fully-qualified url. The
......
......@@ -84,8 +84,8 @@
##############################################################################
'''CGI Response Output formatter
$Id: HTTPResponse.py,v 1.16 1999/06/30 14:02:15 jim Exp $'''
__version__='$Revision: 1.16 $'[11:-2]
$Id: HTTPResponse.py,v 1.17 1999/08/04 18:05:27 jim Exp $'''
__version__='$Revision: 1.17 $'[11:-2]
import string, types, sys, regex
from string import find, rfind, lower, upper, strip, split, join, translate
......@@ -186,6 +186,7 @@ class HTTPResponse(BaseResponse):
accumulated_headers=''
body=''
realm='Zope'
_error_format='text/html'
def __init__(self,body='',status=200,headers=None,
stdout=sys.stdout, stderr=sys.stderr,):
......@@ -207,6 +208,16 @@ class HTTPResponse(BaseResponse):
self.cookies={}
self.stdout=stdout
self.stderr=stderr
def retry(self):
"""Return a response object to be used in a retry attempt
"""
# This implementation is a bit lame, because it assumes that
# only stdout stderr were passed to the constructor. OTOH, I
# think that that's all that is ever passed.
return self.__class__(stdout=self.stdout, stderr=self.stderr)
def setStatus(self, status, reason=None):
'''\
......
......@@ -84,8 +84,8 @@
##############################################################################
__doc__="""Python Object Publisher -- Publish Python objects on web servers
$Id: Publish.py,v 1.134 1999/08/04 12:01:20 jim Exp $"""
__version__='$Revision: 1.134 $'[11:-2]
$Id: Publish.py,v 1.135 1999/08/04 18:05:28 jim Exp $"""
__version__='$Revision: 1.135 $'[11:-2]
import sys, os
from string import lower, atoi, rfind, strip
......@@ -94,6 +94,10 @@ from Request import Request
from maybe_lock import allocate_lock
from mapply import mapply
class Retry(Exception):
"""Raise this to retry a request
"""
def call_object(object, args, request):
result=apply(object,args) # Type s<cr> to step into published object.
return result
......@@ -113,56 +117,125 @@ def publish(request, module_name, after_list, debug=0,
mapply=mapply,
):
request_get=request.get
response=request.response
(bobo_before, bobo_after, object, realm, debug_mode, err_hook,
have_transactions)= get_module_info(module_name)
# First check for "cancel" redirect:
cancel=''
if lower(strip(request_get('SUBMIT','')))=='cancel':
cancel=request_get('CANCEL_ACTION','')
if cancel: raise 'Redirect', cancel
parents=None
bobo_before, bobo_after, object, realm, debug_mode = get_module_info(
module_name)
try:
request.processInputs()
after_list[0]=bobo_after
if debug_mode: response.debug_mode=debug_mode
if realm and not request.get('REMOTE_USER',None):
response.realm=realm
request_get=request.get
response=request.response
# First check for "cancel" redirect:
cancel=''
if lower(strip(request_get('SUBMIT','')))=='cancel':
cancel=request_get('CANCEL_ACTION','')
if cancel: raise 'Redirect', cancel
after_list[0]=bobo_after
if debug_mode: response.debug_mode=debug_mode
if realm and not request.get('REMOTE_USER',None):
response.realm=realm
if bobo_before is not None: bobo_before();
# Get a nice clean path list:
path=strip(request_get('PATH_INFO'))
request['PARENTS']=parents=[object]
if have_transactions: get_transaction().begin()
object=request.traverse(path)
# Record transaction meta-data
if have_transactions:
get_transaction().note(request_get('PATH_INFO'))
auth_user=request_get('AUTHENTICATED_USER',None)
if auth_user is not None:
get_transaction().setUser(auth_user,
request_get('AUTHENTICATION_PATH'))
result=mapply(object, request.args, request,
call_object,1,
missing_name,
dont_publish_class,
request)
if result is not response: response.setBody(result)
if have_transactions: get_transaction().commit()
if bobo_before is not None: bobo_before();
return response
# Get a nice clean path list:
path=strip(request_get('PATH_INFO'))
except:
if err_hook is not None:
if parents: parents=parents[-1]
try:
return err_hook(parents, request,
sys.exc_info()[0],
sys.exc_info()[1],
sys.exc_info()[2],
)
except Retry:
# We need to try again....
if not request.supports_retry():
return err_hook(parents, request,
sys.exc_info()[0],
sys.exc_info()[1],
sys.exc_info()[2],
)
return publish(request.retry(), module_name, after_list, debug)
else: raise
request['PARENTS']=parents=[object]
def publish_module(module_name,
stdin=sys.stdin, stdout=sys.stdout, stderr=sys.stderr,
environ=os.environ, debug=0, request=None, response=None):
must_die=0
status=200
after_list=[None]
try:
try:
if response is None:
response=Response(stdout=stdout, stderr=stderr)
else:
stdout=response.stdout
# Attempt to start a transaction:
try: transaction=get_transaction()
except: transaction=None
if transaction is not None: transaction.begin()
if request is None:
request=Request(stdin, environ, response)
object=request.traverse(path)
response = publish(request, module_name, after_list, debug=debug)
except SystemExit, v:
if hasattr(sys, 'exc_info'): must_die=sys.exc_info()
else: must_die = SystemExit, v, sys.exc_traceback
response.exception(must_die)
except ImportError, v:
if type(v) is type(()) and len(v)==3: must_die=v
elif hasattr(sys, 'exc_info'): must_die=sys.exc_info()
else: must_die = SystemExit, v, sys.exc_traceback
response.exception(1, v)
except:
response.exception()
status=response.getStatus()
# Record transaction meta-data
if transaction is not None:
get_transaction().note(request_get('PATH_INFO'))
auth_user=request_get('AUTHENTICATED_USER',None)
if auth_user is not None:
get_transaction().setUser(auth_user,
request_get('AUTHENTICATION_PATH'))
if response:
response=str(response)
if response:
stdout.write(response)
result=mapply(object, request.args, request,
call_object,1,
missing_name,
dont_publish_class,
request)
# The module defined a post-access function, call it
if after_list[0] is not None: after_list[0]()
if result is not response: response.setBody(result)
finally:
if request is not None: request.other={}
if transaction: get_transaction().commit()
if must_die: raise must_die[0], must_die[1], must_die[2]
sys.exc_type, sys.exc_value, sys.exc_traceback = None, None, None
return status
return response
_l=allocate_lock()
def get_module_info(module_name, modules={},
......@@ -219,8 +292,15 @@ def get_module_info(module_name, modules={},
elif hasattr(module,'web_objects'):
object=module.web_objects
else: object=module
error_hook=getattr(module,'zpublisher_exception_hook', None)
try: get_transaction()
except: have_transactions=0
else: have_transactions=1
info= (bobo_before, bobo_after, object, realm, debug_mode)
info= (bobo_before, bobo_after, object, realm, debug_mode,
error_hook, have_transactions)
modules[module_name]=modules[module_name+'.cgi']=info
......@@ -232,47 +312,3 @@ def get_module_info(module_name, modules={},
finally:
tb=None
release()
def publish_module(module_name,
stdin=sys.stdin, stdout=sys.stdout, stderr=sys.stderr,
environ=os.environ, debug=0, request=None, response=None):
must_die=0
status=200
after_list=[None]
try:
try:
if response is None:
response=Response(stdout=stdout, stderr=stderr)
else:
stdout=response.stdout
if request is None:
request=Request(stdin, environ, response)
request.processInputs()
response=request.response # could have changed!
response = publish(request, module_name, after_list, debug=debug)
except SystemExit, v:
if hasattr(sys, 'exc_info'): must_die=sys.exc_info()
else: must_die = SystemExit, v, sys.exc_traceback
response.exception(must_die)
except ImportError, v:
if type(v) is type(()) and len(v)==3: must_die=v
elif hasattr(sys, 'exc_info'): must_die=sys.exc_info()
else: must_die = SystemExit, v, sys.exc_traceback
response.exception(1, v)
except:
response.exception()
status=response.getStatus()
if response:
response=str(response)
if response: stdout.write(response)
# The module defined a post-access function, call it
if after_list[0] is not None: after_list[0]()
finally:
if request is not None: request.other={}
if must_die: raise must_die[0], must_die[1], must_die[2]
sys.exc_type, sys.exc_value, sys.exc_traceback = None, None, None
return status
......@@ -162,9 +162,9 @@ Examples
s
$Id: Test.py,v 1.32 1999/08/04 12:01:20 jim Exp $
$Id: Test.py,v 1.33 1999/08/04 18:05:29 jim Exp $
'''
__version__='$Revision: 1.32 $'[11:-2]
__version__='$Revision: 1.33 $'[11:-2]
import sys, traceback, profile, os, getopt, string
from time import clock
......@@ -256,8 +256,6 @@ def publish_module(module_name,
stdout=response.stdout
if request is None:
request=Request(stdin, environ, response)
request.processInputs()
response=request.response # could have changed!
for k, v in extra.items(): request[k]=v
response = publish(request, module_name, after_list, debug=debug)
except SystemExit, v:
......
......@@ -82,9 +82,9 @@
# attributions are listed in the accompanying credits file.
#
##############################################################################
__version__='$Revision: 1.7 $'[11:-2]
__version__='$Revision: 1.8 $'[11:-2]
from Publish import publish_module
from Publish import publish_module, Retry
def test(*args, **kw):
global test
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment