[Checkins] SVN: Sandbox/shane/republish/zope/p worked on requests and relative link fixing

Shane Hathaway shane at hathawaymix.org
Fri Feb 13 05:53:16 EST 2009


Log message for revision 96490:
  worked on requests and relative link fixing
  

Changed:
  A   Sandbox/shane/republish/zope/pipeline/apps/fixrel.py
  U   Sandbox/shane/republish/zope/pipeline/apps/mapply.py
  U   Sandbox/shane/republish/zope/pipeline/apps/requestsetup.py
  U   Sandbox/shane/republish/zope/pipeline/configure.zcml
  U   Sandbox/shane/republish/zope/pipeline/entry.py
  A   Sandbox/shane/republish/zope/pipeline/request.py
  U   Sandbox/shane/republish/zope/publisher/interfaces/base.py
  U   Sandbox/shane/republish/zope/publisher/interfaces/http.py

-=-
Added: Sandbox/shane/republish/zope/pipeline/apps/fixrel.py
===================================================================
--- Sandbox/shane/republish/zope/pipeline/apps/fixrel.py	                        (rev 0)
+++ Sandbox/shane/republish/zope/pipeline/apps/fixrel.py	2009-02-13 10:53:16 UTC (rev 96490)
@@ -0,0 +1,72 @@
+##############################################################################
+#
+# Copyright (c) 2009 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.
+#
+##############################################################################
+
+
+class FixRelativeLinks(object):
+    """WSGI middleware that fixes relative links.
+
+    This application deals with request URLs that are problematic
+    for relative links in HTML because the requested URL is too different
+    from the traversed object's canonical URL.  It fixes the problem by
+    either redirecting (before calling the app) or adding a base tag
+    to the response text (after calling the app).
+    """
+    implements(IWSGIApplication)
+    adapts(IWSGIApplication)
+
+    allow_redirect = True
+
+    def __init__(self, app):
+        self.app = app
+
+    def __call__(self, environ, start_response):
+        request = environ['zope.request']
+
+        need_fix = False
+        allow_redirect = False
+
+        if request.form_action:
+            # When there is a :method or :action form variable, we need to
+            # fix relative URLs, but not by redirecting.
+            need_fix = True
+        else:
+            # If the URL ends with a slash, the URL specified one default
+            # traversal step, otherwise the URL specified zero.
+            # Compare the number of default traversal steps
+            # specified by the URL with the number of default traversal
+            # steps actually performed.  Set need_fix to True if
+            # the specified number does not match the actual.
+            if environ['PATH_INFO'].endswith('/'):
+                specified = 1
+            else:
+                specified = 0
+            actual = request.traversed_default
+            if actual != specified:
+                need_fix = True
+                allow_redirect = (
+                    self.allow_redirect and request.method == 'GET')
+
+        if not need_fix:
+            # No fix required
+            return self.app(environ, start_response)
+
+        if redirect:
+            # Redirect, then end the pipeline early
+            request.response.redirect(request.getURL())
+            start_response(response.getStatusString(), response.getHeaders())
+            return response.consumeBodyIter()
+
+        # TODO: Call the app.  Buffer and alter the response
+        # if the result is HTML.
+

Modified: Sandbox/shane/republish/zope/pipeline/apps/mapply.py
===================================================================
--- Sandbox/shane/republish/zope/pipeline/apps/mapply.py	2009-02-13 10:51:03 UTC (rev 96489)
+++ Sandbox/shane/republish/zope/pipeline/apps/mapply.py	2009-02-13 10:53:16 UTC (rev 96490)
@@ -28,7 +28,7 @@
     def __call__(self, environ, start_response):
         request = environ['zope.request']
         name, ob = request.traversed[-1]
-        result = mapply(ob, request.positional_arguments, request)
+        result = mapply(ob, (), request)
         response = request.response
         if result is not response:
             response.setResult(result)

