[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