[Zope-Checkins]
SVN: Zope/branches/regebro-wsgi_support2/lib/python/ZPublisher/WSGIPublisher.py
I can't believe I forgot this file, and then deleted it.
Lennart Regebro
regebro at gmail.com
Sun Apr 30 11:40:18 EDT 2006
Log message for revision 67767:
I can't believe I forgot this file, and then deleted it.
Changed:
A Zope/branches/regebro-wsgi_support2/lib/python/ZPublisher/WSGIPublisher.py
-=-
Added: Zope/branches/regebro-wsgi_support2/lib/python/ZPublisher/WSGIPublisher.py
===================================================================
--- Zope/branches/regebro-wsgi_support2/lib/python/ZPublisher/WSGIPublisher.py 2006-04-30 15:25:18 UTC (rev 67766)
+++ Zope/branches/regebro-wsgi_support2/lib/python/ZPublisher/WSGIPublisher.py 2006-04-30 15:40:18 UTC (rev 67767)
@@ -0,0 +1,477 @@
+##############################################################################
+#
+# Copyright (c) 2002 Zope Corporation and Contributors. All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 2.1 (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
+#
+##############################################################################
+__doc__="""Python Object Publisher -- Publish Python objects on web servers
+
+$Id: Publish.py 67721 2006-04-28 14:57:35Z regebro $"""
+
+import sys, os, re, time
+import transaction
+from Response import Response
+from Request import Request
+from maybe_lock import allocate_lock
+from mapply import mapply
+from zExceptions import Redirect
+from cStringIO import StringIO
+from ZServer.medusa.http_date import build_http_date
+
+class WSGIResponse(Response):
+ """A response object for WSGI
+
+ This Response object knows nothing about ZServer, but tries to be
+ compatible with the ZServerHTTPResponse.
+
+ Most significantly, streaming is not (yet) supported."""
+
+ _streaming = 0
+
+ def __str__(self,
+ html_search=re.compile('<html>',re.I).search,
+ ):
+ if self._wrote:
+ if self._chunking:
+ return '0\r\n\r\n'
+ else:
+ return ''
+
+ headers=self.headers
+ body=self.body
+
+ # set 204 (no content) status if 200 and response is empty
+ # and not streaming
+ if not headers.has_key('content-type') and \
+ not headers.has_key('content-length') and \
+ not self._streaming and \
+ self.status == 200:
+ self.setStatus('nocontent')
+
+ # add content length if not streaming
+ if not headers.has_key('content-length') and \
+ not self._streaming:
+ self.setHeader('content-length',len(body))
+
+
+ content_length= headers.get('content-length', None)
+ if content_length>0 :
+ self.setHeader('content-length', content_length)
+
+ headersl=[]
+ append=headersl.append
+
+ status=headers.get('status', '200 OK')
+
+ # status header must come first.
+ append("HTTP/%s %s" % (self._http_version or '1.0' , status))
+ if headers.has_key('status'):
+ del headers['status']
+
+ # add zserver headers
+ append('Server: %s' % self._server_version)
+ append('Date: %s' % build_http_date(time.time()))
+
+ if self._http_version=='1.0':
+ if self._http_connection=='keep-alive' and \
+ self.headers.has_key('content-length'):
+ self.setHeader('Connection','Keep-Alive')
+ else:
+ self.setHeader('Connection','close')
+
+ # Close the connection if we have been asked to.
+ # Use chunking if streaming output.
+ if self._http_version=='1.1':
+ if self._http_connection=='close':
+ self.setHeader('Connection','close')
+ elif not self.headers.has_key('content-length'):
+ if self.http_chunk and self._streaming:
+ self.setHeader('Transfer-Encoding','chunked')
+ self._chunking=1
+ else:
+ self.setHeader('Connection','close')
+
+ 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)
+ append("%s: %s" % (key, val))
+ if self.cookies:
+ headersl=headersl+self._cookie_list()
+ headersl[len(headersl):]=[self.accumulated_headers, body]
+ return "\r\n".join(headersl)
+
+
+class Retry(Exception):
+ """Raise this to retry a request
+ """
+
+ def __init__(self, t=None, v=None, tb=None):
+ self._args=t, v, tb
+
+ def reraise(self):
+ t, v, tb = self._args
+ if t is None: t=Retry
+ if tb is None: raise t, v
+ try: raise t, v, tb
+ finally: tb=None
+
+def call_object(object, args, request):
+ result=apply(object,args) # Type s<cr> to step into published object.
+ return result
+
+def missing_name(name, request):
+ if name=='self': return request['PARENTS'][0]
+ request.response.badRequestError(name)
+
+def dont_publish_class(klass, request):
+ request.response.forbiddenError("class %s" % klass.__name__)
+
+_default_debug_mode = False
+_default_realm = None
+
+def set_default_debug_mode(debug_mode):
+ global _default_debug_mode
+ _default_debug_mode = debug_mode
+
+def set_default_authentication_realm(realm):
+ global _default_realm
+ _default_realm = realm
+
+def publish(request, module_name, after_list, debug=0,
+ # Optimize:
+ call_object=call_object,
+ missing_name=missing_name,
+ dont_publish_class=dont_publish_class,
+ mapply=mapply,
+ ):
+
+ (bobo_before, bobo_after, object, realm, debug_mode, err_hook,
+ validated_hook, transactions_manager)= get_module_info(module_name)
+
+ parents=None
+ response=None
+ try:
+ request.processInputs()
+
+ request_get=request.get
+ response=request.response
+
+ # First check for "cancel" redirect:
+ if request_get('SUBMIT','').strip().lower()=='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 the path list.
+ # According to RFC1738 a trailing space in the path is valid.
+ path=request_get('PATH_INFO')
+
+ request['PARENTS']=parents=[object]
+
+ if transactions_manager:
+ transactions_manager.begin()
+
+ object=request.traverse(path, validated_hook=validated_hook)
+
+ if transactions_manager:
+ transactions_manager.recordMetaData(object, request)
+
+ result=mapply(object, request.args, request,
+ call_object,1,
+ missing_name,
+ dont_publish_class,
+ request, bind=1)
+
+ if result is not response:
+ response.setBody(result)
+
+ if transactions_manager:
+ transactions_manager.commit()
+
+ return response
+ except:
+
+ # DM: provide nicer error message for FTP
+ sm = None
+ if response is not None:
+ sm = getattr(response, "setMessage", None)
+
+ if sm is not None:
+ from asyncore import compact_traceback
+ cl,val= sys.exc_info()[:2]
+ sm('%s: %s %s' % (
+ getattr(cl,'__name__',cl), val,
+ debug_mode and compact_traceback()[-1] or ''))
+
+ if err_hook is not None:
+ if parents:
+ parents=parents[0]
+ try:
+ try:
+ return err_hook(parents, request,
+ sys.exc_info()[0],
+ sys.exc_info()[1],
+ sys.exc_info()[2],
+ )
+ except Retry:
+ if not request.supports_retry():
+ return err_hook(parents, request,
+ sys.exc_info()[0],
+ sys.exc_info()[1],
+ sys.exc_info()[2],
+ )
+ finally:
+ if transactions_manager:
+ transactions_manager.abort()
+
+ # Only reachable if Retry is raised and request supports retry.
+ newrequest=request.retry()
+ request.close() # Free resources held by the request.
+ try:
+ return publish(newrequest, module_name, after_list, debug)
+ finally:
+ newrequest.close()
+
+ else:
+ if transactions_manager:
+ transactions_manager.abort()
+ raise
+
+def publish_module_standard(environ, start_response):
+
+ must_die=0
+ status=200
+ after_list=[None]
+ stdout = StringIO()
+ stderr = StringIO()
+ response = WSGIResponse(stdout=stdout, stderr=stderr)
+ response._http_version = environ['SERVER_PROTOCOL'].split('/')[1]
+ response._http_connection = environ.get('CONNECTION_TYPE', 'close')
+ response._server_version = environ['SERVER_SOFTWARE']
+
+ request = Request(environ['wsgi.input'], environ, response)
+
+ # Let's support post-mortem debugging
+ handle_errors = environ.get('wsgi.handleErrors', True)
+
+ try:
+ response = publish(request, 'Zope2', after_list=[None],
+ debug=handle_errors)
+ except SystemExit, v:
+ must_die=sys.exc_info()
+ request.response.exception(must_die)
+ except ImportError, v:
+ if isinstance(v, tuple) and len(v)==3: must_die=v
+ elif hasattr(sys, 'exc_info'): must_die=sys.exc_info()
+ else: must_die = SystemExit, v, sys.exc_info()[2]
+ request.response.exception(1, v)
+ except:
+ request.response.exception()
+ status=response.getStatus()
+
+ if response:
+ # Start the WSGI server response
+ status = response.getHeader('status')
+ # ZServerHTTPResponse calculates all headers and things when you
+ # call it's __str__, so we need to get it, and then munge out
+ # the headers from it. It's a bit backwards, and we might optimize
+ # this by not using ZServerHTTPResponse at all, and making the
+ # HTTPResponses more WSGI friendly. But this works.
+ result = str(response)
+ headers, body = result.split('\r\n\r\n',1)
+ headers = [tuple(n.split(': ',1)) for n in headers.split('\r\n')[1:]]
+ start_response(status, headers)
+ # If somebody used response.write, that data will be in the
+ # stdout StringIO, so we put that before the body.
+ # XXX This still needs verification that it really works.
+ result=(stdout.getvalue(), body)
+ request.close()
+ stdout.close()
+
+ if after_list[0] is not None: after_list[0]()
+
+ if must_die:
+ # Try to turn exception value into an exit code.
+ try:
+ if hasattr(must_die[1], 'code'):
+ code = must_die[1].code
+ else: code = int(must_die[1])
+ except:
+ code = must_die[1] and 1 or 0
+ if hasattr(request.response, '_requestShutdown'):
+ request.response._requestShutdown(code)
+
+ try: raise must_die[0], must_die[1], must_die[2]
+ finally: must_die=None
+
+ # Return the result body iterable.
+ return result
+
+
+_l=allocate_lock()
+def get_module_info(module_name, modules={},
+ acquire=_l.acquire,
+ release=_l.release,
+ ):
+
+ if modules.has_key(module_name): return modules[module_name]
+
+ if module_name[-4:]=='.cgi': module_name=module_name[:-4]
+
+ acquire()
+ tb=None
+ g = globals()
+ try:
+ try:
+ module=__import__(module_name, g, g, ('__doc__',))
+
+ # Let the app specify a realm
+ if hasattr(module,'__bobo_realm__'):
+ realm=module.__bobo_realm__
+ elif _default_realm is not None:
+ realm=_default_realm
+ else:
+ realm=module_name
+
+ # Check for debug mode
+ debug_mode=None
+ if hasattr(module,'__bobo_debug_mode__'):
+ debug_mode=not not module.__bobo_debug_mode__
+ else:
+ debug_mode = _default_debug_mode
+
+ bobo_before = getattr(module, "__bobo_before__", None)
+ bobo_after = getattr(module, "__bobo_after__", None)
+
+ if hasattr(module,'bobo_application'):
+ object=module.bobo_application
+ elif hasattr(module,'web_objects'):
+ object=module.web_objects
+ else: object=module
+
+ error_hook=getattr(module,'zpublisher_exception_hook', None)
+ validated_hook=getattr(module,'zpublisher_validated_hook', None)
+
+ transactions_manager=getattr(
+ module,'zpublisher_transactions_manager', None)
+ if not transactions_manager:
+ # Create a default transactions manager for use
+ # by software that uses ZPublisher and ZODB but
+ # not the rest of Zope.
+ transactions_manager = DefaultTransactionsManager()
+
+ info= (bobo_before, bobo_after, object, realm, debug_mode,
+ error_hook, validated_hook, transactions_manager)
+
+ modules[module_name]=modules[module_name+'.cgi']=info
+
+ return info
+ except:
+ t,v,tb=sys.exc_info()
+ v=str(v)
+ raise ImportError, (t, v), tb
+ finally:
+ tb=None
+ release()
+
+
+class DefaultTransactionsManager:
+ def begin(self):
+ transaction.begin()
+ def commit(self):
+ transaction.commit()
+ def abort(self):
+ transaction.abort()
+ def recordMetaData(self, object, request):
+ # Is this code needed?
+ request_get = request.get
+ T= transaction.get()
+ T.note(request_get('PATH_INFO'))
+ auth_user=request_get('AUTHENTICATED_USER',None)
+ if auth_user is not None:
+ T.setUser(auth_user, request_get('AUTHENTICATION_PATH'))
+
+# profiling support
+
+_pfile = None # profiling filename
+_plock=allocate_lock() # profiling lock
+_pfunc=publish_module_standard
+_pstat=None
+
+def install_profiling(filename):
+ global _pfile
+ _pfile = filename
+
+def pm(environ, start_response):
+ try:
+ r=_pfunc(environ, start_response)
+ except: r=None
+ sys._pr_=r
+
+def publish_module_profiled(environ, start_response):
+ import profile, pstats
+ global _pstat
+ _plock.acquire()
+ try:
+ if request is not None:
+ path_info=request.get('PATH_INFO')
+ else: path_info=environ.get('PATH_INFO')
+ if path_info[-14:]=='manage_profile':
+ return _pfunc(environ, start_response)
+ pobj=profile.Profile()
+ pobj.runcall(pm, menviron, start_response)
+ result=sys._pr_
+ pobj.create_stats()
+ if _pstat is None:
+ _pstat=sys._ps_=pstats.Stats(pobj)
+ else: _pstat.add(pobj)
+ finally:
+ _plock.release()
+
+ if result is None:
+ try:
+ error=sys.exc_info()
+ file=open(_pfile, 'w')
+ file.write(
+ "See the url "
+ "http://www.python.org/doc/current/lib/module-profile.html"
+ "\n for information on interpreting profiler statistics.\n\n"
+ )
+ sys.stdout=file
+ _pstat.strip_dirs().sort_stats('cumulative').print_stats(250)
+ _pstat.strip_dirs().sort_stats('time').print_stats(250)
+ file.flush()
+ file.close()
+ except: pass
+ raise error[0], error[1], error[2]
+ return result
+
+def publish_module(environ, start_response):
+ """ publish a Python module, with or without profiling enabled """
+ if _pfile: # profiling is enabled
+ return publish_module_profiled(environ, start_response)
+ else:
+ return publish_module_standard(environ, start_response)
+
More information about the Zope-Checkins
mailing list