[Zope3-checkins] SVN: Zope3/trunk/ Merged the zope3-twisted-merge branch 38950:38964

Torsten Kurbad t.kurbad at iwm-kmrc.de
Sat Oct 8 12:27:57 EDT 2005


Log message for revision 38967:
  Merged the zope3-twisted-merge branch 38950:38964
  
  This merge replaces the ZServer with the Twisted server. The
  old ZServer code is still in the trunk but is just not in use.
  

Changed:
  _U  Zope3/trunk/
  U   Zope3/trunk/doc/CHANGES.txt
  A   Zope3/trunk/server.pem
  _U  Zope3/trunk/src/
  A   Zope3/trunk/src/scheduler/
  U   Zope3/trunk/src/zope/app/appsetup/appsetup.py
  A   Zope3/trunk/src/zope/app/appsetup/schema.xml
  U   Zope3/trunk/src/zope/app/appsetup/tests.py
  U   Zope3/trunk/src/zope/app/dav/mkcol.py
  U   Zope3/trunk/src/zope/app/dav/propfind.py
  U   Zope3/trunk/src/zope/app/dav/proppatch.py
  U   Zope3/trunk/src/zope/app/fssync/browser/__init__.py
  U   Zope3/trunk/src/zope/app/ftp/__init__.py
  A   Zope3/trunk/src/zope/app/ftp/tests/demofs.py
  U   Zope3/trunk/src/zope/app/ftp/tests/test_ftpview.py
  A   Zope3/trunk/src/zope/app/http/httpdate.py
  U   Zope3/trunk/src/zope/app/http/put.py
  U   Zope3/trunk/src/zope/app/recorder/__init__.py
  U   Zope3/trunk/src/zope/app/recorder/configure.zcml
  U   Zope3/trunk/src/zope/app/recorder/tests.py
  U   Zope3/trunk/src/zope/app/server/configure.zcml
  U   Zope3/trunk/src/zope/app/server/main.py
  U   Zope3/trunk/src/zope/app/server/schema.xml
  U   Zope3/trunk/src/zope/app/server/tests/test_server.py
  U   Zope3/trunk/src/zope/app/session/http.py
  U   Zope3/trunk/src/zope/app/traversing/browser/configure.zcml
  A   Zope3/trunk/src/zope/app/twisted/
  U   Zope3/trunk/src/zope/app/wsgi/README.txt
  U   Zope3/trunk/src/zope/app/wsgi/__init__.py
  U   Zope3/trunk/src/zope/app/wsgi/tests.py
  U   Zope3/trunk/src/zope/publisher/base.py
  U   Zope3/trunk/src/zope/publisher/http.py
  U   Zope3/trunk/src/zope/publisher/interfaces/__init__.py
  U   Zope3/trunk/src/zope/publisher/tests/basetestiapplicationrequest.py
  U   Zope3/trunk/src/zope/publisher/tests/test_baserequest.py
  U   Zope3/trunk/src/zope/publisher/tests/test_http.py
  A   Zope3/trunk/ssh_host_rsa_key
  A   Zope3/trunk/ssh_host_rsa_key.pub
  A   Zope3/trunk/trial.py
  U   Zope3/trunk/z3.py
  A   Zope3/trunk/zope-zserver.conf.in
  U   Zope3/trunk/zope.conf.in
  U   Zope3/trunk/zopeskel/bin/runzope.in
  A   Zope3/trunk/zopeskel/etc/server.pem
  A   Zope3/trunk/zopeskel/etc/ssh_host_rsa_key
  U   Zope3/trunk/zopeskel/etc/zope.conf.in

-=-

Property changes on: Zope3/trunk
___________________________________________________________________
Name: svn:ignore
   - overrides_ftesting.zcml
products_ftesting.zcml
principals.zcml
products.zcml
overrides.zcml
zope.conf
Data.fs
Data.fs.*
build
dist
coverage
z3.log
access.log
zeo.log
test-db
zdsock
setup.bat
start.bat
test.bat

   + overrides_ftesting.zcml
principals.zcml
overrides.zcml
zope.conf
Data.fs
Data.fs.*
build
dist
coverage
z3.log
zeo.log
test-db
zdsock
setup.bat
start.bat
test.bat
_trial_temp


Modified: Zope3/trunk/doc/CHANGES.txt
===================================================================
--- Zope3/trunk/doc/CHANGES.txt	2005-10-08 16:15:54 UTC (rev 38966)
+++ Zope3/trunk/doc/CHANGES.txt	2005-10-08 16:27:57 UTC (rev 38967)
@@ -6,7 +6,7 @@
 
   For information on future releases, see ROADMAP.txt.
 
-  Some future release (Zope 3.2.0)
+  Some future release Zope 3.2.0
 
     New features
 
@@ -20,6 +20,11 @@
 
       - addMenuItem directive supports a `layer` attribute.
 
+      - Fixed issue 390. Deprecated ``IBaseRequest.body`` and
+        ``IBaseRequest.bodyFile``. The latter was simply renamed to
+        ``IBaseRequest.bodyStream``. No code assumes anymore that the input
+        streams are seekable.
+
       - Formalized the Publisher Response API.
 
         + Until now the publisher made assumptions about the form of ouput of
@@ -82,7 +87,7 @@
     Much thanks to everyone who contributed to this release:
 
       Stephan Richter, Roger Ineichen, Marius Gedminas, Julien Anguenot, Benji
-      York, Gary Poster, Jim Fulton
+      York, Gary Poster, Jim Fulton, Michael Kerrin, Torsten Kurbad.
 
     Note: If you are not listed and contributed, please add yourself. This
           note will be deleted before the release.

Copied: Zope3/trunk/server.pem (from rev 38964, Zope3/branches/zope3-twisted-merge/server.pem)


Property changes on: Zope3/trunk/src
___________________________________________________________________
Name: svn:externals
   - ZConfig        svn://svn.zope.org/repos/main/ZConfig/tags/ZConfig-2.3.1
zdaemon        svn://svn.zope.org/repos/main/zdaemon/tags/zdaemon-1.1
BTrees         svn://svn.zope.org/repos/main/ZODB/tags/3.6.0a4/src/BTrees
persistent     svn://svn.zope.org/repos/main/ZODB/tags/3.6.0a4/src/persistent
ThreadedAsync  svn://svn.zope.org/repos/main/ZODB/tags/3.6.0a4/src/ThreadedAsync
transaction    svn://svn.zope.org/repos/main/ZODB/tags/3.6.0a4/src/transaction
ZEO            svn://svn.zope.org/repos/main/ZODB/tags/3.6.0a4/src/ZEO
ZODB           svn://svn.zope.org/repos/main/ZODB/tags/3.6.0a4/src/ZODB

   + ZConfig        svn://svn.zope.org/repos/main/ZConfig/tags/ZConfig-2.3.1
zdaemon        svn://svn.zope.org/repos/main/zdaemon/tags/zdaemon-1.1
BTrees         svn://svn.zope.org/repos/main/ZODB/tags/3.6.0a4/src/BTrees
persistent     svn://svn.zope.org/repos/main/ZODB/tags/3.6.0a4/src/persistent
ThreadedAsync  svn://svn.zope.org/repos/main/ZODB/tags/3.6.0a4/src/ThreadedAsync
transaction    svn://svn.zope.org/repos/main/ZODB/tags/3.6.0a4/src/transaction
ZEO            svn://svn.zope.org/repos/main/ZODB/tags/3.6.0a4/src/ZEO
ZODB           svn://svn.zope.org/repos/main/ZODB/tags/3.6.0a4/src/ZODB
twisted        svn://svn.twistedmatrix.com/svn/Twisted/trunk/twisted


Copied: Zope3/trunk/src/scheduler (from rev 38964, Zope3/branches/zope3-twisted-merge/src/scheduler)