Modified: Sandbox/shane/republish/zope/pipeline/apps/requestsetup.py
===================================================================
--- Sandbox/shane/republish/zope/pipeline/apps/requestsetup.py	2009-02-13 10:51:03 UTC (rev 96489)
+++ Sandbox/shane/republish/zope/pipeline/apps/requestsetup.py	2009-02-13 10:53:16 UTC (rev 96490)
@@ -31,6 +31,8 @@
 
     Chooses the type of request based on the content of the WSGI
     environment.
+
+    Also sets the request locale and skin.
     """
     implements(IWSGIApplication)
     adapts(IWSGIApplication)
@@ -46,13 +48,36 @@
             scheme, method, mimetype, environ)
         environ['zope.request'] = request
 
+        self.set_locale(request)
+        self.set_skin(request)
+
+        return self.app(environ, start_response)
+
+    def set_locale(self, request):
+        envadapter = IUserPreferredLanguages(request, None)
+        if envadapter is None:
+            return
+
+        langs = envadapter.getPreferredLanguages()
+        for httplang in langs:
+            parts = (httplang.split('-') + [None, None])[:3]
+            try:
+                request.locale = locales.getLocale(*parts)
+                return
+            except LoadLocaleError:
+                # Just try the next combination
+                pass
+        else:
+            # No combination gave us an existing locale, so use the default,
+            # which is guaranteed to exist
+            request.locale = locales.getLocale(None, None, None)
+
+    def set_skin(self, request):
         if IBrowserRequest.providedBy(request):
             # only browser requests have skins
             setDefaultSkin(request)
 
-        return self.app(environ, start_response)
 
-
 class ProcessForm(object):
     """WSGI middleware that processes HTML form data.
 
@@ -72,13 +97,12 @@
     def __call__(self, environ, start_response):
         request = environ['zope.request']
 
+        charsets = []
         def to_unicode(text):
-            charsets = request.charsets
             if not charsets:
                 envadapter = IUserPreferredCharsets(request)
-                charsets = envadapter.getPreferredCharsets() or ['utf-8']
-                request.charsets = charsets
-
+                charsets.extend(
+                    envadapter.getPreferredCharsets() or ['utf-8'])
             for charset in charsets:
                 try:
                     return unicode(text, charset)

Modified: Sandbox/shane/republish/zope/pipeline/configure.zcml
===================================================================
--- Sandbox/shane/republish/zope/pipeline/configure.zcml	2009-02-13 10:51:03 UTC (rev 96489)
+++ Sandbox/shane/republish/zope/pipeline/configure.zcml	2009-02-13 10:53:16 UTC (rev 96490)
@@ -7,6 +7,7 @@
      unnamed adapter from the request type to IPipelineApplicationList. -->
 
 <wsgi:pipeline for=".interfaces.IUndecidedRequest" names="
+    virtual_host
     retry
     create_request
     switch_pipeline
@@ -22,6 +23,7 @@
     authenticate
     traverse
     annotate_transaction
+    fix_relative_links
     call
     " />
 

Modified: Sandbox/shane/republish/zope/pipeline/entry.py
===================================================================
--- Sandbox/shane/republish/zope/pipeline/entry.py	2009-02-13 10:51:03 UTC (rev 96489)
+++ Sandbox/shane/republish/zope/pipeline/entry.py	2009-02-13 10:53:16 UTC (rev 96490)
@@ -30,12 +30,23 @@
 cleanup.addCleanUp(_pipeline_cache.clear)
 
 def get_database_pipeline(database, global_environ=None):
-    if global_environ is None:
-        global_environ = {}
-    global_environ['zope.database'] = database
+    """Get a pipeline that will connect to the given database.
+
+    The returned pipeline can be used for many requests, even
+    concurrently.
+    """
+    d = {}
+    if global_environ is not None:
+        d.update(global_environ)
+    d['zope.database'] = database
     return get_pipeline(global_environ=global_environ)
 
 def get_pipeline(request=None, global_environ=None):
+    """Get a pipeline.
+
+    The returned pipeline can be used for many requests, even
+    concurrently.
+    """
     if request is None:
         provided = (IUndecidedRequest,)
     else:

