Commit 7abca36a authored by Lennart Regebro's avatar Lennart Regebro

Complete WSGI support rewrite.

...@@ -49,6 +49,17 @@ Zope Changes ...@@ -49,6 +49,17 @@ Zope Changes
Features added Features added
- The traversal has been refactored to take heed of Zope3s
IPublishTraverse adapter interfaces. The ZCML directives
five:traversable and five:defaultViewable are therefore no
longer needed, as everything now is five:traversable and
five:defaultViewable.
There was a bug in earlier versions of Five that allowed you
to do custom publishing traversal with ITraversable adapters.
This bug has been corrected. Anybody using ITraversable
adapters need to convert them to IPublishTraversal adapters.
- Testing.makerequest: Added an 'environ' argument so - Testing.makerequest: Added an 'environ' argument so
clients can use mappings other than os.environ. clients can use mappings other than os.environ.
......
...@@ -25,9 +25,14 @@ from AccessControl.ZopeGuards import guarded_getattr ...@@ -25,9 +25,14 @@ from AccessControl.ZopeGuards import guarded_getattr
from Acquisition import Acquired, aq_inner, aq_parent, aq_base from Acquisition import Acquired, aq_inner, aq_parent, aq_base
from zExceptions import NotFound from zExceptions import NotFound
from ZODB.POSException import ConflictError from ZODB.POSException import ConflictError
from zope.interface import implements from zope.interface import implements, Interface
from interfaces import ITraversable from interfaces import ITraversable
from zope.app.traversing.interfaces import ITraversable as IZope3Traversable
from zope.component import queryMultiAdapter
from zope.app.traversing.interfaces import TraversalError
from zope.app.traversing.namespace import nsParse
from zope.app.traversing.namespace import namespaceLookup
_marker = object() _marker = object()
...@@ -59,6 +64,7 @@ class Traversable: ...@@ -59,6 +64,7 @@ class Traversable:
return self.virtual_url_path() return self.virtual_url_path()
spp = self.getPhysicalPath() spp = self.getPhysicalPath()
try: try:
toUrl = self.REQUEST.physicalPathToURL toUrl = self.REQUEST.physicalPathToURL
except AttributeError: except AttributeError:
...@@ -133,7 +139,6 @@ class Traversable: ...@@ -133,7 +139,6 @@ class Traversable:
If true, then all of the objects along the path are validated with If true, then all of the objects along the path are validated with
the security machinery. Usually invoked using restrictedTraverse(). the security machinery. Usually invoked using restrictedTraverse().
""" """
if not path: if not path:
return self return self
...@@ -188,7 +193,19 @@ class Traversable: ...@@ -188,7 +193,19 @@ class Traversable:
continue continue
bobo_traverse = _getattr(obj, '__bobo_traverse__', _none) bobo_traverse = _getattr(obj, '__bobo_traverse__', _none)
if bobo_traverse is not _none: if name and name[:1] in '@+':
# Process URI segment parameters.
ns, nm = nsParse(name)
if ns:
try:
next = namespaceLookup(ns, nm, obj,
self.REQUEST).__of__(obj)
if restricted and not securityManager.validate(
obj, obj, name, next):
raise Unauthorized, name
except TraversalError:
raise AttributeError(name)
elif bobo_traverse is not _none:
next = bobo_traverse(REQUEST, name) next = bobo_traverse(REQUEST, name)
if restricted: if restricted:
if aq_base(next) is not next: if aq_base(next) is not next:
...@@ -228,11 +245,20 @@ class Traversable: ...@@ -228,11 +245,20 @@ class Traversable:
next = _getattr(obj, name, marker) next = _getattr(obj, name, marker)
if next is marker: if next is marker:
try: try:
next=obj[name] try:
except AttributeError: next=obj[name]
# Raise NotFound for easier debugging except AttributeError:
# instead of AttributeError: __getitem__ # Raise NotFound for easier debugging
raise NotFound, name # instead of AttributeError: __getitem__
raise NotFound, name
except (NotFound, KeyError):
# Try to look for a view
next = queryMultiAdapter((obj, self.REQUEST),
Interface, name)
if next is None:
# Didn't find one, reraise the error:
raise
next = next.__of__(obj)
if restricted and not securityManager.validate( if restricted and not securityManager.validate(
obj, obj, _none, next): obj, obj, _none, next):
raise Unauthorized, name raise Unauthorized, name
......
...@@ -248,12 +248,28 @@ class NotExpr: ...@@ -248,12 +248,28 @@ class NotExpr:
def __repr__(self): def __repr__(self):
return 'not:%s' % `self._s` return 'not:%s' % `self._s`
from zope.interface import Interface, implements
from zope.component import queryMultiAdapter
from zope.app.traversing.namespace import nsParse
from zope.app.traversing.namespace import namespaceLookup
from zope.app.traversing.interfaces import TraversalError
from zope.publisher.interfaces.browser import IBrowserRequest
from zope.app.publication.browser import setDefaultSkin
class FakeRequest(dict):
implements(IBrowserRequest)
def getURL(self):
return "http://codespeak.net/z3/five"
def restrictedTraverse(object, path, securityManager, def restrictedTraverse(object, path, securityManager,
get=getattr, has=hasattr, N=None, M=[], get=getattr, has=hasattr, N=None, M=[],
TupleType=type(()) ): TupleType=type(()) ):
REQUEST = {'path': path} REQUEST = FakeRequest()
REQUEST['path'] = path
REQUEST['TraversalRequestNameStack'] = path = path[:] # Copy! REQUEST['TraversalRequestNameStack'] = path = path[:] # Copy!
setDefaultSkin(REQUEST)
path.reverse() path.reverse()
validate = securityManager.validate validate = securityManager.validate
__traceback_info__ = REQUEST __traceback_info__ = REQUEST
...@@ -282,7 +298,18 @@ def restrictedTraverse(object, path, securityManager, ...@@ -282,7 +298,18 @@ def restrictedTraverse(object, path, securityManager,
continue continue
t=get(object, '__bobo_traverse__', N) t=get(object, '__bobo_traverse__', N)
if t is not N: if name and name[:1] in '@+':
# Process URI segment parameters.
ns, nm = nsParse(name)
if ns:
try:
o = namespaceLookup(ns, nm, object,
REQUEST).__of__(object)
if not validate(object, object, name, o):
raise Unauthorized, name
except TraversalError:
raise AttributeError(name)
elif t is not N:
o=t(REQUEST, name) o=t(REQUEST, name)
container = None container = None
...@@ -305,7 +332,16 @@ def restrictedTraverse(object, path, securityManager, ...@@ -305,7 +332,16 @@ def restrictedTraverse(object, path, securityManager,
# XXX maybe in Python 2.2 we can just check whether # XXX maybe in Python 2.2 we can just check whether
# the object has the attribute "__getitem__" # the object has the attribute "__getitem__"
# instead of blindly catching exceptions. # instead of blindly catching exceptions.
o = object[name] try:
o = object[name]
except (AttributeError, KeyError):
# Try to look for a view
o = queryMultiAdapter((object, REQUEST),
Interface, name)
if o is None:
# Didn't find one, reraise the error:
raise
o = o.__of__(object)
except AttributeError, exc: except AttributeError, exc:
if str(exc).find('__getitem__') >= 0: if str(exc).find('__getitem__') >= 0:
# The object does not support the item interface. # The object does not support the item interface.
......
This diff is collapsed.
...@@ -122,7 +122,6 @@ def publish(request, module_name, after_list, debug=0, ...@@ -122,7 +122,6 @@ def publish(request, module_name, after_list, debug=0,
return response return response
except: except:
# DM: provide nicer error message for FTP # DM: provide nicer error message for FTP
sm = None sm = None
if response is not None: if response is not None:
......
...@@ -311,6 +311,15 @@ class ChannelPipe: ...@@ -311,6 +311,15 @@ class ChannelPipe:
self._close=1 self._close=1
self._request.reply_code=response.status self._request.reply_code=response.status
def start_response(self, status, headers, exc_info=None):
# Used for WSGI
status = 'HTTP/%s %s\r\n' % (self._request.version, status)
self.write(status)
headers = '\r\n'.join([': '.join(x) for x in headers])
self.write(headers)
self.write('\r\n\r\n')
return self.write
is_proxying_match = re.compile(r'[^ ]* [^ \\]*:').match is_proxying_match = re.compile(r'[^ ]* [^ \\]*:').match
proxying_connection_re = re.compile ('Proxy-Connection: (.*)', re.IGNORECASE) proxying_connection_re = re.compile ('Proxy-Connection: (.*)', re.IGNORECASE)
......
...@@ -279,6 +279,48 @@ class zhttp_handler: ...@@ -279,6 +279,48 @@ class zhttp_handler:
</ul>""" %(self.module_name, self.hits) </ul>""" %(self.module_name, self.hits)
) )
from HTTPResponse import ChannelPipe
class zwsgi_handler(zhttp_handler):
def continue_request(self, sin, request):
"continue handling request now that we have the stdin"
s=get_header(CONTENT_LENGTH, request.header)
if s:
s=int(s)
else:
s=0
DebugLogger.log('I', id(request), s)
env=self.get_environment(request)
version = request.version
if version=='1.0' and is_proxying_match(request.request):
# a request that was made as if this zope was an http 1.0 proxy.
# that means we have to use some slightly different http
# headers to manage persistent connections.
connection_re = proxying_connection_re
else:
# a normal http request
connection_re = CONNECTION
env['http_connection'] = get_header(connection_re,
request.header).lower()
env['server_version']=request.channel.server.SERVER_IDENT
env['wsgi.output'] = ChannelPipe(request)
env['wsgi.input'] = sin
env['wsgi.errors'] = sys.stderr
env['wsgi.version'] = (1,0)
env['wsgi.multithread'] = True
env['wsgi.multiprocess'] = True
env['wsgi.run_once'] = True
env['wsgi.url_scheme'] = env['SERVER_PROTOCOL'].split('/')[0]
request.channel.current_request=None
request.channel.queue.append(('Zope2WSGI', env,
env['wsgi.output'].start_response))
request.channel.work()
class zhttp_channel(http_channel): class zhttp_channel(http_channel):
......
...@@ -14,13 +14,24 @@ ...@@ -14,13 +14,24 @@
class ZServerPublisher: class ZServerPublisher:
def __init__(self, accept): def __init__(self, accept):
from ZPublisher import publish_module from ZPublisher import publish_module
from ZPublisher.WSGIPublisher import publish_module as publish_wsgi
while 1: while 1:
try: name, a, b=accept()
name, request, response=accept() if name == "Zope2":
publish_module( try:
name, publish_module(
request=request, name,
response=response) request=a,
finally: response=b)
response._finish() finally:
request=response=None b._finish()
a=b=None
elif name == "Zope2WSGI":
try:
res = publish_wsgi(a, b)
for r in res:
a['wsgi.output'].write(r)
finally:
a['wsgi.output']._close = 1
a['wsgi.output'].close()
...@@ -19,6 +19,7 @@ ...@@ -19,6 +19,7 @@
receive WebDAV source responses to GET requests. receive WebDAV source responses to GET requests.
</description> </description>
</key> </key>
<key name="use-wsgi" datatype="boolean" default="off" />
</sectiontype> </sectiontype>
<sectiontype name="webdav-source-server" <sectiontype name="webdav-source-server"
...@@ -26,6 +27,7 @@ ...@@ -26,6 +27,7 @@
implements="ZServer.server"> implements="ZServer.server">
<key name="address" datatype="inet-binding-address"/> <key name="address" datatype="inet-binding-address"/>
<key name="force-connection-close" datatype="boolean" default="off"/> <key name="force-connection-close" datatype="boolean" default="off"/>
<key name="use-wsgi" datatype="boolean" default="off" />
</sectiontype> </sectiontype>
<sectiontype name="persistent-cgi" <sectiontype name="persistent-cgi"
......
...@@ -71,6 +71,7 @@ class HTTPServerFactory(ServerFactory): ...@@ -71,6 +71,7 @@ class HTTPServerFactory(ServerFactory):
# webdav-source-server sections won't have webdav_source_clients: # webdav-source-server sections won't have webdav_source_clients:
webdav_clients = getattr(section, "webdav_source_clients", None) webdav_clients = getattr(section, "webdav_source_clients", None)
self.webdav_source_clients = webdav_clients self.webdav_source_clients = webdav_clients
self.use_wsgi = section.use_wsgi
def create(self): def create(self):
from ZServer.AccessLogger import access_logger from ZServer.AccessLogger import access_logger
...@@ -86,7 +87,10 @@ class HTTPServerFactory(ServerFactory): ...@@ -86,7 +87,10 @@ class HTTPServerFactory(ServerFactory):
def createHandler(self): def createHandler(self):
from ZServer import HTTPServer from ZServer import HTTPServer
return HTTPServer.zhttp_handler(self.module, '', self.cgienv) if self.use_wsgi:
return HTTPServer.zwsgi_handler(self.module, '', self.cgienv)
else:
return HTTPServer.zhttp_handler(self.module, '', self.cgienv)
class WebDAVSourceServerFactory(HTTPServerFactory): class WebDAVSourceServerFactory(HTTPServerFactory):
......
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