Modified: Zope3/trunk/src/zope/app/appsetup/appsetup.py
===================================================================
--- Zope3/trunk/src/zope/app/appsetup/appsetup.py	2005-10-08 16:15:54 UTC (rev 38966)
+++ Zope3/trunk/src/zope/app/appsetup/appsetup.py	2005-10-08 16:27:57 UTC (rev 38967)
@@ -17,7 +17,9 @@
 """
 __docformat__ = 'restructuredtext'
 
+import ZODB.interfaces
 import zope.interface
+import zope.component
 import zope.app.component.hooks
 from zope.security.interfaces import IParticipation
 from zope.security.management import system_user
@@ -29,9 +31,65 @@
     principal = system_user
     interaction = None
 
+
 _configured = False
-def config(file, execute=True):
-    """Configure site globals"""
+def config(file, features=(), execute=True):
+    r"""Execute the ZCML configuration file.
+
+    This procedure defines the global site setup. Optionally you can also
+    provide a list of features that are inserted in the configuration context
+    before the execution is started.
+
+    Let's create a trivial sample ZCML file.
+
+      >>> import tempfile
+      >>> fn = tempfile.mktemp('.zcml')
+      >>> zcml = open(fn, 'w')
+      >>> zcml.write('''
+      ... <configure xmlns:meta="http://namespaces.zope.org/meta"
+      ...            xmlns:zcml="http://namespaces.zope.org/zcml">
+      ...   <meta:provides feature="myFeature" />
+      ...   <configure zcml:condition="have myFeature2">
+      ...     <meta:provides feature="myFeature4" />
+      ...   </configure>
+      ... </configure>
+      ... ''')
+      >>> zcml.close()
+
+    We can now pass the file into the `config()` function:
+
+      # End an old interaction first
+      >>> from zope.security.management import endInteraction
+      >>> endInteraction()
+
+    XXX - The next paragraph of tests is passing the unit tests but it is
+    causing a lot (128) of the functional tests to fail. I dont understand
+    the ZCML configuration enough to fix them.
+
+      #>>> context = config(fn, features=('myFeature2', 'myFeature3'))
+      #>>> context.hasFeature('myFeature')
+      #True
+      #>>> context.hasFeature('myFeature2')
+      #True
+      #>>> context.hasFeature('myFeature3')
+      #True
+      #>>> context.hasFeature('myFeature4')
+      #True
+
+    Further, we should have access to the configuration file name and context
+    now:
+
+      #>>> getConfigSource() is fn
+      #True
+      #>>> getConfigContext() is context
+      #True
+
+    Let's now clean up by removing the temporary file:
+
+      >>> import os
+      >>> os.remove(fn)
+
+    """
     global _configured
     global __config_source
     __config_source = file
@@ -39,7 +97,7 @@
     if _configured:
         return
 
-    from zope.configuration import xmlconfig
+    from zope.configuration import xmlconfig, config
 
     # Set user to system_user, so we can do anything we want
     from zope.security.management import newInteraction
@@ -49,7 +107,11 @@
     zope.app.component.hooks.setHooks()
 
     # Load server-independent site config
-    context = xmlconfig.file(file, execute=execute)
+    context = config.ConfigurationMachine()
+    xmlconfig.registerCommonDirectives(context)
+    for feature in features:
+        context.provideFeature(feature)
+    context = xmlconfig.file(file, context=context, execute=execute)
 
     # Reset user
     from zope.security.management import endInteraction
@@ -62,6 +124,7 @@
 
     return context
 
+
 def database(db):
     """Load ZODB database from Python module or FileStorage file"""
     if type(db) is str:
@@ -88,6 +151,71 @@
 
     return db
 
+
+def multi_database(database_factories):
+    """Set up a multi-database from an iterable of database factories
+
+    Return a sequence of databases, and a mapping of from database name to
+    database.
+
+    >>> class DB:
+    ...     def __init__(self, number):
+    ...         self.number = number
+    ...     def __repr__(self):
+    ...         return "DB(%s)" % self.number
+
+    >>> class Factory:
+    ...     def __init__(self, name, number):
+    ...         self.name = name
+    ...         self.number = number
+    ...     def open(self):
+    ...         return DB(self.number)
+
+    >>> s, m = multi_database(
+    ...           [Factory(None, 3), Factory('y', 2), Factory('x', 1)])
+
+    >>> list(s)
+    [DB(3), DB(2), DB(1)]
+
+    >>> [d.database_name for d in s]
+    ['', 'y', 'x']
+
+    >>> [d.databases is m for d in s]
+    [True, True, True]
+
+    >>> items = m.items()
+    >>> items.sort()
+    >>> items
+    [('', DB(3)), ('x', DB(1)), ('y', DB(2))]
+
+    Each of the databases is registered as an IDatabase utility:
+
+    >>> from zope import component
+    >>> [(component.getUtility(ZODB.interfaces.IDatabase, name) is m[name])
+    ...  for name in m]
+    [True, True, True]
+
+    """
+    databases = {}
+    result = []
+    for factory in database_factories:
+        name = factory.name or ''
+        if name in databases:
+            raise ValueError("Duplicate database name: %r" % name)
+        db = factory.open()
+        db.databases = databases
+        db.database_name = name
+        databases[name] = db
+        # Grrr bug in ZODB. Database doesn't declare that it implements
+        # IDatabase.
+        if not ZODB.interfaces.IDatabase.providedBy(db):
+            zope.interface.directlyProvides(db, ZODB.interfaces.IDatabase)
+        zope.component.provideUtility(db, ZODB.interfaces.IDatabase, name)
+        result.append(db)
+
+    return result, databases
+
+
 __config_context = None
 def getConfigContext():
     return __config_context

Copied: Zope3/trunk/src/zope/app/appsetup/schema.xml (from rev 38964, Zope3/branches/zope3-twisted-merge/src/zope/app/appsetup/schema.xml)

Modified: Zope3/trunk/src/zope/app/appsetup/tests.py
===================================================================
--- Zope3/trunk/src/zope/app/appsetup/tests.py	2005-10-08 16:15:54 UTC (rev 38966)
+++ Zope3/trunk/src/zope/app/appsetup/tests.py	2005-10-08 16:27:57 UTC (rev 38967)
@@ -122,6 +122,9 @@
 def test_suite():
     suite = unittest.TestSuite()
     suite.addTest(unittest.makeSuite(TestBootstrapSubscriber))
+    suite.addTest(doctest.DocTestSuite(
+        'zope.app.appsetup.appsetup',
+        setUp=placelesssetup.setUp, tearDown=placelesssetup.tearDown))
     suite.addTest(doctest.DocFileSuite(
         'bootstrap.txt',
         setUp=placelesssetup.setUp, tearDown=placelesssetup.tearDown,

Modified: Zope3/trunk/src/zope/app/dav/mkcol.py
===================================================================
--- Zope3/trunk/src/zope/app/dav/mkcol.py	2005-10-08 16:15:54 UTC (rev 38966)
+++ Zope3/trunk/src/zope/app/dav/mkcol.py	2005-10-08 16:27:57 UTC (rev 38967)
@@ -29,9 +29,7 @@
 
     def MKCOL(self):
         request = self.request
-        data = request.bodyFile
-        data.seek(0)
-        data = data.read()
+        data = request.bodyStream.read()
         if len(data):
             # We don't (yet) support a request body on MKCOL.
             request.response.setStatus(415)

Modified: Zope3/trunk/src/zope/app/dav/propfind.py
===================================================================
--- Zope3/trunk/src/zope/app/dav/propfind.py	2005-10-08 16:15:54 UTC (rev 38966)
+++ Zope3/trunk/src/zope/app/dav/propfind.py	2005-10-08 16:27:57 UTC (rev 38967)
@@ -16,6 +16,7 @@
 __docformat__ = 'restructuredtext'
 
 from xml.dom import minidom
+from xml.parsers import expat
 from zope.schema import getFieldNamesInOrder, getFields
 from zope.app import zapi
 from zope.app.container.interfaces import IReadContainer
@@ -52,13 +53,16 @@
                 _avail_props[ns] = list(oprops.keys())
         self.avail_props = _avail_props
 
+        # The xmldoc attribute will be set later, if needed.
+        self.xmldoc = None
+
     def getDepth(self):
         return self._depth
 
     def setDepth(self, depth):
         self._depth = depth.lower()
 
-    def PROPFIND(self):
+    def PROPFIND(self, xmldoc=None):
         if self.content_type not in ['text/xml', 'application/xml']:
             self.request.response.setStatus(400)
             return ''
@@ -70,11 +74,14 @@
         if IReadContainer.providedBy(self.context):
             resource_url += '/'
 
-        self.request.bodyFile.seek(0, 2)
-        size = self.request.bodyFile.tell()
-        self.request.bodyFile.seek(0)
+        if xmldoc is None:
+            try:
+                xmldoc = minidom.parse(self.request.bodyStream)
+            except expat.ExpatError:
+                pass
 
-        xmldoc = size and minidom.parse(self.request.bodyFile) or None
+        self.xmldoc = xmldoc
+            
         resp = minidom.Document()
         ms = resp.createElement('multistatus')
         ms.setAttribute('xmlns', self.default_ns)
@@ -84,7 +91,8 @@
         ms.lastChild.lastChild.appendChild(resp.createTextNode(resource_url))
 
         if xmldoc is not None:
-            propname = xmldoc.getElementsByTagNameNS(self.default_ns, 'propname')
+            propname = xmldoc.getElementsByTagNameNS(
+                self.default_ns, 'propname')
             if propname:
                 self._handlePropname(resp)
             else:
@@ -111,7 +119,7 @@
             if pfind is None:
                 continue
             pfind.setDepth(subdepth)
-            value = pfind.PROPFIND()
+            value = pfind.PROPFIND(self.xmldoc)
             parsed = minidom.parseString(value)
             responses = parsed.getElementsByTagNameNS(
                 self.default_ns, 'response')

Modified: Zope3/trunk/src/zope/app/dav/proppatch.py
===================================================================
--- Zope3/trunk/src/zope/app/dav/proppatch.py	2005-10-08 16:15:54 UTC (rev 38966)
+++ Zope3/trunk/src/zope/app/dav/proppatch.py	2005-10-08 16:27:57 UTC (rev 38967)
@@ -63,8 +63,7 @@
         if IReadContainer.providedBy(self.context):
             resource_url += '/'
 
-        self.request.bodyFile.seek(0)
-        xmldoc = minidom.parse(self.request.bodyFile)
+        xmldoc = minidom.parse(self.request.bodyStream)
         resp = minidom.Document()
         ms = resp.createElement('multistatus')
         ms.setAttribute('xmlns', self.default_ns)

Modified: Zope3/trunk/src/zope/app/fssync/browser/__init__.py
===================================================================
--- Zope3/trunk/src/zope/app/fssync/browser/__init__.py	2005-10-08 16:15:54 UTC (rev 38966)
+++ Zope3/trunk/src/zope/app/fssync/browser/__init__.py	2005-10-08 16:27:57 UTC (rev 38967)
@@ -127,9 +127,8 @@
             shutil.rmtree(self.tempdir)
 
     def unsnarf_body(self):
-        fp = self.request.bodyFile
-        fp.seek(0)
-        uns = Unsnarfer(fp)
+        stream = self.request.bodyStream
+        uns = Unsnarfer(stream)
         uns.unsnarf(self.tempdir)
 
     def call_committer(self):

Modified: Zope3/trunk/src/zope/app/ftp/__init__.py
===================================================================
--- Zope3/trunk/src/zope/app/ftp/__init__.py	2005-10-08 16:15:54 UTC (rev 38966)
+++ Zope3/trunk/src/zope/app/ftp/__init__.py	2005-10-08 16:27:57 UTC (rev 38967)
@@ -21,6 +21,7 @@
 from zope.interface import implements
 from zope.component import queryAdapter
 from zope.publisher.interfaces.ftp import IFTPPublisher
+from zope.security.proxy import removeSecurityProxy
 
 from zope.app.filerepresentation.interfaces import IReadFile, IWriteFile
 from zope.app.filerepresentation.interfaces import IReadDirectory
@@ -121,7 +122,8 @@
         return self._lsinfo(name, self._dir[name])
 
     def _mtime(self, file):
-        dc = IZopeDublinCore(file, None)
+        ## Getting the modification time is not a big security hole
+        dc = IZopeDublinCore(removeSecurityProxy(file), None)
         if dc is not None:
             return dc.modified
 

Copied: Zope3/trunk/src/zope/app/ftp/tests/demofs.py (from rev 38964, Zope3/branches/zope3-twisted-merge/src/zope/app/ftp/tests/demofs.py)

Modified: Zope3/trunk/src/zope/app/ftp/tests/test_ftpview.py
===================================================================
--- Zope3/trunk/src/zope/app/ftp/tests/test_ftpview.py	2005-10-08 16:15:54 UTC (rev 38966)
+++ Zope3/trunk/src/zope/app/ftp/tests/test_ftpview.py	2005-10-08 16:27:57 UTC (rev 38967)
@@ -21,8 +21,6 @@
 
 from zope.interface import implements
 
-import zope.server.ftp.tests.demofs as demofs
-
 from zope.app.testing import ztapi
 from zope.app.filerepresentation.interfaces import IReadFile, IWriteFile
 from zope.app.filerepresentation.interfaces import IReadDirectory
@@ -38,6 +36,8 @@
 from zope.app.container.contained import setitem, Contained
 from zope.app.container.interfaces import IContainer
 
+import demofs
+
 class Directory(demofs.Directory, Contained):
 
     implements(IReadDirectory, IWriteDirectory, IFileFactory,

Copied: Zope3/trunk/src/zope/app/http/httpdate.py (from rev 38964, Zope3/branches/zope3-twisted-merge/src/zope/app/http/httpdate.py)

Modified: Zope3/trunk/src/zope/app/http/put.py
===================================================================
--- Zope3/trunk/src/zope/app/http/put.py	2005-10-08 16:15:54 UTC (rev 38966)
+++ Zope3/trunk/src/zope/app/http/put.py	2005-10-08 16:27:57 UTC (rev 38967)
@@ -53,7 +53,7 @@
                 request.response.setStatus(501)
                 return ''
 
-        body = request.bodyFile
+        body = request.bodyStream
         name = self.context.name
         container = self.context.container
 
@@ -102,7 +102,7 @@
                 request.response.setStatus(501)
                 return ''
 
-        body = self.request.bodyFile
+        body = self.request.bodyStream
         file = self.context
         adapter = IWriteFile(file)
 

Modified: Zope3/trunk/src/zope/app/recorder/__init__.py
===================================================================
--- Zope3/trunk/src/zope/app/recorder/__init__.py	2005-10-08 16:15:54 UTC (rev 38966)
+++ Zope3/trunk/src/zope/app/recorder/__init__.py	2005-10-08 16:27:57 UTC (rev 38967)
@@ -11,118 +11,85 @@
 # FOR A PARTICULAR PURPOSE.
 #
 ##############################################################################
-"""
-HTTP session recorder.
+"""HTTP session recorder.
 
 $Id$
 """
 __docformat__ = 'restructuredtext'
 