Added: Sandbox/shane/republish/zope/pipeline/request.py
===================================================================
--- Sandbox/shane/republish/zope/pipeline/request.py	                        (rev 0)
+++ Sandbox/shane/republish/zope/pipeline/request.py	2009-02-13 10:53:16 UTC (rev 96490)
@@ -0,0 +1,208 @@
+##############################################################################
+#
+# Copyright (c) 2009 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.
+#
+##############################################################################
+"""Standard Zope request classes.
+"""
+
+from zope.interface import implements
+from zope.publisher.interfaces import IDebugFlags
+from zope.publisher.interfaces import IRequest
+from zope.publisher.interfaces.http import IHTTPRequest
+
+from zope.pipeline.response import BaseResponse
+
+
+class DebugFlags(object):
+    implements(IDebugFlags)
+
+    sourceAnnotations = False
+    showTAL = False
+
+
+class BaseRequest(object):
+    """Represents a publishing request.
+
+    This object provides access to request data. Request data may
+    vary depending on the protocol used.
+
+    Request objects are created by the object publisher and will be
+    passed to published objects through the argument name, REQUEST.
+
+    The request object is a mapping object that represents a
+    collection of variable to value mappings.
+    """
+
+    implements(IRequest)
+
+    __slots__ = (
+        '__provides__',      # Allow request to directly provide interfaces
+        'environment',       # The request environment (CGI. WSGI, or similar)
+        'bodyStream',        # body input stream
+        'traversal_stack',   # Names to be traversed, in reverse order
+        'traversal_hooks',   # list of functions to call during traversal
+        'traversed',         # list of (name, obj) that have been traversed
+        'traversed_default', # number of steps added by default traversal
+        'principal',         # authorized principal
+        'debug',             # debug flags
+        'interaction',       # interaction, set by interaction
+        'annotations',       # per-package annotations
+        'response',          # The response
+        'locale',            # The locale for the request
+        )
+
+    _response_factory = BaseResponse
+
+    def __init__(self, environ):
+        self.environment = environ
+        self.bodyStream = environ.get('wsgi.input')
+        self.traversal_stack = []
+        self.traversal_hooks = []
+        self.traversed = []
+        self.traversed_default = 0
+        self.principal = None
+        self.debug = DebugFlags()
+        self.interaction = None
+        self.annotations = {}
+        self.response = self._response_factory()
+        self.locale = None
+
+    def getTraversalStack(self):
+        """Deprecated"""
+        return self.traversal_stack
+
+    def setTraversalStack(self, stack):
+        """Deprecated"""
+        self.traversal_stack[:] = list(stack)
+
+    def setPrincipal(self, principal):
+        """Deprecated"""
+        self.principal = principal
+
+    def getBasicCredentials(self):
+        return None
+
+    def _authUserPW(self):
+        """Deprecated"""
+        return self.getBasicCredentials()
+
+    def unauthorized(self, challenge):
+        """Deprecated"""
+        self.response.unauthorized(challenge)
+
+"""
+    def __len__(self):
+        'See Interface.Common.Mapping.IEnumerableMapping'
+        return len(self.keys())
+
+    def items(self):
+        'See Interface.Common.Mapping.IEnumerableMapping'
+        result = []
+        get = self.get
+        for k in self.keys():
+            result.append((k, get(k)))
+        return result
+
+    def keys(self):
+        'See Interface.Common.Mapping.IEnumerableMapping'
+        return self._environ.keys()
+
+    def __iter__(self):
+        return iter(self.keys())
+
+    def values(self):
+        'See Interface.Common.Mapping.IEnumerableMapping'
+        result = []
+        get = self.get
+        for k in self.keys():
+            result.append(get(k))
+        return result
+
+    def __getitem__(self, key):
+        'See Interface.Common.Mapping.IReadMapping'
+        result = self.get(key, _marker)
+        if result is _marker:
+            raise KeyError(key)
+        else:
+            return result
+
+    def get(self, key, default=None):
+        'See Interface.Common.Mapping.IReadMapping'
+        result = self._environ.get(key, _marker)
+        if result is not _marker:
+            return result
+
+        return default
+
+    def __contains__(self, key):
+        'See Interface.Common.Mapping.IReadMapping'
+        lookup = self.get(key, self)
+        return lookup is not self
+
+    has_key = __contains__
+
+    def __nonzero__(self):
+        # This is here to avoid calling __len__ for boolean tests
+        return 1
+
+"""
+
+    def __str__(self):
+        items = self.items()
+        items.sort()
+        return "\n".join("%s:\t%s" % item for item in items)
+
+"""
+    def _setupPath_helper(self, attr):
+        path = self.get(attr, "/")
+        if path.endswith('/'):
+            # Remove trailing backslash, so that we will not get an empty
+            # last entry when splitting the path.
+            path = path[:-1]
+            self._endswithslash = True
+        else:
+            self._endswithslash = False
+
+        clean = []
+        for item in path.split('/'):
+            if not item or item == '.':
+                continue
+            elif item == '..':
+                try:
+                    del clean[-1]
+                except IndexError:
+                    raise NotFound('..')
+            else: clean.append(item)
+
+        clean.reverse()
+        self.setTraversalStack(clean)
+
+        self._path_suffix = None
+"""
+
+class HTTPRequest(BaseRequest):
+    implements(IHTTPRequest)
+
+    __slots__ = (
+        'method',         # The upper-cased request method (REQUEST_METHOD)
+        '_cookies',       # The request cookies
+        'form',
+        'form_action',
+        )
+        #'_app_names',     # The application path as a sequence
+        #'_app_server',    # The server path of the application url
+        #'_endswithslash', # Does the given path end with /
+
+    def __init__(self, environ):
+        super(HTTPRequest, self).__init__(environ)
+        self.method = environ.get("REQUEST_METHOD", 'GET').upper()
+        

