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 _______________________________________________ Zope-Checkins maillist - Zope-Checkins@zope.org https://mail.zope.org/mailman/listinfo/zope-checkins