[Zope3-checkins] SVN: Zope3/branches/srichter-twisted-integration2/ Merged rev 29982, the fix for bug 390.

Stephan Richter srichter at cosmos.phy.tufts.edu
Wed Sep 7 20:34:12 EDT 2005


Log message for revision 38377:
  Merged rev 29982, the fix for bug 390.
  
  

Changed:
  U   Zope3/branches/srichter-twisted-integration2/doc/CHANGES.txt
  _U  Zope3/branches/srichter-twisted-integration2/src/
  U   Zope3/branches/srichter-twisted-integration2/src/zope/app/dav/mkcol.py
  U   Zope3/branches/srichter-twisted-integration2/src/zope/app/dav/propfind.py
  U   Zope3/branches/srichter-twisted-integration2/src/zope/app/dav/proppatch.py
  U   Zope3/branches/srichter-twisted-integration2/src/zope/app/fssync/browser/__init__.py
  U   Zope3/branches/srichter-twisted-integration2/src/zope/app/http/put.py
  U   Zope3/branches/srichter-twisted-integration2/src/zope/publisher/base.py
  U   Zope3/branches/srichter-twisted-integration2/src/zope/publisher/http.py
  U   Zope3/branches/srichter-twisted-integration2/src/zope/publisher/interfaces/__init__.py
  U   Zope3/branches/srichter-twisted-integration2/src/zope/publisher/tests/basetestiapplicationrequest.py
  U   Zope3/branches/srichter-twisted-integration2/src/zope/publisher/tests/test_baserequest.py
  U   Zope3/branches/srichter-twisted-integration2/src/zope/publisher/tests/test_http.py

-=-
Modified: Zope3/branches/srichter-twisted-integration2/doc/CHANGES.txt
===================================================================
--- Zope3/branches/srichter-twisted-integration2/doc/CHANGES.txt	2005-09-07 23:48:38 UTC (rev 38376)
+++ Zope3/branches/srichter-twisted-integration2/doc/CHANGES.txt	2005-09-08 00:34:11 UTC (rev 38377)
@@ -6,10 +6,15 @@
 
   For information on future releases, see ROADMAP.txt.
 
-  Some future release (Zope 3.2.0)
+  Some future release Zope 3.2.0
 
     New features
 
+      - 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
@@ -72,6 +77,8 @@
 
     Much thanks to everyone who contributed to this release:
 
+      Stephan Richter, Michael Kerrin, Jim Fulton
+
     Note: If you are not listed and contributed, please add yourself. This
           note will be deleted before the release.
 


Property changes on: Zope3/branches/srichter-twisted-integration2/src
___________________________________________________________________
Name: svn:externals
   - BTrees         svn://svn.zope.org/repos/main/ZODB/tags/3.4.0a2/src/BTrees
persistent     svn://svn.zope.org/repos/main/ZODB/tags/3.4.0a2/src/persistent
ThreadedAsync  svn://svn.zope.org/repos/main/ZODB/tags/3.4.0a2/src/ThreadedAsync
transaction    svn://svn.zope.org/repos/main/ZODB/tags/3.4.0a2/src/transaction
ZEO            svn://svn.zope.org/repos/main/ZODB/tags/3.4.0a2/src/ZEO
ZODB           svn://svn.zope.org/repos/main/ZODB/tags/3.4.0a2/src/ZODB
twisted        svn://svn.twistedmatrix.com/svn/Twisted/trunk/twisted

   + 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.0a3/src/BTrees
persistent     svn://svn.zope.org/repos/main/ZODB/tags/3.6.0a3/src/persistent
ThreadedAsync  svn://svn.zope.org/repos/main/ZODB/tags/3.6.0a3/src/ThreadedAsync
transaction    svn://svn.zope.org/repos/main/ZODB/tags/3.6.0a3/src/transaction
ZEO            svn://svn.zope.org/repos/main/ZODB/tags/3.6.0a3/src/ZEO
ZODB           svn://svn.zope.org/repos/main/ZODB/tags/3.6.0a3/src/ZODB
twisted        svn://svn.twistedmatrix.com/svn/Twisted/trunk/twisted