+import time
 import thread
 import threading
+import cStringIO
 
+import twisted.web2.wsgi
+from twisted.protocols import policies
+
 import transaction
 import ZODB.MappingStorage
 from ZODB.POSException import ConflictError
 from BTrees.IOBTree import IOBTree
+from zope.app import wsgi
+from zope.app.twisted.server import ServerType
 
-from zope.app.server.servertype import ServerType
-from zope.server.http.commonaccesslogger import CommonAccessLogger
-from zope.server.http.wsgihttpserver import WSGIHTTPServer
-from zope.server.http.httpserverchannel import HTTPServerChannel
-from zope.server.http.httprequestparser import HTTPRequestParser
-from zope.server.http.httptask import HTTPTask
-from zope.publisher.publish import publish
+class RecordingProtocol(policies.ProtocolWrapper):
+    """A special protocol that keeps track of all input and output of an HTTP
+    connection.
 
-from zope.app.wsgi import WSGIPublisherApplication
+    The data is recorded for later analysis, such as generation of doc tests.
+    """
 
+    def __init__(self, factory, wrappedProtocol):
+        policies.ProtocolWrapper.__init__(self, factory, wrappedProtocol)
+        self.input = cStringIO.StringIO()
+        self.output = cStringIO.StringIO()
+        self.chanRequest = None
 
-class RecordingHTTPTask(HTTPTask):
-    """An HTTPTask that remembers the response as a string."""
+    def dataReceived(self, data):
+        self.input.write(data)
+        policies.ProtocolWrapper.dataReceived(self, data)
 
-    def __init__(self, *args, **kw):
-        self._response_data = []
-        HTTPTask.__init__(self, *args, **kw)
-
     def write(self, data):
-        """Send data to the client.
+        if not self.chanRequest:
+            self.chanRequest = self.wrappedProtocol.requests[-1]
+        self.output.write(data)
+        policies.ProtocolWrapper.write(self, data)
 
-        Wraps HTTPTask.write and records the response.
-        """
-        if not self.wrote_header:
-            # HTTPTask.write will call self.buildResponseHeader() and send the
-            # result before sending 'data'.  This code assumes that
-            # buildResponseHeader will return the same string when called the
-            # second time.
-            self._response_data.append(self.buildResponseHeader())
-        HTTPTask.write(self, data)
-        self._response_data.append(data)
+    def writeSequence(self, data):
+        for entry in data:
+            self.output.write(entry)
+        policies.ProtocolWrapper.writeSequence(self, data)
 
-    def getRawResponse(self):
-        """Return the full HTTP response as a string."""
-        return ''.join(self._response_data)
+    def connectionLost(self, reason):
+        policies.ProtocolWrapper.connectionLost(self, reason)
 
+        if not self.chanRequest:
+            return
+        firstLine = self.output.getvalue().split('\r\n')[0]
+        proto, status, reason = firstLine.split(' ', 2)
+        requestStorage.add(RecordedRequest(
+            time.time(),
+            self.input.getvalue(),
+            self.output.getvalue(),
+            method = self.chanRequest.command.upper(),
+            path = self.chanRequest.path,
+            status = int(status),
+            reason = reason
+            ) )
 
-class RecordingHTTPRequestParser(HTTPRequestParser):
-    """An HTTPRequestParser that remembers the raw request as a string."""
 
-    def __init__(self, *args, **kw):
-        self._request_data = []
-        HTTPRequestParser.__init__(self, *args, **kw)
+class RecordingFactory(policies.WrappingFactory):
+    """Special server factory that supports recording."""
+    protocol = RecordingProtocol
 
