[Zope-Checkins] SVN: Zope/branches/2.12/ Hoping that silence (or apathy?) is consent here. :) Adding an event to indicate when streaming is starting in case of chunked/streamed responses using response.write()

Martin Aspeli optilude at gmx.net
Fri Mar 26 09:14:47 EDT 2010


Log message for revision 110187:
  Hoping that silence (or apathy?) is consent here. :) Adding an event to indicate when streaming is starting in case of chunked/streamed responses using response.write()

Changed:
  U   Zope/branches/2.12/doc/CHANGES.rst
  U   Zope/branches/2.12/src/ZPublisher/HTTPResponse.py
  U   Zope/branches/2.12/src/ZPublisher/interfaces.py
  U   Zope/branches/2.12/src/ZPublisher/pubevents.py
  U   Zope/branches/2.12/src/ZPublisher/tests/testpubevents.py
  U   Zope/branches/2.12/src/ZServer/HTTPResponse.py
  U   Zope/branches/2.12/src/ZServer/tests/test_responses.py

-=-
Modified: Zope/branches/2.12/doc/CHANGES.rst
===================================================================
--- Zope/branches/2.12/doc/CHANGES.rst	2010-03-26 12:48:34 UTC (rev 110186)
+++ Zope/branches/2.12/doc/CHANGES.rst	2010-03-26 13:14:46 UTC (rev 110187)
@@ -17,6 +17,11 @@
   - ExtensionClass = 2.13.0
   - Persistence = 2.13.0
 
+- There is now an event ZPublisher.interfaces.IPubBeforeStreaming which will
+  be fired just before the first chunk of data is written to the response
+  stream when using the write() method on the response. This is the last
+  possible point at which response headers may be set in this case.
+
 Bugs Fixed
 ++++++++++
 

Modified: Zope/branches/2.12/src/ZPublisher/HTTPResponse.py
===================================================================
--- Zope/branches/2.12/src/ZPublisher/HTTPResponse.py	2010-03-26 12:48:34 UTC (rev 110186)
+++ Zope/branches/2.12/src/ZPublisher/HTTPResponse.py	2010-03-26 13:14:46 UTC (rev 110187)
@@ -18,10 +18,12 @@
 import types, os, sys, re
 import zlib, struct
 from string import translate, maketrans
+from zope.event import notify
 from BaseResponse import BaseResponse
 from zExceptions import Unauthorized, Redirect
 from zExceptions.ExceptionFormatter import format_exception
 from ZPublisher import BadRequest, InternalError, NotFound
+from ZPublisher.pubevents import PubBeforeStreaming
 from cgi import escape
 from urllib import quote
 
@@ -921,6 +923,9 @@
 
         """
         if not self._wrote:
+            
+            notify(PubBeforeStreaming(self))
+            
             self.outputBody()
             self._wrote = 1
             self.stdout.flush()

Modified: Zope/branches/2.12/src/ZPublisher/interfaces.py
===================================================================
--- Zope/branches/2.12/src/ZPublisher/interfaces.py	2010-03-26 12:48:34 UTC (rev 110186)
+++ Zope/branches/2.12/src/ZPublisher/interfaces.py	2010-03-26 13:14:46 UTC (rev 110187)
@@ -50,3 +50,11 @@
     """
     exc_info = Attribute('''The exception info as returned by 'sys.exc_info()'.''')
     retry = Attribute('Whether the request will be retried')
+
+class IPubBeforeStreaming(Interface):
+    """Event fired just before a streaming response is initiated, i.e. when
+    something calls response.write() for the first time. Note that this is
+    carries a reference to the *response*, not the request.
+    """
+    
+    response = Attribute(u"The current HTTP response")

Modified: Zope/branches/2.12/src/ZPublisher/pubevents.py
===================================================================
--- Zope/branches/2.12/src/ZPublisher/pubevents.py	2010-03-26 12:48:34 UTC (rev 110186)
+++ Zope/branches/2.12/src/ZPublisher/pubevents.py	2010-03-26 13:14:46 UTC (rev 110187)
@@ -10,7 +10,8 @@
 from zope.interface import implements
 
 from interfaces import IPubStart, IPubSuccess, IPubFailure, \
-     IPubAfterTraversal, IPubBeforeCommit, IPubBeforeAbort
+     IPubAfterTraversal, IPubBeforeCommit, IPubBeforeAbort, \
+     IPubBeforeStreaming
 
 class _Base(object):
     """PubEvent base class."""
@@ -49,3 +50,11 @@
     
     def __init__(self, request, exc_info, retry):
         self.request, self.exc_info, self.retry = request, exc_info, retry
+
+class PubBeforeStreaming(object):
+    """Notified immediately before streaming via response.write() commences
+    """
+    implements(IPubBeforeStreaming)
+    
+    def __init__(self, response):
+        self.response = response

Modified: Zope/branches/2.12/src/ZPublisher/tests/testpubevents.py
===================================================================
--- Zope/branches/2.12/src/ZPublisher/tests/testpubevents.py	2010-03-26 12:48:34 UTC (rev 110186)
+++ Zope/branches/2.12/src/ZPublisher/tests/testpubevents.py	2010-03-26 13:14:46 UTC (rev 110187)
@@ -1,3 +1,4 @@
+from StringIO import StringIO
 from sys import modules, exc_info
 from unittest import TestCase, TestSuite, makeSuite, main
 
@@ -7,11 +8,14 @@
 
 from ZPublisher.Publish import publish, Retry
 from ZPublisher.BaseRequest import BaseRequest
+from ZPublisher.HTTPResponse import HTTPResponse
 from ZPublisher.pubevents import PubStart, PubSuccess, PubFailure, \