Modified: Zope3/branches/srichter-twisted-integration2/src/zope/app/dav/mkcol.py
===================================================================
--- Zope3/branches/srichter-twisted-integration2/src/zope/app/dav/mkcol.py	2005-09-07 23:48:38 UTC (rev 38376)
+++ Zope3/branches/srichter-twisted-integration2/src/zope/app/dav/mkcol.py	2005-09-08 00:34:11 UTC (rev 38377)
@@ -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/branches/srichter-twisted-integration2/src/zope/app/dav/propfind.py
===================================================================
--- Zope3/branches/srichter-twisted-integration2/src/zope/app/dav/propfind.py	2005-09-07 23:48:38 UTC (rev 38376)
+++ Zope3/branches/srichter-twisted-integration2/src/zope/app/dav/propfind.py	2005-09-08 00:34:11 UTC (rev 38377)
@@ -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/branches/srichter-twisted-integration2/src/zope/app/dav/proppatch.py
===================================================================
--- Zope3/branches/srichter-twisted-integration2/src/zope/app/dav/proppatch.py	2005-09-07 23:48:38 UTC (rev 38376)
+++ Zope3/branches/srichter-twisted-integration2/src/zope/app/dav/proppatch.py	2005-09-08 00:34:11 UTC (rev 38377)
@@ -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/branches/srichter-twisted-integration2/src/zope/app/fssync/browser/__init__.py
===================================================================
--- Zope3/branches/srichter-twisted-integration2/src/zope/app/fssync/browser/__init__.py	2005-09-07 23:48:38 UTC (rev 38376)
+++ Zope3/branches/srichter-twisted-integration2/src/zope/app/fssync/browser/__init__.py	2005-09-08 00:34:11 UTC (rev 38377)
@@ -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/branches/srichter-twisted-integration2/src/zope/app/http/put.py
===================================================================
--- Zope3/branches/srichter-twisted-integration2/src/zope/app/http/put.py	2005-09-07 23:48:38 UTC (rev 38376)
+++ Zope3/branches/srichter-twisted-integration2/src/zope/app/http/put.py	2005-09-08 00:34:11 UTC (rev 38377)
@@ -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/branches/srichter-twisted-integration2/src/zope/publisher/base.py
===================================================================
--- Zope3/branches/srichter-twisted-integration2/src/zope/publisher/base.py	2005-09-07 23:48:38 UTC (rev 38376)
+++ Zope3/branches/srichter-twisted-integration2/src/zope/publisher/base.py	2005-09-08 00:34:11 UTC (rev 38377)
@@ -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/branches/srichter-twisted-integration2/src/zope/publisher/http.py
===================================================================
--- Zope3/branches/srichter-twisted-integration2/src/zope/publisher/http.py	2005-09-07 23:48:38 UTC (rev 38376)
+++ Zope3/branches/srichter-twisted-integration2/src/zope/publisher/http.py	2005-09-08 00:34:11 UTC (rev 38377)
@@ -16,6 +16,7 @@
 $Id$
 """
 import re, time, random
+import cStringIO
 from urllib import quote, unquote, splitport
 from types import StringTypes, ClassType
 from cgi import escape
@@ -169,6 +170,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 = cStringIO.StringIO()
+
+    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
 
@@ -249,7 +281,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)
@@ -380,10 +413,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/branches/srichter-twisted-integration2/src/zope/publisher/interfaces/__init__.py
===================================================================
--- Zope3/branches/srichter-twisted-integration2/src/zope/publisher/interfaces/__init__.py	2005-09-07 23:48:38 UTC (rev 38376)
+++ Zope3/branches/srichter-twisted-integration2/src/zope/publisher/interfaces/__init__.py	2005-09-08 00:34:11 UTC (rev 38377)
@@ -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/branches/srichter-twisted-integration2/src/zope/publisher/tests/basetestiapplicationrequest.py
===================================================================
--- Zope3/branches/srichter-twisted-integration2/src/zope/publisher/tests/basetestiapplicationrequest.py	2005-09-07 23:48:38 UTC (rev 38376)
+++ Zope3/branches/srichter-twisted-integration2/src/zope/publisher/tests/basetestiapplicationrequest.py	2005-09-08 00:34:11 UTC (rev 38377)
@@ -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/branches/srichter-twisted-integration2/src/zope/publisher/tests/test_baserequest.py
===================================================================
--- Zope3/branches/srichter-twisted-integration2/src/zope/publisher/tests/test_baserequest.py	2005-09-07 23:48:38 UTC (rev 38376)
+++ Zope3/branches/srichter-twisted-integration2/src/zope/publisher/tests/test_baserequest.py	2005-09-08 00:34:11 UTC (rev 38377)
@@ -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/branches/srichter-twisted-integration2/src/zope/publisher/tests/test_http.py
===================================================================
--- Zope3/branches/srichter-twisted-integration2/src/zope/publisher/tests/test_http.py	2005-09-07 23:48:38 UTC (rev 38376)
+++ Zope3/branches/srichter-twisted-integration2/src/zope/publisher/tests/test_http.py	2005-09-08 00:34:11 UTC (rev 38377)
@@ -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,48 @@
         return self._id
 
 
+data = '''\
+line 1
+line 2
+line 3'''
+
+
+class HTTPInputStreamTests(unittest.TestCase):
+
+    def setUp(self):
+        self.stream = HTTPInputStream(StringIO(data))
+
+    def testRead(self):
+        output = ''
+        self.assertEqual(output, self.stream.cacheStream.getvalue())
+        output += self.stream.read(5)
+        self.assertEqual(output, self.stream.cacheStream.getvalue())
+        output += self.stream.read()
+        self.assertEqual(output, self.stream.cacheStream.getvalue())
+        self.assertEqual(data, self.stream.cacheStream.getvalue())
+
+    def testReadLine(self):
+        output = self.stream.readline()
+        self.assertEqual(output, self.stream.cacheStream.getvalue())
+        output += self.stream.readline()
+        self.assertEqual(output, self.stream.cacheStream.getvalue())
+        output += self.stream.readline()
+        self.assertEqual(output, self.stream.cacheStream.getvalue())
+        output += self.stream.readline()
+        self.assertEqual(output, self.stream.cacheStream.getvalue())
+        self.assertEqual(data, self.stream.cacheStream.getvalue())
+
+    def testReadLines(self):
+        output = ''.join(self.stream.readlines(4))
+        self.assertEqual(output, self.stream.cacheStream.getvalue())
+        output += ''.join(self.stream.readlines())
+        self.assertEqual(output, self.stream.cacheStream.getvalue())
+        self.assertEqual(data, self.stream.cacheStream.getvalue())
+
+    def testGetChacheStream(self):
+        self.stream.read(5)
+        self.assertEqual(data, self.stream.getCacheStream().getvalue())
+
 class HTTPTests(unittest.TestCase):
 
     _testEnv =  {
@@ -558,6 +601,7 @@
     suite = unittest.TestSuite()
     suite.addTest(unittest.makeSuite(ConcreteHTTPTests))
     suite.addTest(unittest.makeSuite(TestHTTPResponse))
+    suite.addTest(unittest.makeSuite(HTTPInputStreamTests))
     return suite
 
 



More information about the Zope3-Checkins mailing list