Modified: Sandbox/shane/republish/zope/publisher/interfaces/base.py
===================================================================
--- Sandbox/shane/republish/zope/publisher/interfaces/base.py	2009-02-13 10:51:03 UTC (rev 96489)
+++ Sandbox/shane/republish/zope/publisher/interfaces/base.py	2009-02-13 10:53:16 UTC (rev 96490)
@@ -5,7 +5,7 @@
 from zope.interface import Interface
 from zope.interface.common.mapping import IExtendedReadMapping
 
-__all__ = ('IRequest', 'IResponse', 'IResult', 'IHeld', 'IDebugFlags',
+__all__ = ('IRequest', 'IResponse', 'IResult', 'IDebugFlags',
     'IWSGIApplication')
 
 
@@ -16,21 +16,25 @@
     values will be looked up in the order: [TODO].
     """
 
-    response = Attribute(
-        """The request's IResponse object
+    environment = Attribute(
+        """WSGI or CGI environment.
         """)
 
-    def close():
-        """Release resources held by the request.
-        """
+    bodyStream = Attribute(
+        """The stream that provides the data of the request.
 
-    def hold(held):
-        """Hold a reference to an object until the request is closed.
+        The data returned by the stream will not include any possible header
+        information, which should have been stripped by the server (or
+        previous layer) before.
 
-        The object should be an IHeld.  If it is an IHeld, its
-        release method will be called when it is released.
-        """
+        Also, the body stream might already be read and not return any
+        data. This is commonly done when retrieving the data for the ``body``
+        attribute.
 
+        If you access this stream directly to retrieve data, it will not be
+        possible by other parts of the framework to access the data of the
+        request via the ``body`` attribute.""")
+
     traversal_stack = Attribute(
         """The list of steps to traverse in reverse order.
 
@@ -39,53 +43,36 @@
         during traversal.
         """)
 
-    def getTraversalStack():
-        """Deprecated: use the traversal_stack attribute instead.
-        """
+    traversal_hooks = Attribute(
+        """List of hooks to call before each traversal step.
 
-    def setTraversalStack(stack):
-        """Deprecated: use the traversal_stack attribute instead.
+        Each hook will be called with two parameters, request and ob.
+        The hook does not need to return anything.
+
+        These hooks will be called before traversing an object for the
+        first time.  If the same object is traversed more than
+        once, the hook will still only be called the first time.
         """
 
-    positional_arguments = Attribute(
-        """The positional arguments passed to the request.
+    traversed = Attribute(
+        """List of (name, obj) steps that have been traversed.
+
+        The first object is the application root and may have an empty name.
+        The last object is the object to call.
         """)
 
-    def getPositionalArguments():
-        """Deprecated: use the positional_arguments attribute instead.
-        """
+    traversed_default = Attribute(
+        """The number of steps added by default traversal.
+        """)
 
-    def setPrincipal(principal):
-        """Deprecated: use the principal attribute instead.
-        """
-
     principal = Attribute(
         """IPrincipal object associated with the request.
         """)
 
-    bodyStream = Attribute(
-        """The stream that provides the data of the request.
-
-        The data returned by the stream will not include any possible header
-        information, which should have been stripped by the server (or
-        previous layer) before.
-
-        Also, the body stream might already be read and not return any
-        data. This is commonly done when retrieving the data for the ``body``
-        attribute.
-
-        If you access this stream directly to retrieve data, it will not be
-        possible by other parts of the framework to access the data of the
-        request via the ``body`` attribute.""")
-
     debug = Attribute("""Debug flags (see IDebugFlags).""")
 
-    environment = Attribute(
-        """Request environment data.
+    interaction = Attribute("""The Zope security interaction""")
 
-        This is a pointer to the WSGI or CGI environment.
-        """)
-
     annotations = Attribute(
         """Stores arbitrary application data under package-unique keys.
 
@@ -101,6 +88,24 @@
           "zope.persistentadapter"
         """)
 
+    response = Attribute(
+        """The request's IResponse object""")
+
+    locale = Attribute(
+        """The locale object associated with this request.""")
+
+    def getTraversalStack():
+        """Deprecated: use the traversal_stack attribute instead.
+        """
+
+    def setTraversalStack(stack):
+        """Deprecated: use the traversal_stack attribute instead.
+        """
+
+    def setPrincipal(principal):
+        """Deprecated: use the principal attribute instead.
+        """
+
     def getBasicCredentials():
         """Return (login, password) if the request contains basic credentials.
 
@@ -116,25 +121,7 @@
         """Deprecated: use response.unauthorized() instead.
         """
 
-    traversed = Attribute(
-        """List of (name, obj) steps that were traversed.
 
-        The first object is the application root and may have an empty name.
-        The last object is the object to call.
-        """)
-
-    traversal_hooks = Attribute(
-        """List of hooks to call before each traversal step.
-
-        Each hook will be called with two parameters, request and ob.
-        The hook does not need to return anything.
-
-        These hooks will be called before traversing an object for the
-        first time.  If the same object is traversed more than
-        once, the hook will still only be called the first time.
-        """
-
-
 class IResponse(Interface):
     """Holds a response result."""
 
@@ -249,18 +236,6 @@
         """
 
 
-class IHeld(Interface):
-    """Object to be held and explicitly released by a request
-    """
-
-    def release():
-        """Release the held object
-
-        This is called by a request that holds the IHeld when the
-        request is closed
-
-        """
-
 class IDebugFlags(Interface):
     """Features that support debugging."""
 

Modified: Sandbox/shane/republish/zope/publisher/interfaces/http.py
===================================================================
--- Sandbox/shane/republish/zope/publisher/interfaces/http.py	2009-02-13 10:51:03 UTC (rev 96489)
+++ Sandbox/shane/republish/zope/publisher/interfaces/http.py	2009-02-13 10:53:16 UTC (rev 96490)
@@ -50,19 +50,15 @@
 
     method = Attribute("Request method, normalized to upper case")
 
-    def setPathSuffix(steps):
-        """Deprecated: Prepend to traversal_stack instead.
-        """
-
-    locale = Attribute(
-        """The locale object associated with this request.""")
-
     form = Attribute(
         """Form data
 
-        This is a read-only mapping from name to form value for the name.
+        This is a mapping from name to form value for the name.
         """)
 
+    form_action = Attribute(
+        """The :action or :method specified by the form.""")
+
     def getCookies():
         """Return the cookie data
 



More information about the Checkins mailing list