[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