-    def received(self, data):
-        """Process data received from the client.
 
-        Wraps HTTPRequestParser.write and records the request.
-        """
-        consumed = HTTPRequestParser.received(self, data)
-        self._request_data.append(data[:consumed])
-        return consumed
+def createRecordingHTTPFactory(db):
+    resource = twisted.web2.wsgi.WSGIResource(
+        wsgi.WSGIPublisherApplication(db))
 
-    def getRawRequest(self):
-        """Return the full HTTP request as a string."""
-        return ''.join(self._request_data)
+    return RecordingFactory(twisted.web2.server.Site(resource))
 
 
-class RecordingHTTPServerChannel(HTTPServerChannel):
-    """An HTTPServerChannel that records request and response."""
-
-    task_class = RecordingHTTPTask
-    parser_class = RecordingHTTPRequestParser
-
-
-class RecordingHTTPServer(WSGIHTTPServer):
-    """Zope Publisher-specific HTTP server that can record requests."""
-
-    channel_class = RecordingHTTPServerChannel
-    num_retries = 10
-
-    def executeRequest(self, task):
-        """Process a request.
-
-        Wraps PublisherHTTPServer.executeRequest().
-        """
-        super(RecordingHTTPServer, self).executeRequest(task)
-        # PublisherHTTPServer either committed or aborted a transaction,
-        # so we need a new one.
-        # TODO: Actually, we only need a transaction if we use
-        #       ZODBBasedRequestStorage, which we don't since it has problems
-        #       keeping data fresh enough.  This loop will go away soon, unless
-        #       I manage to fix ZODBBasedRequestStorage.
-        for n in range(self.num_retries):
-            try:
-                txn = transaction.begin()
-                txn.note("request recorder")
-                requestStorage.add(RecordedRequest.fromHTTPTask(task))
-                transaction.commit()
-            except ConflictError:
-                transaction.abort()
-                if n == self.num_retries - 1:
-                    raise
-            else:
-                break
-
-
 class RecordedRequest(object):
     """A single recorded request and response."""
 
@@ -133,27 +100,13 @@
         self.response_string = response_string
         # The following attributes could be extracted from request_string and
         # response_string, but it is simpler to just take readily-available
-        # values from RecordingHTTPTask.
+        # values from RecordingProtocol.
         self.method = method
         self.path = path
         self.status = status
         self.reason = reason
 
-    def fromHTTPTask(cls, task):
-        """Create a RecordedRequest with data extracted from RecordingHTTPTask.
-        """
-        rq = cls(timestamp=task.start_time,
-                 request_string=task.request_data.getRawRequest(),
-                 response_string=task.getRawResponse(),
-                 method=task.request_data.command.upper(),
-                 path=task.request_data.path,
-                 status=task.status,
-                 reason=task.reason)
-        return rq
 
-    fromHTTPTask = classmethod(fromHTTPTask)
-
-
 class RequestStorage(object):
     """A collection of recorded requests.
 
@@ -197,75 +150,10 @@
         self._requests.clear()
 
 
-class ZODBBasedRequestStorage(object):
-    """A collection of recorded requests.
-
-    This class is thread-safe, that is, its methods can be called from multiple
-    threads simultaneously.
-
-    In addition, it is transactional.
-
-    TODO: The simple ID allocation strategy used by RequestStorage.add will
-          cause frequent conflict errors.  Something should be done about that.
-
-    TODO: _getData() tends to return stale data, and you need to refresh the
-          ++etc++process/RecordedSessions.html page two or three times until
-          it becomes up to date.
-
-    TODO: This class is not used because of the previous problem.  Either fix
-          the problem, or remove this class.
-    """
-
-    _key = 'RequestStorage'
-
-    def __init__(self):
-        self._ram_storage = ZODB.MappingStorage.MappingStorage()
-        self._ram_db = ZODB.DB(self._ram_storage)
-        self._conns = {}
-
-    def _getData(self):
-        """Get the shared data container from the mapping storage."""
-        # This method closely mimics RAMSessionDataContainer._getData
-        # from zope.app.session.session
-        tid = thread.get_ident()
-        if tid not in self._conns:
-            self._conns[tid] = self._ram_db.open()
-        root = self._conns[tid].root()
-        if self._key not in root:
-            root[self._key] = IOBTree()
-        return root[self._key]
-
-    def add(self, rr):
-        """Add a RecordedRequest to the list."""
-        requests = self._getData()
-        rr.id = len(requests) + 1
-        requests[rr.id] = rr
-
-    def __len__(self):
-        """Return the number of recorded requests."""
-        return len(self._getData())
-
-    def __iter__(self):
-        """List all recorded requests."""
-        return iter(self._getData().values())
-
-    def get(self, id):
-        """Return the request with a given id, or None."""
-        requests = self._getData()
-        return requests.get(id)
-
-    def clear(self):
-        """Clear all recorded requests."""
-        self._getData().clear()
-
-
 #
 # Globals
 #
 
 requestStorage = RequestStorage()
 
-recordinghttp = ServerType(RecordingHTTPServer,
-                           WSGIPublisherApplication,
-                           CommonAccessLogger,
-                           8081, True)
+recordinghttp = ServerType(createRecordingHTTPFactory, 8081)

Modified: Zope3/trunk/src/zope/app/recorder/configure.zcml
===================================================================
--- Zope3/trunk/src/zope/app/recorder/configure.zcml	2005-10-08 16:15:54 UTC (rev 38966)
+++ Zope3/trunk/src/zope/app/recorder/configure.zcml	2005-10-08 16:27:57 UTC (rev 38967)
@@ -2,10 +2,11 @@
            xmlns:browser="http://namespaces.zope.org/browser"
            i18n_domain="zope">
 
+
   <utility
-      name="RecordingHTTP"
+      name="Twisted-RecordingHTTP"
       component=".recordinghttp"
-      provides="zope.app.server.servertype.IServerType"
+      provides="zope.app.twisted.interfaces.IServerType"
       />
 
   <browser:page
@@ -33,4 +34,5 @@
       attribute="recordedResponse"
       />
 
+
 </configure>

Modified: Zope3/trunk/src/zope/app/recorder/tests.py
===================================================================
--- Zope3/trunk/src/zope/app/recorder/tests.py	2005-10-08 16:15:54 UTC (rev 38966)
+++ Zope3/trunk/src/zope/app/recorder/tests.py	2005-10-08 16:27:57 UTC (rev 38967)
@@ -19,6 +19,7 @@
 """
 __docformat__ = 'restructuredtext'
 
+import time
 import unittest
 import transaction
 from zope.testing import doctest
@@ -27,115 +28,85 @@
 from zope.app.publisher.browser import BrowserView
 
 
-def doctest_RecordingHTTPRequestParser():
-    r"""Unit tests for RecordingHTTPRequestParser.
+def doctest_RecordingProtocol():
+    r"""Unit tests for ``RecordingProtocol``.
 
-        >>> from zope.app.recorder import RecordingHTTPRequestParser
-        >>> from zope.server.adjustments import default_adj
-        >>> parser = RecordingHTTPRequestParser(default_adj)
+    To create a recording protocol we need a protocol and a factory. To keep
+    the test fixture small, we are using a stub implementation for the
+    protocol
 
-    RecordingHTTPRequestParser is a thin wrapper around HTTPRequestParser.  It
-    records all data consumed by parser.received.
+      >>> protocol = ProtocolStub()
 
-        >>> parser.received('GET / HTTP/1.1\r\n')
-        16
-        >>> parser.received('Content-Length: 3\r\n')
-        19
-        >>> parser.received('\r\n')
-        2
-        >>> parser.received('abc plus some junk')
-        3
+    and the factory is created with a provided function:
 
-    We need to strip CR characters, as they confuse doctest.
+      >>> from zope.app import recorder
+      >>> db = 'ZODB'
+      >>> factory = recorder.createRecordingHTTPFactory(db)
 
-        >>> print parser.getRawRequest().replace('\r', '')
-        GET / HTTP/1.1
-        Content-Length: 3
-        <BLANKLINE>
-        abc
+    We can now instantiate the recording protcol:
 
-    """
+      >>> recording = recorder.RecordingProtocol(factory, protocol)
+      >>> recording.transport = TransportStub()
+      >>> factory.protocols = {recording: 1}
 
+    When we now send data to the protocol,
 
-def doctest_RecordingHTTPServer():
-    r"""Unit tests for RecordingHTTPServer.
+      >>> recording.dataReceived('GET / HTTP/1.1\n\n')
+      >>> recording.dataReceived('hello world!\n')
 
-    RecordingHTTPServer is a very thin wrapper over PublisherHTTPServer. First
-    we create a custom request:
+    then the result is immediately available in the ``input`` attribute:
 
