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
This diff is collapsed.
......@@ -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