[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