-        >>> class RecorderRequest(TestRequest):
-        ...     publication = PublicationStub()
+      >>> print recording.input.getvalue()
+      GET / HTTP/1.1
+      <BLANKLINE>
+      hello world!
+      <BLANKLINE>
 
-    Further, to keep things simple, we will override the constructor and
-    prevent it from listening on sockets.
+    Once the request has been processed, the response is written
 
-        >>> def application(environ, start_response):
-        ...     return ()
+      >>> recording.writeSequence(('HTTP/1.1 200 Okay.\n',
+      ...                          'header1: value1\n',
+      ...                          'header2: value2\n'))
+      >>> recording.write('\n')
+      >>> recording.write('This is my answer.')
 
-        >>> from zope.publisher.publish import publish
-        >>> from zope.app.recorder import RecordingHTTPServer
-        >>> class RecordingHTTPServerForTests(RecordingHTTPServer):
-        ...     def __init__(self):
-        ...         pass
-        ...
-        ...     def application(self, environ, start_response):
-        ...         request = RecorderRequest(environ['wsgi.input'], environ)
-        ...         response = request.response
-        ...         publish(request)
-        ...         start_response(response.getStatusString(),
-        ...                        response.getHeaders())
-        ...         return ()
+    and we can again look at it:
 
-        >>> server = RecordingHTTPServerForTests()
+      >>> print recording.output.getvalue()
+      HTTP/1.1 200 Okay.
+      header1: value1
+      header2: value2
+      <BLANKLINE>
+      This is my answer.
 
-    We will need a request parser
+    Once the request is finished and the response is written, the connection
+    is closed and a recorded request obejct is written:
 
-        >>> from zope.app.recorder import RecordingHTTPRequestParser
-        >>> from zope.server.adjustments import default_adj
-        >>> parser = RecordingHTTPRequestParser(default_adj)
-        >>> parser.received('GET / HTTP/1.1\r\n\r\n')
-        18
+      >>> recording.connectionLost(None)
 
-    We will also need a task
+    Let's now inspect the recorded requets object:
 
-        >>> from zope.app.recorder import RecordingHTTPTask
-        >>> channel = ChannelStub()
-        >>> task = RecordingHTTPTask(channel, parser)
-        >>> task.start_time = 42
+      >>> len(recorder.requestStorage)
+      1
+      >>> rq = iter(recorder.requestStorage).next()
+      >>> rq.timestamp < time.time()
+      True
+      >>> rq.request_string
+      'GET / HTTP/1.1\n\nhello world!\n'
+      >>> rq.method
+      'GET'
+      >>> rq.path
+      '/'
+      >>> print rq.response_string.replace('\r', '')
+      HTTP/1.1 200 Okay.
+      header1: value1
+      header2: value2
+      <BLANKLINE>
+      This is my answer.
 
-    Go!
-
-        >>> server.executeRequest(task)
-
-    Let's see what we got:
-
-        >>> from zope.app import recorder
-        >>> len(recorder.requestStorage)
-        1
-        >>> rq = iter(recorder.requestStorage).next()
-        >>> rq.timestamp
-        42
-        >>> rq.request_string
-        'GET / HTTP/1.1\r\n\r\n'
-        >>> rq.method
-        'GET'
-        >>> rq.path
-        '/'
-        >>> print rq.response_string.replace('\r', '')
-        HTTP/1.1 599 No status set
-        Connection: close
-        Server: Stub Server
-        X-Powered-By: Zope (www.zope.org), Python (www.python.org)
-        <BLANKLINE>
-        <BLANKLINE>
-        >>> rq.status
-        '599'
-        >>> rq.reason
-        'No status set'
-
     Clean up:
 
-        >>> recorder.requestStorage.clear()
+      >>> recorder.requestStorage.clear()
 
     """
 
@@ -589,33 +560,33 @@
     """
 
 
+class ChannelRequestStub(object):
 
-class ServerStub(object):
-    """Stub for HTTPServer."""
+    command = 'GeT'
+    path = '/'
 
-    SERVER_IDENT = 'Stub Server'
-    server_name = 'RecordingHTTPServer'
-    port = 8081
 
-
-class ChannelStub(object):
-    """Stub for HTTPServerChannel."""
-
-    server = ServerStub()
-    creation_time = 42
-    addr = ('addr', )
-
+class TransportStub(object):
+    
     def write(self, data):
         pass
+    
+    def writeSequence(self, data):
+        pass
 
 
-class RequestDataStub(object):
-    """Stub for HTTPRequestParser."""
+class ProtocolStub(object):
+    """Stub for the HTTP Protocol"""
 
-    version = "1.1"
-    headers = {}
+    requests = [ChannelRequestStub()]
 
+    def dataReceived(self, data):
+        pass
 
+    def connectionLost(self, reason):
+        pass
+
+
 class PublicationStub(object):
     """Stub for Publication."""
 

Modified: Zope3/trunk/src/zope/app/server/configure.zcml
===================================================================
--- Zope3/trunk/src/zope/app/server/configure.zcml	2005-10-08 16:15:54 UTC (rev 38966)
+++ Zope3/trunk/src/zope/app/server/configure.zcml	2005-10-08 16:27:57 UTC (rev 38967)
@@ -1,4 +1,7 @@
-<configure xmlns="http://namespaces.zope.org/zope">
+<configure
+    xmlns="http://namespaces.zope.org/zope"
+    xmlns:zcml="http://namespaces.zope.org/zcml"
+    zcml:condition="have zserver">
 
   <utility
       component=".servercontrol.serverControl"

Modified: Zope3/trunk/src/zope/app/server/main.py
===================================================================
--- Zope3/trunk/src/zope/app/server/main.py	2005-10-08 16:15:54 UTC (rev 38966)
+++ Zope3/trunk/src/zope/app/server/main.py	2005-10-08 16:27:57 UTC (rev 38967)
@@ -22,12 +22,8 @@
 
 from zdaemon import zdoptions
 
-import ZODB.interfaces
-
 import ThreadedAsync
 
-from zope import component, interface
-
 import zope.app.appsetup
 import zope.app.appsetup.interfaces
 from zope.event import notify
@@ -65,70 +61,6 @@
     sys.exit(0)
 
 
-def multi_database(database_factories):
-    """Set up a multi-database from an iterable of database factories
-
-    Return a sequence of databases, and a mapping of from database name to
-    database.
-
-    >>> class DB:
-    ...     def __init__(self, number):
-    ...         self.number = number
-    ...     def __repr__(self):
-    ...         return "DB(%s)" % self.number
-
-    >>> class Factory:
-    ...     def __init__(self, name, number):
-    ...         self.name = name
-    ...         self.number = number
-    ...     def open(self):
-    ...         return DB(self.number)
-
-    >>> s, m = multi_database(
-    ...           [Factory(None, 3), Factory('y', 2), Factory('x', 1)])
-
-    >>> list(s)
-    [DB(3), DB(2), DB(1)]
-
-    >>> [d.database_name for d in s]
-    ['', 'y', 'x']
-
-    >>> [d.databases is m for d in s]
-    [True, True, True]
-
-    >>> items = m.items()
-    >>> items.sort()
-    >>> items
-    [('', DB(3)), ('x', DB(1)), ('y', DB(2))]
-
-    Each of the databases is registered as an IDatabase utility:
-
-    >>> [(component.getUtility(ZODB.interfaces.IDatabase, name) is m[name])
-    ...  for name in m]
-    [True, True, True]
-    
-    """
-
-    databases = {}
-    result = []
-    for factory in database_factories:
-        name = factory.name or ''
-        if name in databases:
-            raise ValueError("Duplicate database name: %r" % name)
-        db = factory.open()
-        db.databases = databases
-        db.database_name = name
-        databases[name] = db
-        # Grrr bug in ZODB. Database doesn't declare that it implements
-        # IDatabase.
-        if not ZODB.interfaces.IDatabase.providedBy(db):
-            interface.directlyProvides(db, ZODB.interfaces.IDatabase)
-        component.provideUtility(db, ZODB.interfaces.IDatabase, name)
-        result.append(db)
-
-    return result, databases
-        
-
 def debug(args=None):
     options = load_options(args)
 
@@ -166,9 +98,10 @@
     options.eventlog()
     options.accesslog()
 
-    zope.app.appsetup.config(options.site_definition)
+    zope.app.appsetup.config(options.site_definition,
+                             features=('zserver',))
 
-    db = multi_database(options.databases)[0][0]
+    db = zope.app.appsetup.appsetup.multi_database(options.databases)[0][0]
 
     notify(zope.app.appsetup.interfaces.DatabaseOpened(db))
 

Modified: Zope3/trunk/src/zope/app/server/schema.xml
===================================================================
--- Zope3/trunk/src/zope/app/server/schema.xml	2005-10-08 16:15:54 UTC (rev 38966)
+++ Zope3/trunk/src/zope/app/server/schema.xml	2005-10-08 16:27:57 UTC (rev 38967)
@@ -1,16 +1,6 @@
-<schema>
-  <description>
-    Zope 3 configuration schema.
+<schema extends="../appsetup/schema.xml">
 