-     PubAfterTraversal, PubBeforeCommit, PubBeforeAbort
+     PubAfterTraversal, PubBeforeCommit, PubBeforeAbort, \
+     PubBeforeStreaming
 from ZPublisher.interfaces import \
      IPubStart, IPubEnd, IPubSuccess, IPubFailure, \
-     IPubAfterTraversal, IPubBeforeCommit
+     IPubAfterTraversal, IPubBeforeCommit, \
+     IPubBeforeStreaming
 
 PUBMODULE = 'TEST_testpubevents'
 
@@ -41,7 +45,10 @@
     def testBeforeCommit(self):
         e = PubBeforeCommit(_Request())
         verifyObject(IPubBeforeCommit, e)
-
+    
+    def testBeforeStreaming(self):
+        e = PubBeforeStreaming(_Response())
+        verifyObject(IPubBeforeStreaming, e)
         
 class TestPubEvents(TestCase):
     def setUp(self):
@@ -127,6 +134,21 @@
         self.assert_(isinstance(events[5], PubBeforeCommit))
         self.assert_(isinstance(events[6], PubSuccess))
 
+    def testStreaming(self):
+        
+        out = StringIO()
+        response = HTTPResponse(stdout=out)
+        response.write('datachunk1')
+        response.write('datachunk2')
+        
+        events = self.reporter.events
+        self.assertEqual(len(events), 1)
+        self.assert_(isinstance(events[0], PubBeforeStreaming))
+        self.assertEqual(events[0].response, response)
+        
+        self.failUnless('datachunk1datachunk2' in out.getvalue())
+
+
 # Auxiliaries
 def _succeed():
     ''' '''

Modified: Zope/branches/2.12/src/ZServer/HTTPResponse.py
===================================================================
--- Zope/branches/2.12/src/ZServer/HTTPResponse.py	2010-03-26 12:48:34 UTC (rev 110186)
+++ Zope/branches/2.12/src/ZServer/HTTPResponse.py	2010-03-26 13:14:46 UTC (rev 110187)
@@ -20,8 +20,10 @@
 import time, re,  sys, tempfile
 from cStringIO import StringIO
 import thread
+from zope.event import notify
 from ZPublisher.HTTPResponse import HTTPResponse
 from ZPublisher.Iterators import IStreamIterator
+from ZPublisher.pubevents import PubBeforeStreaming
 from medusa.http_date import build_http_date
 from PubCore.ZEvent import Wakeup
 from medusa.producers import hooked_producer
@@ -165,6 +167,9 @@
         stdout=self.stdout
 
         if not self._wrote:
+            
+            notify(PubBeforeStreaming(self))
+            
             l=self.headers.get('content-length', None)
             if l is not None:
                 try:

Modified: Zope/branches/2.12/src/ZServer/tests/test_responses.py
===================================================================
--- Zope/branches/2.12/src/ZServer/tests/test_responses.py	2010-03-26 12:48:34 UTC (rev 110186)
+++ Zope/branches/2.12/src/ZServer/tests/test_responses.py	2010-03-26 13:14:46 UTC (rev 110187)
@@ -19,17 +19,21 @@
 from ZServer.PCGIServer import PCGIResponse
 from ZServer.FCGIServer import FCGIResponse
 from ZPublisher.Iterators import IStreamIterator
+from ZPublisher.pubevents import PubBeforeStreaming
 from zope.interface import implements
 import unittest
 from cStringIO import StringIO
 
+from zope.event import subscribers
+
+
 class ZServerResponseTestCase(unittest.TestCase):
     """Test ZServer response objects."""
 
     def test_http_response_write_unicode(self):
         response = ZServerHTTPResponse()
         self.assertRaises(TypeError, response.write, u'bad')
-
+    
     def test_ftp_response_write_unicode(self):
         response = FTPResponse()
         self.assertRaises(TypeError, response.write, u'bad')
@@ -57,7 +61,7 @@
         one = ZServerHTTPResponse(stdout=DummyChannel())
         self.assertRaises(AssertionError,
                           one.setBody, test_streamiterator())
-        
+    
 class DummyChannel:
     def __init__(self):
         self.out = StringIO()
@@ -267,12 +271,39 @@
                                        '',
                                        ''))
 
+class _Reporter(object):
+    def __init__(self): self.events = []
+    def __call__(self, event): self.events.append(event)
 
+class ZServerHTTPResponseEventsTestCase(unittest.TestCase):
+
+    def setUp(self):
+        self._saved_subscribers = subscribers[:]
+        self.reporter = r = _Reporter()
+        subscribers[:] = [r]
+
+    def tearDown(self):
+        subscribers[:] = self._saved_subscribers
+    
+    def testStreaming(self):
+        out = StringIO()
+        response = ZServerHTTPResponse(stdout=out)
+        response.write('datachunk1')
+        response.write('datachunk2')
+        
+        events = self.reporter.events
+        self.assertEqual(len(events), 1)
+        self.assert_(isinstance(events[0], PubBeforeStreaming))
+        self.assertEqual(events[0].response, response)
+        
+        self.failUnless('datachunk1datachunk2' in out.getvalue())
+
 def test_suite():
     suite = unittest.TestSuite()
     suite.addTests((
         unittest.makeSuite(ZServerResponseTestCase),
-        unittest.makeSuite(ZServerHTTPResponseTestCase)
+        unittest.makeSuite(ZServerHTTPResponseTestCase),
+        unittest.makeSuite(ZServerHTTPResponseEventsTestCase)
     ))
     return suite
 



More information about the Zope-Checkins mailing list