[z3-checkins] r9402 - in z3/davuseragent: . branch tag trunk
philikon at codespeak.net
philikon at codespeak.net
Tue Feb 22 14:45:01 MET 2005
Author: philikon
Date: Tue Feb 22 14:45:01 2005
New Revision: 9402
Added:
z3/davuseragent/
z3/davuseragent/branch/
z3/davuseragent/tag/
z3/davuseragent/trunk/
z3/davuseragent/trunk/README.txt
z3/davuseragent/trunk/__init__.py
z3/davuseragent/trunk/configure.zcml
z3/davuseragent/trunk/davuseragent-configure.zcml
z3/davuseragent/trunk/dummylock.py
z3/davuseragent/trunk/garbage
z3/davuseragent/trunk/httpget.py
z3/davuseragent/trunk/requestfactory.py
z3/davuseragent/trunk/servertype.py
Log:
Added a package that takes a different approach to handling DAV
in Zope X3.0. It makes simple DAV tasks like editing an object with
filereprsentation adapters workable for the first time.
TODO (by Paul):
- update docs (see XXX)
- improve list of allowed clients and the detection mechanism
Added: z3/davuseragent/trunk/README.txt
==============================================================================
--- (empty file)
+++ z3/davuseragent/trunk/README.txt Tue Feb 22 14:45:01 2005
@@ -0,0 +1,73 @@
+davuseragent
+============
+
+The Problem
+-----------
+
+Zope 3 currently distinguishes different HTTP requests the following
+way:
+
+ * If it's not GET, POST or HEAD, it's a regular HTTP/WebDAV request
+
+ * If it's POST and the content type starts with 'text/xml', it must
+ be XML-RPC
+
+ * Otherwise, it's a browser request
+
+The problem with this approach is that WebDAV clients seeking to edit
+a particular object are treated like a browser client when they do a
+GET. That way, they get to see the fully rendered default view of an
+object instead of the filerepresentation source that they expect. Of
+course, clients are theoretically supposed to do a PROPFIND request
+for the source, but no known client does so.
+
+
+The Solution
+------------
+
+To solve this problem, Zope 2 invented a WebDAV source port, a
+different port to which one could connect WebDAV clients and where a
+GET request always led to the source of objects. This approach is
+impractical because XXX (Paul adds some stuff here) XXX.
+
+With `davuseragent`, we follow a different approach: We decide based
+on the User-Agent header of the incoming request whether we have a
+WebDAV client or not. That allows us to support a definite set of
+clients that are known to work together with Zope 3's DAV support
+(which is, admittedly, far from perfected).
+
+`davuseragent` is not trying to please everyone. It tries to make DAV
+workable for a limited set of well-known and widely-used clients.
+
+
+How it works
+------------
+
+Upon an incoming HTTP request, Zope 3's HTTPServer delegates to a
+request/publication factory. The default one is located in
+`zope.app.publication` and currently decides which request type to
+instantiate based on the rule described in the first section of this
+document.
+
+`davuseragent` simply provides an alternate implementation of a
+request/publication factory. In consequence, it provides a new HTTP
+server type which works like the regular `HTTP` server type, but uses
+the alternate factory.
+
+
+Installation
+------------
+
+In order to enable `davuseragent`, you'll have to install it as a
+regular Zope 3 package in `package-includes` and change your `server`
+section in `zope.conf` to use the new server type, e.g.::
+
+ <server>
+ type DAVUserAgent
+ address 8080
+ </server>
+
+That's it!
+
+Philipp von Weitershausen, philikon at philikon.de
+Paul Everitt, paul at zope-europe.org
Added: z3/davuseragent/trunk/__init__.py
==============================================================================
--- (empty file)
+++ z3/davuseragent/trunk/__init__.py Tue Feb 22 14:45:01 2005
@@ -0,0 +1 @@
+# make this directory a package
Added: z3/davuseragent/trunk/configure.zcml
==============================================================================
--- (empty file)
+++ z3/davuseragent/trunk/configure.zcml Tue Feb 22 14:45:01 2005
@@ -0,0 +1,38 @@
+<configure xmlns="http://namespaces.zope.org/zope">
+
+ <utility
+ name="DAVUserAgent"
+ component="davuseragent.servertype.davuseragent"
+ provides="zope.app.server.servertype.IServerType"
+ />
+
+ <!-- two HTTP/WebDAV views that make DAV half-way usable -->
+
+ <view
+ for="*"
+ name="GET"
+ type="zope.publisher.interfaces.http.IHTTPRequest"
+ factory="davuseragent.httpget.FileGET"
+ permission="zope.Public"
+ allowed_attributes="GET"
+ />
+
+ <view
+ for="*"
+ name="LOCK"
+ type="zope.publisher.interfaces.http.IHTTPRequest"
+ factory="davuseragent.dummylock.DummyLock"
+ permission="zope.Public"
+ allowed_attributes="LOCK"
+ />
+
+ <view
+ for="*"
+ name="UNLOCK"
+ type="zope.publisher.interfaces.http.IHTTPRequest"
+ factory="davuseragent.dummylock.DummyLock"
+ permission="zope.Public"
+ allowed_attributes="UNLOCK"
+ />
+
+</configure>
Added: z3/davuseragent/trunk/davuseragent-configure.zcml
==============================================================================
--- (empty file)
+++ z3/davuseragent/trunk/davuseragent-configure.zcml Tue Feb 22 14:45:01 2005
@@ -0,0 +1 @@
+<include package="davuseragent" />
\ No newline at end of file
Added: z3/davuseragent/trunk/dummylock.py
==============================================================================
--- (empty file)
+++ z3/davuseragent/trunk/dummylock.py Tue Feb 22 14:45:01 2005
@@ -0,0 +1,66 @@
+import time
+from xml.dom import minidom
+
+DAV_NS = "DAV:"
+
+def childElements(node, name=None, ns=None):
+ nodes = []
+ for n in node.childNodes:
+ if (n.nodeType == n.ELEMENT_NODE and
+ ((name is None) or ((n.localName.lower())==name)) and
+ ((ns is None) or (n.namespaceURI==ns))):
+ nodes.append(n)
+ return nodes
+
+class DummyLock(object):
+
+ def __init__(self, context, request):
+ self.context = context
+ self.request = request
+
+ def LOCK(self):
+ # get the lock scope and type from the incoming request
+ data = self.request.bodyFile
+ data.seek(0)
+ request = minidom.parse(data)
+
+ lockinfo = childElements(request, 'lockinfo', ns=DAV_NS)[0]
+ lockscope = childElements(lockinfo, 'lockscope', ns=DAV_NS)[0]
+ locktype = childElements(lockinfo, 'locktype', ns=DAV_NS)[0]
+
+ scope = childElements(lockscope)[0].localName
+ type = childElements(locktype)[0].localName
+
+ # stub values for everything else
+ owner = ''
+ timeout = 'Second-3600'
+ token = 'opaquelocktoken:' + str(time.time())
+ depth = 0
+
+ body = """\
+<?xml version="1.0" encoding="utf-8" ?>
+<prop xmlns="DAV:">
+<lockdiscovery>
+ <activelock>
+ <locktype><%(locktype)s/></locktype>
+ <lockscope><%(lockscope)s/></lockscope>
+ <depth>%(depth)s</depth>
+ <owner>%(owner)s</owner>
+ <timeout>%(timeout)s</timeout>
+ <locktoken><href>%(token)s</href></locktoken>
+ </activelock>
+</lockdiscovery>
+</prop>""" % {
+ 'locktype': type,
+ 'lockscope': scope,
+ 'depth': depth,
+ 'owner': owner,
+ 'timeout': timeout,
+ 'token': token,
+ }
+ self.request.response.setHeader('Lock-Token', token)
+ return body
+
+ def UNLOCK(self):
+ self.request.response.setStatus(204)
+ return ''
Added: z3/davuseragent/trunk/garbage
==============================================================================
--- (empty file)
+++ z3/davuseragent/trunk/garbage Tue Feb 22 14:45:01 2005
@@ -0,0 +1,23 @@
+ # create the basic XML structure
+ response = minidom.Document()
+ prop = response.createElement('prop')
+ prop.setAttribute('xmlns', DAV_NS)
+ response.appendChild(prop)
+ lockdiscovery = response.createElement('lockdiscovery')
+ prop.appendChild(lockdiscovery)
+ activelock = response.createElement('activelock')
+ lockdiscovery.appendChild(activelock)
+
+ # just spit back whatever is requested
+ for node in request.documentElement.childNodes:
+ imported = response.importNode(node, True)
+ activelock.appendChild(imported)
+
+ # give an arbitrary (time.time()) locktoken
+
+ locktoken = response.createElement('locktoken')
+ lockdiscovery.appendChild(locktoken)
+ href = response.createElement('href')
+ locktoken.appendChild(href)
+ locktoken_textnode = response.createTextNode(locktoken_id)
+ href.appendChild(locktoken_textnode)
Added: z3/davuseragent/trunk/httpget.py
==============================================================================
--- (empty file)
+++ z3/davuseragent/trunk/httpget.py Tue Feb 22 14:45:01 2005
@@ -0,0 +1,17 @@
+from zope.app.filerepresentation.interfaces import IReadFile
+
+class FileGET(object):
+ """GET handler for file-like things"""
+
+ def __init__(self, context, request):
+ self.context = context
+ self.request = request
+
+ def GET(self):
+ adapter = IReadFile(self.context, None)
+ if adapter is not None:
+ # TODO: maybe set the content-length header according to
+ # adapter.size()?
+ return adapter.read()
+ #XXX what to do on fallback?
+ return ''
Added: z3/davuseragent/trunk/requestfactory.py
==============================================================================
--- (empty file)
+++ z3/davuseragent/trunk/requestfactory.py Tue Feb 22 14:45:01 2005
@@ -0,0 +1,62 @@
+from zope.publisher.http import HTTPRequest
+from zope.publisher.browser import BrowserRequest
+from zope.publisher.xmlrpc import XMLRPCRequest
+
+from zope.app.publication.httpfactory import HTTPPublicationRequestFactory
+
+_browser_methods = 'GET', 'POST', 'HEAD'
+
+_dav_user_agents = ['cadaver/0.22.2 neon/0.24.7',
+ 'cadaver/0.20.5 neon/0.23.9',
+ 'neon/0.23.9 cadaver/0.20.5']
+
+class DAVUserAgentFactory(HTTPPublicationRequestFactory):
+
+ def __call__(self, input_stream, output_steam, env):
+ """See `zope.app.publication.interfaces.IPublicationRequestFactory`"""
+ method = env.get('REQUEST_METHOD', 'GET').upper()
+
+ # only make a distinction if we're dealing with browser
+ # methods at all
+ if method not in _browser_methods:
+ request = HTTPRequest(input_stream, output_steam, env)
+ request.setPublication(self._http)
+ return request
+
+ content_type = env.get('CONTENT_TYPE', '')
+ is_xml = content_type.startswith('text/xml')
+
+ if (method == 'POST' and is_xml):
+ # soap (enable this on the trunk)
+ #if (env.get('HTTP_SOAPACTION', None)
+ # and self._soapreq is not None):
+ # request = self._soapreq(input_stream, output_steam, env)
+ # request.setPublication(self._soappub)
+ # return request
+ request = XMLRPCRequest(input_stream, output_steam, env)
+ request.setPublication(self._xmlrpc)
+ return request
+
+ # check if we have a distinguished DAV client
+ user_agent = env.get('HTTP_USER_AGENT', '')
+ if user_agent in _dav_user_agents:
+ request = HTTPRequest(input_stream, output_steam, env)
+ request.setPublication(self._http)
+ return request
+
+ # fallback to a regular browser request
+ request = BrowserRequest(input_stream, output_steam, env)
+ request.setPublication(self._brower)
+ return request
+
+# use this on the trunk instead of the last paragraph
+# # fallback to a regular browser request:
+# request = BrowserRequest(input_stream, output_steam, env)
+# request.setPublication(self._brower)
+# # Set the default skin
+# adapters = zapi.getService(zapi.servicenames.Adapters)
+# skin = adapters.lookup((providedBy(request),), IDefaultSkin, '')
+# if skin is not None:
+# directlyProvides(request, directlyProvidedBy(request)+skin)
+# else:
+# directlyProvides(request, IDefaultBrowserLayer)
Added: z3/davuseragent/trunk/servertype.py
==============================================================================
--- (empty file)
+++ z3/davuseragent/trunk/servertype.py Tue Feb 22 14:45:01 2005
@@ -0,0 +1,11 @@
+from zope.server.http.commonaccesslogger import CommonAccessLogger
+from zope.server.http.publisherhttpserver import PMDBHTTPServer
+from zope.server.http.publisherhttpserver import PublisherHTTPServer
+from zope.app.server.servertype import ServerType
+
+from davuseragent.requestfactory import DAVUserAgentFactory
+
+davuseragent = ServerType(PublisherHTTPServer,
+ DAVUserAgentFactory,
+ CommonAccessLogger,
+ 8080, True)
More information about the z3-checkins
mailing list