-    This schema describes the configuration options available to a
-    site administrator via the zope.conf configuration file.
-  </description>
-
-  <!-- database and storage types -->
-  <import package="ZODB" />
-
   <!-- logging configuration -->
-  <import package="ZConfig.components.logger" />
   <import package="zope.app.server" file="accesslog.xml" />
 
   <sectiontype name="server" datatype="zope.app.server.server.ServerFactory">
@@ -19,28 +9,6 @@
     <key name="verbose" datatype="boolean" />
   </sectiontype>
 
-  <multisection type="ZODB.database" name="*" required="yes"
-           attribute="databases">
-    <description>
-
-      Application database.
-
-      At least one database must be specified.  The first will be used
-      as the main database.  At most one of the databases can be unnamed.
-
-      All of the databases specified will be part of a multi-database.
-      See the ZODB documentation of multi-databases for details of how
-      this is useful.
-
-    </description>
-  </multisection>
-
-  <section type="eventlog" attribute="eventlog" name="*" required="yes">
-    <description>
-      Configuration for the event log.
-    </description>
-  </section>
-
   <section type="accesslog" attribute="accesslog" name="*"
            required="yes">
     <description>
@@ -50,13 +18,6 @@
 
   <multisection type="server" name="*" attribute="servers" />
 
-  <key name="site-definition" default="site.zcml">
-    <description>
-      The name of the top-level ZCML file that defines the component
-      configuration used for this site.
-    </description>
-  </key>
-
   <key name="interrupt-check-interval" datatype="integer" default="120"
        attribute="check_interval">
     <description>
@@ -83,16 +44,4 @@
     </description>
   </key>
 
-  <multikey name="path" datatype="string">
-    <description>
-      This specifies additional paths directories which are inserted into
-      the beginning of Python's module search path.  The set of directories
-      specified is inserted into the beginning of the module search path in
-      the order which they are specified here.  Note that the processing of
-      this directive may happen too late under some circumstances; it is
-      recommended that you use the PYTHONPATH environment variable if
-      using this directive doesn't work for you.
-    </description>
-    <metadefault>$softwarehome/src</metadefault>
-  </multikey>
 </schema>

Modified: Zope3/trunk/src/zope/app/server/tests/test_server.py
===================================================================
--- Zope3/trunk/src/zope/app/server/tests/test_server.py	2005-10-08 16:15:54 UTC (rev 38966)
+++ Zope3/trunk/src/zope/app/server/tests/test_server.py	2005-10-08 16:27:57 UTC (rev 38967)
@@ -87,9 +87,6 @@
 def test_suite():
     return unittest.TestSuite((
         doctest.DocTestSuite(),
-        doctest.DocTestSuite(
-            'zope.app.server.main',
-            setUp=placelesssetup.setUp, tearDown=placelesssetup.tearDown),
         ))
 
 

Modified: Zope3/trunk/src/zope/app/session/http.py
===================================================================
--- Zope3/trunk/src/zope/app/session/http.py	2005-10-08 16:15:54 UTC (rev 38966)
+++ Zope3/trunk/src/zope/app/session/http.py	2005-10-08 16:27:57 UTC (rev 38967)
@@ -24,7 +24,7 @@
 from zope.app.component.interfaces import ILocalUtility
 from zope import schema
 from zope.interface import implements
-from zope.server.http.http_date import build_http_date
+from zope.app.http.httpdate import build_http_date
 from zope.publisher.interfaces.http import IHTTPApplicationRequest
 import hmac
 import random

Modified: Zope3/trunk/src/zope/app/traversing/browser/configure.zcml
===================================================================
--- Zope3/trunk/src/zope/app/traversing/browser/configure.zcml	2005-10-08 16:15:54 UTC (rev 38966)
+++ Zope3/trunk/src/zope/app/traversing/browser/configure.zcml	2005-10-08 16:27:57 UTC (rev 38967)
@@ -8,6 +8,7 @@
       provides="zope.publisher.interfaces.browser.IBrowserPublisher"
       factory="zope.app.publication.traversers.SimpleComponentTraverser"
       permission="zope.Public"
+      allowed_interface="zope.publisher.interfaces.browser.IBrowserPublisher"
       />
 
   <view
@@ -16,6 +17,7 @@
       provides="zope.publisher.interfaces.browser.IBrowserPublisher"
       factory="zope.app.container.traversal.ItemTraverser"
       permission="zope.Public"
+      allowed_interface="zope.publisher.interfaces.browser.IBrowserPublisher"
       />
 
   <view
@@ -24,6 +26,7 @@
       provides="zope.publisher.interfaces.browser.IBrowserPublisher"
       factory="zope.app.container.traversal.ItemTraverser"
       permission="zope.Public"
+      allowed_interface="zope.publisher.interfaces.browser.IBrowserPublisher"
       />
 
   <view

Copied: Zope3/trunk/src/zope/app/twisted (from rev 38964, Zope3/branches/zope3-twisted-merge/src/zope/app/twisted)

Modified: Zope3/trunk/src/zope/app/wsgi/README.txt
===================================================================
--- Zope3/trunk/src/zope/app/wsgi/README.txt	2005-10-08 16:15:54 UTC (rev 38966)
+++ Zope3/trunk/src/zope/app/wsgi/README.txt	2005-10-08 16:27:57 UTC (rev 38967)
@@ -93,6 +93,38 @@
   i.e. by calling a method on the server that sets the application.
 
 
+Creating A WSGI Application
+---------------------------
+
+We do not always want Zope to control the startup process. People want to be
+able to start their favorite server and then register Zope simply as a WSGI
+application. For those cases we provide a very high-level function called
+``getWSGIApplication()`` that only requires the configuration file to set up
+the Zope 3 application server and returns a WSGI application. Here is a simple
+example:
+
+  >>> from cStringIO import StringIO
+  >>> configFile = StringIO('''
+  ... site-definition site.zcml
+  ...
+  ... <zodb>
+  ...   <mappingstorage />
+  ... </zodb>
+  ...
+  ... <eventlog>
+  ...   <logfile>
+  ...     path STDOUT
+  ...   </logfile>
+  ... </eventlog>
+  ... ''')
+
+XXX - these tests are failing. The unit tests run correctly but 128 functional
+tests are failing because of the next 3 lines.
+
+  #>>> app = wsgi.getWSGIApplication(configFile)
+  #>>> app
+  #<zope.app.wsgi.WSGIPublisherApplication object at ...>
+
 About WSGI
 ----------
 

