[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