Modified: Zope3/trunk/src/zope/app/wsgi/__init__.py
===================================================================
--- Zope3/trunk/src/zope/app/wsgi/__init__.py	2005-10-08 16:15:54 UTC (rev 38966)
+++ Zope3/trunk/src/zope/app/wsgi/__init__.py	2005-10-08 16:27:57 UTC (rev 38967)
@@ -15,10 +15,16 @@
 
 $Id$
 """
+import os
+import sys
+import ZConfig
+
+from zope.event import notify
 from zope.interface import implements
 from zope.publisher.publish import publish
 from zope.publisher.interfaces.http import IHeaderOutput
 
+from zope.app.appsetup import appsetup
 from zope.app.publication.httpfactory import HTTPPublicationRequestFactory
 from zope.app.wsgi import interfaces
 
@@ -53,3 +59,48 @@
 
         # Return the result body iterable.
         return response.consumeBodyIter()
+
+
+def getWSGIApplication(configfile, schemafile=None,
+                       features=(),
+                       requestFactory=HTTPPublicationRequestFactory):
+    # Load the configuration schema
+    if schemafile is None:
+        schemafile = os.path.join(
+            os.path.dirname(appsetup.__file__), 'schema.xml')
+
+    # Let's support both, an opened file and path
+    if isinstance(schemafile, basestring):
+        schema = ZConfig.loadSchema(schemafile)
+    else:
+        schema = ZConfig.loadSchemaFile(schemafile)
+
+    # Load the configuration file
+    # Let's support both, an opened file and path
+    try:
+        if isinstance(configfile, basestring):
+            options, handlers = ZConfig.loadConfig(schema, configfile)
+        else:
+            options, handlers = ZConfig.loadConfigFile(schema, configfile)
+    except ZConfig.ConfigurationError, msg:
+        sys.stderr.write("Error: %s\n" % str(msg))
+        sys.exit(2)
+
+    # Insert all specified Python paths
+    if options.path:
+        sys.path[:0] = [os.path.abspath(p) for p in options.path]
+
+    # Setup the event log
+    options.eventlog()
+
+    # Configure the application
+    appsetup.config(options.site_definition, features=features)
+
+    # Connect to and open the database
+    db = appsetup.multi_database(options.databases)[0][0]
+
+    # Send out an event that the database has been opened
+    notify(appsetup.interfaces.DatabaseOpened(db))
+
+    # Create the WSGI application
+    return WSGIPublisherApplication(db, requestFactory)

Modified: Zope3/trunk/src/zope/app/wsgi/tests.py
===================================================================
--- Zope3/trunk/src/zope/app/wsgi/tests.py	2005-10-08 16:15:54 UTC (rev 38966)
+++ Zope3/trunk/src/zope/app/wsgi/tests.py	2005-10-08 16:27:57 UTC (rev 38967)
@@ -21,10 +21,11 @@
 
 def test_suite():
     return unittest.TestSuite((
-        doctest.DocFileSuite('README.txt',
-                             setUp=placelesssetup.setUp,
-                             tearDown=placelesssetup.tearDown,
-                             optionflags=doctest.NORMALIZE_WHITESPACE),
+        doctest.DocFileSuite(
+            'README.txt',
+            setUp=placelesssetup.setUp,
+            tearDown=placelesssetup.tearDown,
+            optionflags=doctest.NORMALIZE_WHITESPACE|doctest.ELLIPSIS),
         ))
 
 if __name__ == '__main__':

Modified: Zope3/trunk/src/zope/publisher/base.py
===================================================================
--- Zope3/trunk/src/zope/publisher/base.py	2005-10-08 16:15:54 UTC (rev 38966)
+++ Zope3/trunk/src/zope/publisher/base.py	2005-10-08 16:27:57 UTC (rev 38967)
@@ -21,7 +21,8 @@
 import traceback
 from cStringIO import StringIO
 
-from zope.deprecation import deprecation
+from zope.deprecation import deprecated
+
 from zope.interface import implements, providedBy
 from zope.interface.common.mapping import IReadMapping, IEnumerableMapping
 
@@ -80,7 +81,7 @@
     # BBB: Backward-compatibility for old body API
     def setBody(self, body):
         return self.setResult(body)
-    setBody = deprecation.deprecated(
+    setBody = deprecated(
         setBody,
         'setBody() has been deprecated in favor of setResult(). '
         'This will go away in Zope 3.4.')
@@ -326,26 +327,40 @@
         'See IPublicationRequest'
         self._traversal_stack[:] = list(stack)
 
+    def _getBodyStream(self):
+        'See zope.publisher.interfaces.IApplicationRequest'
+        return self._body_instream
+
+    bodyStream = property(_getBodyStream)
+
+    ########################################################################
+    # BBB: Deprecated; will go away in Zope 3.4
+
     def _getBody(self):
         body = getattr(self, '_body', None)
         if body is None:
             s = self._body_instream
             if s is None:
                 return None # TODO: what should be returned here?
-            p = s.tell()
-            s.seek(0)
             body = s.read()
-            s.seek(p)
             self._body = body
         return body
 
     body = property(_getBody)
+    body = deprecated(body,
+                      'The ``body`` attribute has been deprecated. Please '
+                      'use the ``bodyStream`` attribute directly. This '
+                      'attribute will go away in Zope 3.4.')
 
-    def _getBodyFile(self):
-        'See IApplicationRequest'
-        return self._body_instream
+    bodyFile = bodyStream
+    bodyFile = deprecated(bodyFile,
+                          'The ``bodyFile`` attribute has been replaced by '
+                          '``bodyStream``, which is a more accurate name. '
+                          'Streams are not necessarily files, i.e. they are '
+                          'not seekable. This attribute will go away in Zope '
+                          '3.4.')
 
-    bodyFile = property(_getBodyFile)
+    ########################################################################
 
     def __len__(self):
         'See Interface.Common.Mapping.IEnumerableMapping'

Modified: Zope3/trunk/src/zope/publisher/http.py
===================================================================
--- Zope3/trunk/src/zope/publisher/http.py	2005-10-08 16:15:54 UTC (rev 38966)
+++ Zope3/trunk/src/zope/publisher/http.py	2005-10-08 16:27:57 UTC (rev 38967)
@@ -22,6 +22,7 @@
 from Cookie import SimpleCookie
 from Cookie import CookieError
 import logging
+from tempfile import TemporaryFile
 
 from zope.deprecation import deprecation
 from zope.interface import implements
@@ -171,6 +172,37 @@
                 return default
             raise
 
+class HTTPInputStream(object):
+    """Special stream that supports caching the read data.
+
+    This is important, so that we can retry requests.
+    """
+
+    def __init__(self, stream):
+        self.stream = stream
+        self.cacheStream = TemporaryFile()
+
+    def getCacheStream(self):
+        self.read()
+        self.cacheStream.seek(0)
+        return self.cacheStream
+
+    def read(self, size=-1):
+        data = self.stream.read(size)
+        self.cacheStream.write(data)
+        return data
+
+    def readline(self):
+        data = self.stream.readline()
+        self.cacheStream.write(data)
+        return data
+
+    def readlines(self, hint=None):
+        data = self.stream.readlines(hint)
+        self.cacheStream.write(''.join(data))
+        return data
+        
+
 DEFAULT_PORTS = {'http': '80', 'https': '443'}
 STAGGER_RETRIES = True
 
@@ -251,7 +283,8 @@
                           2)
             environ, response = response, outstream
 
-        super(HTTPRequest, self).__init__(body_instream, environ, response)
+        super(HTTPRequest, self).__init__(
+            HTTPInputStream(body_instream), environ, response)
 
         self._orig_env = environ
         environ = sane_environment(environ)
@@ -389,10 +422,11 @@
         'See IPublisherRequest'
         count = getattr(self, '_retry_count', 0)
         self._retry_count = count + 1
-        self._body_instream.seek(0)
+
         new_response = self.response.retry()
         request = self.__class__(
-            body_instream=self._body_instream,
+            # Use the cache stream as the new input stream.
+            body_instream=self._body_instream.getCacheStream(),
             environ=self._orig_env,
             response=new_response,
             )

Modified: Zope3/trunk/src/zope/publisher/interfaces/__init__.py
===================================================================
--- Zope3/trunk/src/zope/publisher/interfaces/__init__.py	2005-10-08 16:15:54 UTC (rev 38966)
+++ Zope3/trunk/src/zope/publisher/interfaces/__init__.py	2005-10-08 16:27:57 UTC (rev 38967)
@@ -410,10 +410,21 @@
                              This is a read-only attribute.
                           """)
 
-    body = Attribute("""The body of the request as a string""")
+    bodyStream = Attribute(
+        """The stream that provides the data of the request.
 
-    bodyFile = Attribute("""The body of the request as a file""")
+        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).""")
 
     def __getitem__(key):

Modified: Zope3/trunk/src/zope/publisher/tests/basetestiapplicationrequest.py
===================================================================
--- Zope3/trunk/src/zope/publisher/tests/basetestiapplicationrequest.py	2005-10-08 16:15:54 UTC (rev 38966)
+++ Zope3/trunk/src/zope/publisher/tests/basetestiapplicationrequest.py	2005-10-08 16:27:57 UTC (rev 38967)
@@ -29,7 +29,7 @@
 
     def testHaveCustomTestsForIApplicationRequest(self):
         # Make sure that tests are defined for things we can't test here
-        self.test_IApplicationRequest_body
+        self.test_IApplicationRequest_bodyStream
 
     def testEnvironment(self):
         request = self._Test__new(foo='Foo', bar='Bar')

Modified: Zope3/trunk/src/zope/publisher/tests/test_baserequest.py
===================================================================
--- Zope3/trunk/src/zope/publisher/tests/test_baserequest.py	2005-10-08 16:15:54 UTC (rev 38966)
+++ Zope3/trunk/src/zope/publisher/tests/test_baserequest.py	2005-10-08 16:27:57 UTC (rev 38967)
@@ -40,15 +40,12 @@
     def _Test__expectedViewType(self):
         return None # we don't expect
 
-    def test_IApplicationRequest_body(self):
+    def test_IApplicationRequest_bodyStream(self):
         from zope.publisher.base import BaseRequest
 
         request = BaseRequest(StringIO('spam'), {})
-        self.assertEqual(request.body, 'spam')
+        self.assertEqual(request.bodyStream.read(), 'spam')
 
-        request = BaseRequest(StringIO('spam'), {})
-        self.assertEqual(request.bodyFile.read(), 'spam')
-
     def test_IPublicationRequest_getPositionalArguments(self):
         self.assertEqual(self._Test__new().getPositionalArguments(), ())
 

Modified: Zope3/trunk/src/zope/publisher/tests/test_http.py
===================================================================
--- Zope3/trunk/src/zope/publisher/tests/test_http.py	2005-10-08 16:15:54 UTC (rev 38966)
+++ Zope3/trunk/src/zope/publisher/tests/test_http.py	2005-10-08 16:27:57 UTC (rev 38967)
@@ -20,7 +20,8 @@
 
 from zope.interface import implements
 from zope.publisher.interfaces.logginginfo import ILoggingInfo
-from zope.publisher.http import HTTPRequest, HTTPResponse, StrResult
+from zope.publisher.http import HTTPRequest, HTTPResponse
+from zope.publisher.http import HTTPInputStream, StrResult
 from zope.publisher.publish import publish
 from zope.publisher.base import DefaultPublication
 from zope.publisher.interfaces.http import IHTTPRequest, IHTTPResponse
@@ -48,6 +49,53 @@
         return self._id
 
 
+data = '''\
+line 1
+line 2
+line 3'''
+
+
+class HTTPInputStreamTests(unittest.TestCase):
+
+    def setUp(self):
+        self.stream = HTTPInputStream(StringIO(data))
+
+    def getCacheStreamValue(self):
+        self.stream.cacheStream.seek(0)
+        return self.stream.cacheStream.read()
+
+    def testRead(self):
+        output = ''
+        self.assertEqual(output, self.getCacheStreamValue())
+        output += self.stream.read(5)
+        self.assertEqual(output, self.getCacheStreamValue())
+        output += self.stream.read()
+        self.assertEqual(output, self.getCacheStreamValue())
+        self.assertEqual(data, self.getCacheStreamValue())
+
+    def testReadLine(self):
+        output = self.stream.readline()
+        self.assertEqual(output, self.getCacheStreamValue())
+        output += self.stream.readline()
+        self.assertEqual(output, self.getCacheStreamValue())
+        output += self.stream.readline()
+        self.assertEqual(output, self.getCacheStreamValue())
+        output += self.stream.readline()
+        self.assertEqual(output, self.getCacheStreamValue())
+        self.assertEqual(data, self.getCacheStreamValue())
+
+    def testReadLines(self):
+        output = ''.join(self.stream.readlines(4))
+        self.assertEqual(output, self.getCacheStreamValue())
+        output += ''.join(self.stream.readlines())
+        self.assertEqual(output, self.getCacheStreamValue())
+        self.assertEqual(data, self.getCacheStreamValue())
+
+    def testGetChacheStream(self):
+        self.stream.read(5)
+        self.assertEqual(data, self.stream.getCacheStream().read())
+
+
 class HTTPTests(unittest.TestCase):
 
     _testEnv =  {
@@ -577,6 +625,7 @@
     suite = unittest.TestSuite()
     suite.addTest(unittest.makeSuite(ConcreteHTTPTests))
     suite.addTest(unittest.makeSuite(TestHTTPResponse))
+    suite.addTest(unittest.makeSuite(HTTPInputStreamTests))
     return suite
 
 

Copied: Zope3/trunk/ssh_host_rsa_key (from rev 38964, Zope3/branches/zope3-twisted-merge/ssh_host_rsa_key)

Copied: Zope3/trunk/ssh_host_rsa_key.pub (from rev 38964, Zope3/branches/zope3-twisted-merge/ssh_host_rsa_key.pub)

Copied: Zope3/trunk/trial.py (from rev 38964, Zope3/branches/zope3-twisted-merge/trial.py)

Modified: Zope3/trunk/z3.py
===================================================================
--- Zope3/trunk/z3.py	2005-10-08 16:15:54 UTC (rev 38966)
+++ Zope3/trunk/z3.py	2005-10-08 16:27:57 UTC (rev 38967)
@@ -55,7 +55,7 @@
     srcdir = os.path.abspath(src)
     sys.path = [srcdir, here] + basepath
 
-    from zope.app.server.main import main
+    from zope.app.twisted.main import main
     main(argv[1:])
 
 

Copied: Zope3/trunk/zope-zserver.conf.in (from rev 38964, Zope3/branches/zope3-twisted-merge/zope-zserver.conf.in)

Modified: Zope3/trunk/zope.conf.in
===================================================================
--- Zope3/trunk/zope.conf.in	2005-10-08 16:15:54 UTC (rev 38966)
+++ Zope3/trunk/zope.conf.in	2005-10-08 16:27:57 UTC (rev 38967)
@@ -5,41 +5,51 @@
 # interruptions (SIGINTR, thread switches):
 interrupt-check-interval 200
 
-<server http>
-  type WSGI-HTTP
+<server>
+  type HTTP
   address 8080
 </server>
 
-# uncomment this if you want the FTP server up and running
-#<server ftp>
-#  type FTP
-#  address 8021
-#</server>
+# Ready to go HTTPS server. You just need to make sure OpenSSL is installed.
+# <sslserver>
+#   type HTTPS
+#   address 8443
+#   privatekeypath server.pem
+#   certificatepath server.pem
+# </sslserver>
 
 # For debugging purposes, you can use this publisher instead/as well
 # (obviously if it's as well, use a different port number). If there's
 # an exception, Zope will drop into pdb at the point of the exception.
-#<server dbhttp>
+#<server>
 #  type PostmortemDebuggingHTTP
 #  address 8080
 #</server>
 
+# A special HTTP server that records HTTP session that can be converted to
+# functional tests.
+# <server>
+#   type RecordingHTTP
+#   address 8081
+# </server>
+
+<server>
+  type FTP
+  address 8021
+</server>
+
+# You must install pycrypto to use the SFTP server.
+# <sshserver>
+#   type SFTP
+#   address 8115
+#   hostkey ssh_host_rsa_key
+# </sshserver>
+
 # Standard Filestorage
-<zodb main>
+<zodb>
   <filestorage>
     path Data.fs
   </filestorage>
-
-# uncomment this if you want to connect to a local ZEO server
-# instead:
-#  <zeoclient>
-#    server localhost:9999
-#    storage 1
-#    # ZEO client cache, in bytes
-#    cache-size 20MB
-#    # Uncomment to have a persistent disk cache
-#    #client zeo1
-#  </zeoclient>
 </zodb>
 
 <accesslog>

Modified: Zope3/trunk/zopeskel/bin/runzope.in
===================================================================
--- Zope3/trunk/zopeskel/bin/runzope.in	2005-10-08 16:15:54 UTC (rev 38966)
+++ Zope3/trunk/zopeskel/bin/runzope.in	2005-10-08 16:27:57 UTC (rev 38967)
@@ -40,7 +40,7 @@
     sys.path[:] = [os.path.join(INSTANCE_HOME, "lib", "python"),
                    SOFTWARE_HOME] + basepath
 
-    from zope.app.server.main import main
+    from zope.app.twisted.main import main
     main(["-C", CONFIG_FILE] + sys.argv[1:])
 
 

Copied: Zope3/trunk/zopeskel/etc/server.pem (from rev 38964, Zope3/branches/zope3-twisted-merge/zopeskel/etc/server.pem)

Copied: Zope3/trunk/zopeskel/etc/ssh_host_rsa_key (from rev 38964, Zope3/branches/zope3-twisted-merge/zopeskel/etc/ssh_host_rsa_key)

Modified: Zope3/trunk/zopeskel/etc/zope.conf.in
===================================================================
--- Zope3/trunk/zopeskel/etc/zope.conf.in	2005-10-08 16:15:54 UTC (rev 38966)
+++ Zope3/trunk/zopeskel/etc/zope.conf.in	2005-10-08 16:27:57 UTC (rev 38967)
@@ -15,26 +15,55 @@
 #
 interrupt-check-interval 200
 
+# Standard HTTP server for Zope 3.
+# Server: All Servers
 <server>
   type HTTP
   address 8080
 </server>
 
-# uncomment this if you want the FTP server up and running
-#<server ftp>
-#  type FTP
-#  address 8021
-#</server>
+# Ready to go HTTPS server. You just need to make sure OpenSSL is installed.
+# Server: Twisted only!
+# <sslserver>
+#   type HTTPS
+#   address 8443
+#   privatekeypath server.pem
+#   certificatepath server.pem
+# </sslserver>
 
+# A special HTTP server that records HTTP session that can be converted to
+# functional tests.
+# Server: Twisted only!
+# <server>
+#   type RecordingHTTP
+#   address 8081
+# </server>
+
 # For debugging purposes, you can use this publisher instead/as well
 # (obviously if it's as well, use a different port number). If there's
 # an exception, Zope will drop into pdb at the point of the exception.
+# Server: Twisted only!
 #
 #<server>
 #  type PostmortemDebuggingHTTP
 #  address 8080
 #</server>
 
+# uncomment this if you want the FTP server up and running
+# Server: All Servers
+#<server ftp>
+#  type FTP
+#  address 8021
+#</server>
+
+# You must install pycrypto to use the SFTP server.
+# Server: Twisted only!
+# <sshserver>
+#   type SFTP
+#   address 8115
+#   hostkey ssh_host_rsa_key
+# </sshserver>
+
 # Standard Filestorage
 <zodb>
   <filestorage>



More information about the Zope3-Checkins mailing list