[Zope3-checkins] SVN: Zope3/trunk/ - Formalized the Publisher Response API.

Stephan Richter srichter at cosmos.phy.tufts.edu
Wed Sep 7 16:14:35 EDT 2005


Log message for revision 38357:
  - Formalized the Publisher Response API.
  
    + Until now the publisher made assumptions about the form of ouput of
      a publishing process. Either the called method returned a string
      (regular or unicode) or the response's write() method was used
      directly to write the data. Those models do not work well with some
      protocols. Thus, now the publisher deals with result objects. Those
      are generally not well defined, but for HTTP they must implement the
      IResult interface.
  
    + HTTP responses provide two new methods that make reading the output
      easier: `consumeBody()` and `consumeBodyIter()`. Either method can
      be only called once. After that the output iterator is used and
      empty.
  
    + The WSGI specification specifically has some provisions in it that
      supported our use of writing directly to the output stream. However,
      this method of providing an output is strongly discouraged. Instead,
      the application should return an iterable. Using the new IResult
      implementation in the HTTP publisher, we can now return such an
      iterable.
  
    + When a retry is issued in the publisher, then a new request is
      created. This means that the request (including its response) that
      were passed into `publish()` are not necessarily the same that are
      eventually used. This caused some failures in the WSGI support. Now
      the `publish()` method returns the used request and the request's
      `close()` method does not unlink the response, so that
      `request.response` is still available after the publish call.
  
  
  There are several oppurtunities here:
  
  1. Support large file uploads and downloads without using up a lot of 
     memory.
  
  2. Use the concept of results for protocols that do not fit the simple 
     body response scheme.
  
  ...
  
  

Changed:
  U   Zope3/trunk/doc/CHANGES.txt
  U   Zope3/trunk/src/zope/app/authentication/ftpplugins.py
  U   Zope3/trunk/src/zope/app/container/browser/tests/test_adding.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/dav/tests/test_propfind.py
  U   Zope3/trunk/src/zope/app/dav/tests/test_proppatch.py
  U   Zope3/trunk/src/zope/app/debug/debug.py
  U   Zope3/trunk/src/zope/app/file/browser/file.txt
  U   Zope3/trunk/src/zope/app/file/browser/ftests.py
  U   Zope3/trunk/src/zope/app/ftests/doctest.txt
  U   Zope3/trunk/src/zope/app/http/exception/tests/test_methodnotallowed.py
  U   Zope3/trunk/src/zope/app/http/tests/test_put.py
  U   Zope3/trunk/src/zope/app/i18n/browser/tests/test_translate.py
  U   Zope3/trunk/src/zope/app/publication/browser.py
  U   Zope3/trunk/src/zope/app/publication/httpfactory.py
  U   Zope3/trunk/src/zope/app/publication/tests/test_browserpublication.py
  U   Zope3/trunk/src/zope/app/publication/tests/test_http.py
  U   Zope3/trunk/src/zope/app/publication/tests/test_httpfactory.py
  U   Zope3/trunk/src/zope/app/publication/tests/test_zopepublication.py
  U   Zope3/trunk/src/zope/app/publication/zopepublication.py
  U   Zope3/trunk/src/zope/app/publisher/xmlrpc/README.txt
  U   Zope3/trunk/src/zope/app/recorder/__init__.py
  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/ftp.py
  D   Zope3/trunk/src/zope/app/server/http.py
  U   Zope3/trunk/src/zope/app/server/wsgi.py
  U   Zope3/trunk/src/zope/app/session/http.py
  U   Zope3/trunk/src/zope/app/session/session.py
  U   Zope3/trunk/src/zope/app/session/tests.py
  U   Zope3/trunk/src/zope/app/testing/functional.py
  U   Zope3/trunk/src/zope/app/testing/tests.py
  U   Zope3/trunk/src/zope/app/wsgi/README.txt
  U   Zope3/trunk/src/zope/app/wsgi/__init__.py
  U   Zope3/trunk/src/zope/publisher/base.py
  U   Zope3/trunk/src/zope/publisher/browser.py
  U   Zope3/trunk/src/zope/publisher/ftp.py
  U   Zope3/trunk/src/zope/publisher/http.py
  U   Zope3/trunk/src/zope/publisher/interfaces/__init__.py
  U   Zope3/trunk/src/zope/publisher/interfaces/http.py
  U   Zope3/trunk/src/zope/publisher/publish.py
  U   Zope3/trunk/src/zope/publisher/tests/basetestipublicationrequest.py
  U   Zope3/trunk/src/zope/publisher/tests/httprequest.py
  U   Zope3/trunk/src/zope/publisher/tests/publication.py
  U   Zope3/trunk/src/zope/publisher/tests/test_baserequest.py
  U   Zope3/trunk/src/zope/publisher/tests/test_baseresponse.py
  U   Zope3/trunk/src/zope/publisher/tests/test_browserrequest.py
  U   Zope3/trunk/src/zope/publisher/tests/test_browserresponse.py
  U   Zope3/trunk/src/zope/publisher/tests/test_ftp.py
  U   Zope3/trunk/src/zope/publisher/tests/test_http.py
  U   Zope3/trunk/src/zope/publisher/tests/test_ipublication.py
  U   Zope3/trunk/src/zope/publisher/tests/test_publisher.py
  U   Zope3/trunk/src/zope/publisher/tests/test_xmlrpcrequest.py
  U   Zope3/trunk/src/zope/publisher/xmlrpc.py
  U   Zope3/trunk/src/zope/server/ftp/tests/test_publisher.py
  U   Zope3/trunk/src/zope/server/http/publisherhttpserver.py
  D   Zope3/trunk/src/zope/server/http/tests/test_publisherserver.py
  A   Zope3/trunk/src/zope/server/http/tests/test_wsgiserver.py
  U   Zope3/trunk/src/zope/server/http/wsgihttpserver.py
  U   Zope3/trunk/zope.conf.in

-=-
Modified: Zope3/trunk/doc/CHANGES.txt
===================================================================
--- Zope3/trunk/doc/CHANGES.txt	2005-09-07 19:34:16 UTC (rev 38356)
+++ Zope3/trunk/doc/CHANGES.txt	2005-09-07 20:14:34 UTC (rev 38357)
@@ -10,6 +10,36 @@
 
     New features
 
+      - Formalized the Publisher Response API.
+
+        + Until now the publisher made assumptions about the form of ouput of
+          a publishing process. Either the called method returned a string
+          (regular or unicode) or the response's write() method was used
+          directly to write the data. Those models do not work well with some
+          protocols. Thus, now the publisher deals with result objects. Those
+          are generally not well defined, but for HTTP they must implement the
+          IResult interface.
+
+        + HTTP responses provide two new methods that make reading the output
+          easier: `consumeBody()` and `consumeBodyIter()`. Either method can
+          be only called once. After that the output iterator is used and
+          empty.
+
+        + The WSGI specification specifically has some provisions in it that
+          supported our use of writing directly to the output stream. However,
+          this method of providing an output is strongly discouraged. Instead,
+          the application should return an iterable. Using the new IResult
+          implementation in the HTTP publisher, we can now return such an
+          iterable.
+
+        + When a retry is issued in the publisher, then a new request is
+          created. This means that the request (including its response) that
+          were passed into `publish()` are not necessarily the same that are
+          eventually used. This caused some failures in the WSGI support. Now
+          the `publish()` method returns the used request and the request's
+          `close()` method does not unlink the response, so that
+          `request.response` is still available after the publish call.
+
       - Added a re-implementation of i18n message IDs (now simply
         called ``Message``) that is immutable and thus can be treated
         like unicode strings with respect to security proxying.  This

Modified: Zope3/trunk/src/zope/app/authentication/ftpplugins.py
===================================================================
--- Zope3/trunk/src/zope/app/authentication/ftpplugins.py	2005-09-07 19:34:16 UTC (rev 38356)
+++ Zope3/trunk/src/zope/app/authentication/ftpplugins.py	2005-09-07 20:14:34 UTC (rev 38357)
@@ -31,7 +31,7 @@
 
           >>> from zope.publisher.ftp import FTPRequest
           >>> from StringIO import StringIO
-          >>> request = FTPRequest(StringIO(''), StringIO(),
+          >>> request = FTPRequest(StringIO(''),
           ...                      {'credentials': ('bob', '123'),
           ...                       'path': '/a/b/c'})
 

Modified: Zope3/trunk/src/zope/app/container/browser/tests/test_adding.py
===================================================================
--- Zope3/trunk/src/zope/app/container/browser/tests/test_adding.py	2005-09-07 19:34:16 UTC (rev 38356)
+++ Zope3/trunk/src/zope/app/container/browser/tests/test_adding.py	2005-09-07 20:14:34 UTC (rev 38357)
@@ -195,10 +195,10 @@
         ztapi.browserView(None, '', AbsoluteURL, providing=IAbsoluteURL)
         self.assertRaises(UserError, adding.action, '', 'foo')
         adding.action('Thing', 'foo')
-        self.assertEqual(adding.request.response._headers['location'],
+        self.assertEqual(adding.request.response.getHeader('location'),
                          '/container/+/Thing=foo')
         adding.action('Thing/screen1', 'foo')
-        self.assertEqual(adding.request.response._headers['location'],
+        self.assertEqual(adding.request.response.getHeader('location'),
                          '/container/+/Thing/screen1=foo')
 
     def test_publishTraverse_factory(self):

Modified: Zope3/trunk/src/zope/app/dav/propfind.py
===================================================================
--- Zope3/trunk/src/zope/app/dav/propfind.py	2005-09-07 19:34:16 UTC (rev 38356)
+++ Zope3/trunk/src/zope/app/dav/propfind.py	2005-09-07 20:14:34 UTC (rev 38357)
@@ -96,7 +96,7 @@
         self._depthRecurse(ms)
 
         body = resp.toxml('utf-8')
-        self.request.response.setBody(body)
+        self.request.response.setResult(body)
         self.request.response.setStatus(207)
         self.request.response.setHeader('content-type', 'text/xml')
         return body

Modified: Zope3/trunk/src/zope/app/dav/proppatch.py
===================================================================
--- Zope3/trunk/src/zope/app/dav/proppatch.py	2005-09-07 19:34:16 UTC (rev 38356)
+++ Zope3/trunk/src/zope/app/dav/proppatch.py	2005-09-07 20:14:34 UTC (rev 38357)
@@ -47,13 +47,13 @@
         _avail_props = {}
         # List all *registered* DAV interface namespaces and their properties
         for ns, iface in zapi.getUtilitiesFor(IDAVNamespace):
-            _avail_props[ns] = getFieldNamesInOrder(iface)    
+            _avail_props[ns] = getFieldNamesInOrder(iface)
         # List all opaque DAV namespaces and the properties we know of
         if self.oprops:
             for ns, oprops in self.oprops.items():
                 _avail_props[ns] = list(oprops.keys())
         self.avail_props = _avail_props
-    
+
     def PROPPATCH(self):
         if self.content_type not in ['text/xml', 'application/xml']:
             self.request.response.setStatus(400)
@@ -73,7 +73,7 @@
         ms.lastChild.appendChild(resp.createElement('href'))
         ms.lastChild.lastChild.appendChild(resp.createTextNode(resource_url))
 
-        updateel = xmldoc.getElementsByTagNameNS(self.default_ns, 
+        updateel = xmldoc.getElementsByTagNameNS(self.default_ns,
                                                  'propertyupdate')
         if not updateel:
             self.request.response.setStatus(422)
@@ -87,7 +87,7 @@
         self._handlePropertyUpdate(resp, updates)
 
         body = resp.toxml('utf-8')
-        self.request.response.setBody(body)
+        self.request.response.setResult(body)
         self.request.response.setStatus(207)
         return body
 
@@ -108,7 +108,7 @@
                 props = results.setdefault(node.namespaceURI, [])
                 if node.localName not in props:
                     props.append(node.localName)
-        
+
         if _propresults.keys() != [200]:
             # At least some props failed, abort transaction
             transaction.abort()
@@ -119,7 +119,7 @@
                     failed_props = failed.setdefault(ns, [])
                     failed_props.extend(props)
                 del _propresults[200]
-        
+
         # Create the response document
         re = resp.lastChild.lastChild
         for status, results in _propresults.items():
@@ -156,30 +156,30 @@
                     props.append(prop.localName)
                 return 200
             return 403
-        
+
         if not prop.localName in self.avail_props[ns]:
             return 403 # Cannot add propeties to a registered schema
-        
+
         fields = getFields(iface)
         field = fields[prop.localName]
         if field.readonly:
             return 409 # RFC 2518 specifies 409 for readonly props
-        
+
         value = field.get(iface(self.context))
         if value is field.missing_value:
             value = no_value
         setUpWidget(self, prop.localName, field, IDAVWidget,
             value=value, ignoreStickyValues=True)
-        
+
         widget = getattr(self, prop.localName + '_widget')
         widget.setRenderedValue(prop)
 
         if not widget.hasValidInput():
             return 409 # Didn't match the widget validation
-        
+
         if widget.applyChanges(iface(self.context)):
             return 200
-        
+
         return 422 # Field didn't accept the value
 
     def _handleRemove(self, prop):
@@ -193,20 +193,20 @@
                 return 200
             self.oprops.removeProperty(ns, prop.localName)
             return 200
-        
+
         # Registered interfaces
         fields = getFields(iface)
         field = fields[prop.localName]
         if field.readonly:
             return 409 # RFC 2518 specifies 409 for readonly props
-        
+
         if field.required:
             if field.default is None:
                 return 409 # Clearing a required property is a conflict
             # Reset the field to the default if a value is required
             field.set(iface(self.context), field.default)
             return 200
-        
+
         # Reset the field to it's defined missing_value
         field.set(iface(self.context), field.missing_value)
         return 200

Modified: Zope3/trunk/src/zope/app/dav/tests/test_propfind.py
===================================================================
--- Zope3/trunk/src/zope/app/dav/tests/test_propfind.py	2005-09-07 19:34:16 UTC (rev 38356)
+++ Zope3/trunk/src/zope/app/dav/tests/test_propfind.py	2005-09-07 20:14:34 UTC (rev 38357)
@@ -84,7 +84,7 @@
             if _environ.has_key(key.upper()):
                 del _environ[key.upper()]
 
-    request = TestRequest(StringIO(body), StringIO(), _environ)
+    request = TestRequest(StringIO(body), _environ)
     return request
 
 class FileSized(object):
@@ -267,7 +267,7 @@
         # Check HTTP Response
         self.assertEqual(request.response.getStatus(), 207)
         self.assertEqual(pfind.getDepth(), depth)
-        s1 = normalize_xml(request.response._body)
+        s1 = normalize_xml(request.response.consumeBody())
         s2 = normalize_xml(expect)
         self.assertEqual(s1, s2)
 

Modified: Zope3/trunk/src/zope/app/dav/tests/test_proppatch.py
===================================================================
--- Zope3/trunk/src/zope/app/dav/tests/test_proppatch.py	2005-09-07 19:34:16 UTC (rev 38356)
+++ Zope3/trunk/src/zope/app/dav/tests/test_proppatch.py	2005-09-07 20:14:34 UTC (rev 38357)
@@ -28,7 +28,7 @@
 from zope.publisher.http import status_reasons
 from zope.pagetemplate.tests.util import normalize_xml
 from ZODB.tests.util import DB
-	
+
 from zope.app import zapi
 from zope.app.testing import ztapi
 
@@ -70,7 +70,7 @@
                 ''.join(remove))
         for prefix, ns in namespaces:
             nsAttrs += ' xmlns:%s="%s"' % (prefix, ns)
-            
+
         body = '''<?xml version="1.0" encoding="utf-8"?>
 
         <propertyupdate xmlns="DAV:"%s>
@@ -90,7 +90,7 @@
             if _environ.has_key(key.upper()):
                 del _environ[key.upper()]
 
-    request = TestRequest(StringIO(body), StringIO(), _environ)
+    request = TestRequest(StringIO(body), _environ)
     return request
 
 
@@ -108,7 +108,7 @@
     implements(ITestSchema)
     __used_for__ = IAnnotatable
     annotations = None
-    
+
     def __init__(self, context):
         annotations = IAnnotations(context)
         data = annotations.get(TestKey)
@@ -119,7 +119,7 @@
                      u'unusualMissingValue': (EmptyTestValue,),
                      u'constrained': (EmptyTestValue,)}
         self._mapping = data
-        
+
     def _changed(self):
         if self.annotations is not None:
             self.annotations[TestKey] = self._mapping
@@ -171,7 +171,7 @@
         root = self.conn.root()
         root['Application'] = self.rootFolder
         transaction.commit()
-        
+
     def tearDown(self):
         self.db.close()
 
@@ -222,7 +222,7 @@
         # Check HTTP Response
         self.assertEqual(request.response.getStatus(), 207)
         self.assertEqual(ppatch.content_type, 'text/xml')
-        
+
     def test_noupdates(self):
         file = self.file
         request = _createRequest(namespaces=(), set=(), remove=())
@@ -230,9 +230,9 @@
         ppatch.PROPPATCH()
         # Check HTTP Response
         self.assertEqual(request.response.getStatus(), 422)
-        
+
     def _checkProppatch(self, obj, ns=(), set=(), rm=(), extra='', expect=''):
-        request = _createRequest(namespaces=ns, set=set, remove=rm, 
+        request = _createRequest(namespaces=ns, set=set, remove=rm,
                                  extra=extra)
         resource_url = zapi.absoluteURL(obj, request)
         expect = '''<?xml version="1.0" encoding="utf-8"?>
@@ -246,10 +246,10 @@
         ppatch.PROPPATCH()
         # Check HTTP Response
         self.assertEqual(request.response.getStatus(), 207)
-        s1 = normalize_xml(request.response._body)
+        s1 = normalize_xml(request.response.consumeBody())
         s2 = normalize_xml(expect)
         self.assertEqual(s1, s2)
-        
+
     def _makePropstat(self, ns, properties, status=200):
         nsattrs = ''
         count = 0
@@ -268,37 +268,37 @@
         namespacesA.sort()
         namespacesB = expect.keys()
         namespacesB.sort()
-        self.assertEqual(namespacesA, namespacesB, 
+        self.assertEqual(namespacesA, namespacesB,
                          'available opaque namespaces were %s, '
                          'expected %s' % (namespacesA, namespacesB))
-        
+
         for ns in namespacesA:
             propnamesA = list(oprops[ns].keys())
             propnamesA.sort()
             propnamesB = expect[ns].keys()
             propnamesB.sort()
-            self.assertEqual(propnamesA, propnamesB, 
+            self.assertEqual(propnamesA, propnamesB,
                              'props for opaque namespaces %s were %s, '
                              'expected %s' % (ns, propnamesA, propnamesB))
             for prop in propnamesA:
                 valueA = oprops[ns][prop]
                 valueB = expect[ns][prop]
-                self.assertEqual(valueA, valueB, 
+                self.assertEqual(valueA, valueB,
                                  'opaque prop %s:%s was %s, '
                                  'expected %s' % (ns, prop, valueA, valueB))
-        
+
     def test_removenonexisting(self):
         expect = self._makePropstat(('uri://foo',), '<bar xmlns="a0"/>')
         self._checkProppatch(self.zpt, ns=(('foo', 'uri://foo'),),
             rm=('<foo:bar />'), expect=expect)
-        
+
     def test_opaque_set_simple(self):
         expect = self._makePropstat(('uri://foo',), '<bar xmlns="a0"/>')
         self._checkProppatch(self.zpt, ns=(('foo', 'uri://foo'),),
             set=('<foo:bar>spam</foo:bar>'), expect=expect)
-        self._assertOPropsEqual(self.zpt, 
+        self._assertOPropsEqual(self.zpt,
                                 {u'uri://foo': {u'bar': '<bar>spam</bar>'}})
-        
+
     def test_opaque_remove_simple(self):
         oprops = IDAVOpaqueNamespaces(self.zpt)
         oprops['uri://foo'] = {'bar': '<bar>eggs</bar>'}
@@ -306,26 +306,26 @@
         self._checkProppatch(self.zpt, ns=(('foo', 'uri://foo'),),
             rm=('<foo:bar>spam</foo:bar>'), expect=expect)
         self._assertOPropsEqual(self.zpt, {})
-        
+
     def test_opaque_add_and_replace(self):
         oprops = IDAVOpaqueNamespaces(self.zpt)
         oprops['uri://foo'] = {'bar': '<bar>eggs</bar>'}
         expect = self._makePropstat(
-            ('uri://castle', 'uri://foo'), 
+            ('uri://castle', 'uri://foo'),
             '<camelot xmlns="a0"/><bar xmlns="a1"/>')
-        self._checkProppatch(self.zpt, 
+        self._checkProppatch(self.zpt,
             ns=(('foo', 'uri://foo'), ('c', 'uri://castle')),
-            set=('<foo:bar>spam</foo:bar>', 
+            set=('<foo:bar>spam</foo:bar>',
                  '<c:camelot place="silly" xmlns:k="uri://knights">'
                  '  <k:roundtable/>'
-                 '</c:camelot>'), 
+                 '</c:camelot>'),
             expect=expect)
         self._assertOPropsEqual(self.zpt, {
             u'uri://foo': {u'bar': '<bar>spam</bar>'},
-            u'uri://castle': {u'camelot': 
+            u'uri://castle': {u'camelot':
                 '<camelot place="silly" xmlns:p0="uri://knights">'
                 '  <p0:roundtable/></camelot>'}})
-        
+
     def test_opaque_set_and_remove(self):
         expect = self._makePropstat(
             ('uri://foo',), '<bar xmlns="a0"/>')
@@ -333,7 +333,7 @@
             set=('<foo:bar>eggs</foo:bar>',), rm=('<foo:bar/>',),
             expect=expect)
         self._assertOPropsEqual(self.zpt, {})
-        
+
     def test_opaque_complex(self):
         # PROPPATCH allows us to set, remove and set the same property, ordered
         expect = self._makePropstat(
@@ -342,45 +342,45 @@
             set=('<foo:bar>spam</foo:bar>',), rm=('<foo:bar/>',),
             extra='<set><prop><foo:bar>spam</foo:bar></prop></set>',
             expect=expect)
-        self._assertOPropsEqual(self.zpt, 
+        self._assertOPropsEqual(self.zpt,
                                 {u'uri://foo': {u'bar': '<bar>spam</bar>'}})
-        
+
     def test_proppatch_failure(self):
         expect = self._makePropstat(
             ('uri://foo',), '<bar xmlns="a0"/>', 424)
         expect += self._makePropstat(
             ('http://www.purl.org/dc/1.1',), '<nonesuch xmlns="a0"/>', 403)
-        self._checkProppatch(self.zpt, 
+        self._checkProppatch(self.zpt,
             ns=(('foo', 'uri://foo'), ('DC', 'http://www.purl.org/dc/1.1')),
             set=('<foo:bar>spam</foo:bar>', '<DC:nonesuch>Test</DC:nonesuch>'),
             expect=expect)
         self._assertOPropsEqual(self.zpt, {})
-        
+
     def test_nonexistent_dc(self):
         expect = self._makePropstat(
             ('http://www.purl.org/dc/1.1',), '<nonesuch xmlns="a0"/>', 403)
-        self._checkProppatch(self.zpt, 
+        self._checkProppatch(self.zpt,
             ns=(('DC', 'http://www.purl.org/dc/1.1'),),
             set=('<DC:nonesuch>Test</DC:nonesuch>',), expect=expect)
-        
+
     def test_set_readonly(self):
         expect = self._makePropstat((), '<getcontentlength/>', 409)
-        self._checkProppatch(self.zpt, 
+        self._checkProppatch(self.zpt,
             set=('<getcontentlength>Test</getcontentlength>',), expect=expect)
-        
+
     def test_remove_readonly(self):
         expect = self._makePropstat((), '<getcontentlength/>', 409)
-        self._checkProppatch(self.zpt, rm=('<getcontentlength/>',), 
+        self._checkProppatch(self.zpt, rm=('<getcontentlength/>',),
                              expect=expect)
 
     def test_remove_required_no_default(self):
         testprops = ITestSchema(self.zpt)
         testprops.requiredNoDefault = u'foo'
         transaction.commit()
-        expect = self._makePropstat((TestURI,), 
+        expect = self._makePropstat((TestURI,),
                                     '<requiredNoDefault xmlns="a0"/>', 409)
-        self._checkProppatch(self.zpt, 
-            ns=(('tst', TestURI),), rm=('<tst:requiredNoDefault/>',), 
+        self._checkProppatch(self.zpt,
+            ns=(('tst', TestURI),), rm=('<tst:requiredNoDefault/>',),
             expect=expect)
         self.assertEqual(ITestSchema(self.zpt).requiredNoDefault, u'foo')
 
@@ -388,10 +388,10 @@
         testprops = ITestSchema(self.zpt)
         testprops.requiredDefault = u'foo'
         transaction.commit()
-        expect = self._makePropstat((TestURI,), 
+        expect = self._makePropstat((TestURI,),
                                     '<requiredDefault xmlns="a0"/>', 200)
-        self._checkProppatch(self.zpt, 
-            ns=(('tst', TestURI),), rm=('<tst:requiredDefault/>',), 
+        self._checkProppatch(self.zpt,
+            ns=(('tst', TestURI),), rm=('<tst:requiredDefault/>',),
             expect=expect)
         self.assertEqual(testprops.requiredDefault, u'Default Value')
 
@@ -399,10 +399,10 @@
         testprops = ITestSchema(self.zpt)
         testprops.unusualMissingValue = u'foo'
         transaction.commit()
-        expect = self._makePropstat((TestURI,), 
+        expect = self._makePropstat((TestURI,),
                                     '<unusualMissingValue xmlns="a0"/>', 200)
-        self._checkProppatch(self.zpt, 
-            ns=(('tst', TestURI),), rm=('<tst:unusualMissingValue/>',), 
+        self._checkProppatch(self.zpt,
+            ns=(('tst', TestURI),), rm=('<tst:unusualMissingValue/>',),
             expect=expect)
         self.assertEqual(testprops.unusualMissingValue, u'Missing Value')
 
@@ -410,11 +410,11 @@
         dc = IZopeDublinCore(self.zpt)
         dc.title = u'Test Title'
         transaction.commit()
-        expect = self._makePropstat(('http://www.purl.org/dc/1.1',), 
+        expect = self._makePropstat(('http://www.purl.org/dc/1.1',),
                                     '<title xmlns="a0"/>', 200)
-        self._checkProppatch(self.zpt, 
-            ns=(('DC', 'http://www.purl.org/dc/1.1'),), 
-            set=('<DC:title>Foo Bar</DC:title>',), 
+        self._checkProppatch(self.zpt,
+            ns=(('DC', 'http://www.purl.org/dc/1.1'),),
+            set=('<DC:title>Foo Bar</DC:title>',),
             expect=expect)
         self.assertEqual(dc.title, u'Foo Bar')
 
@@ -422,11 +422,11 @@
         dc = IZopeDublinCore(self.zpt)
         dc.subjects = (u'Bla', u'Ble', u'Bli')
         transaction.commit()
-        expect = self._makePropstat(('http://www.purl.org/dc/1.1',), 
+        expect = self._makePropstat(('http://www.purl.org/dc/1.1',),
                                     '<subjects xmlns="a0"/>', 200)
-        self._checkProppatch(self.zpt, 
-            ns=(('DC', 'http://www.purl.org/dc/1.1'),), 
-            set=('<DC:subjects>Foo, Bar</DC:subjects>',), 
+        self._checkProppatch(self.zpt,
+            ns=(('DC', 'http://www.purl.org/dc/1.1'),),
+            set=('<DC:subjects>Foo, Bar</DC:subjects>',),
             expect=expect)
         self.assertEqual(dc.subjects, (u'Foo', u'Bar'))
 

Modified: Zope3/trunk/src/zope/app/debug/debug.py
===================================================================
--- Zope3/trunk/src/zope/app/debug/debug.py	2005-09-07 19:34:16 UTC (rev 38356)
+++ Zope3/trunk/src/zope/app/debug/debug.py	2005-09-07 20:14:34 UTC (rev 38357)
@@ -51,7 +51,7 @@
         return self.db.open().root()[ZopePublication.root_name]
 
     def _request(self,
-                 path='/', stdin='', stdout=None, basic=None,
+                 path='/', stdin='', basic=None,
                  environment = None, form=None,
                  request=None, publication=BrowserPublication):
         """Create a request
@@ -59,9 +59,6 @@
 
         env = {}
 
-        if stdout is None:
-            stdout = StringIO()
-
         if type(stdin) is str:
             stdin = StringIO(stdin)
 
@@ -83,9 +80,9 @@
         pub = publication(self.db)
 
         if request is not None:
-            request = request(stdin, stdout, env)
+            request = request(stdin, env)
         else:
-            request = TestRequest(stdin, stdout, env)
+            request = TestRequest(stdin, env)
             setDefaultSkin(request)
         request.setPublication(pub)
         if form:

Modified: Zope3/trunk/src/zope/app/file/browser/file.txt
===================================================================
--- Zope3/trunk/src/zope/app/file/browser/file.txt	2005-09-07 19:34:16 UTC (rev 38356)
+++ Zope3/trunk/src/zope/app/file/browser/file.txt	2005-09-07 20:14:34 UTC (rev 38357)
@@ -32,7 +32,7 @@
   >>> print http(r"""
   ... GET /@@+/action.html?type_name=zope.app.file.File HTTP/1.1
   ... Authorization: Basic mgr:mgrpw
-  ... """)
+  ... """, handle_errors=False)
   HTTP/1.1 303 See Other
   Content-Length: ...
   Location: http://localhost/+/zope.app.file.File=
@@ -171,7 +171,7 @@
   ... """)
   HTTP/1.1 200 Ok
   Content-Length: 0
-  Content-Type: text/plain;charset=utf-8
+  Content-Type: text/plain
   <BLANKLINE>
 
 Since it is a text file, we can edit it directly in a web form.
@@ -268,7 +268,7 @@
   ... """)
   HTTP/1.1 200 Ok
   Content-Length: ...
-  Content-Type: text/plain;charset=utf-8
+  Content-Type: text/plain
   <BLANKLINE>
   This is a sample text file.
   <BLANKLINE>

Modified: Zope3/trunk/src/zope/app/file/browser/ftests.py
===================================================================
--- Zope3/trunk/src/zope/app/file/browser/ftests.py	2005-09-07 19:34:16 UTC (rev 38356)
+++ Zope3/trunk/src/zope/app/file/browser/ftests.py	2005-09-07 20:14:34 UTC (rev 38357)
@@ -26,7 +26,7 @@
 
 class FileTest(BrowserTestCase):
 
-    content = u'File <Data>' 
+    content = u'File <Data>'
 
     def addFile(self):
         file = File(self.content)
@@ -130,7 +130,7 @@
         file = root['file']
         self.assertEqual(file.data, '<h1>A file</h1>')
         self.assertEqual(file.contentType, 'text/plain')
-        
+
     def testIndex(self):
         self.addFile()
         response = self.publish(

Modified: Zope3/trunk/src/zope/app/ftests/doctest.txt
===================================================================
--- Zope3/trunk/src/zope/app/ftests/doctest.txt	2005-09-07 19:34:16 UTC (rev 38356)
+++ Zope3/trunk/src/zope/app/ftests/doctest.txt	2005-09-07 20:14:34 UTC (rev 38357)
@@ -23,19 +23,19 @@
   HTTP/1.1 401 Unauthorized
   Content-Length: ...
   Content-Type: text/html;charset=utf-8
-  Www-Authenticate: basic realm=zope
+  WWW-Authenticate: basic realm=zope
   <BLANKLINE>
   <!DOCTYPE html PUBLIC ...
 
 Here we see that we got:
 
   - A 404 response,
-  - A Www-Authenticate header, and
+  - A WWW-Authenticate header, and
   - An html body with an error message
 
 Note that we used ellipeses to indicate ininteresting details.
 
-Next, we'll access the same page with credentials: 
+Next, we'll access the same page with credentials:
 
   >>> print http(r"""
   ... GET /@@contents.html HTTP/1.1
@@ -48,11 +48,11 @@
   <!DOCTYPE html PUBLIC ...
 
 Important note: you must use the user named "mgr" with a password
-"mgrpw". 
+"mgrpw".
 
 And we get a normal output.
 
-Next we'll try accessing site management. Since we used "/manage", 
+Next we'll try accessing site management. Since we used "/manage",
 we got redirected:
 
   >>> print http(r"""
@@ -125,7 +125,7 @@
   ... Authorization: Basic mgr:mgrpw
   ... Content-Length: 73
   ... Content-Type: application/x-www-form-urlencoded
-  ... 
+  ...
   ... type_name=BrowserAdd__zope.app.folder.folder.Folder&new_value=f1""")
   HTTP/1.1 303 See Other
   Content-Length: ...

Modified: Zope3/trunk/src/zope/app/http/exception/tests/test_methodnotallowed.py
===================================================================
--- Zope3/trunk/src/zope/app/http/exception/tests/test_methodnotallowed.py	2005-09-07 19:34:16 UTC (rev 38356)
+++ Zope3/trunk/src/zope/app/http/exception/tests/test_methodnotallowed.py	2005-09-07 20:14:34 UTC (rev 38357)
@@ -65,8 +65,7 @@
         from zope.publisher.http import HTTPRequest
 
         context = C()
-        request = HTTPRequest(StringIO('PUT /bla/bla HTTP/1.1\n\n'),
-                              StringIO(), {})
+        request = HTTPRequest(StringIO('PUT /bla/bla HTTP/1.1\n\n'), {})
         error = MethodNotAllowed(context, request)
         view = MethodNotAllowedView(error, request)
 

Modified: Zope3/trunk/src/zope/app/http/tests/test_put.py
===================================================================
--- Zope3/trunk/src/zope/app/http/tests/test_put.py	2005-09-07 19:34:16 UTC (rev 38356)
+++ Zope3/trunk/src/zope/app/http/tests/test_put.py	2005-09-07 20:14:34 UTC (rev 38357)
@@ -52,7 +52,7 @@
     def test(self):
         container = Container()
         content = "some content\n for testing"
-        request = TestRequest(StringIO(content), StringIO(),
+        request = TestRequest(StringIO(content),
                               {'CONTENT_TYPE': 'test/foo',
                                'CONTENT_LENGTH': str(len(content)),
                                })
@@ -60,7 +60,7 @@
         put = zope.app.http.put.NullPUT(null, request)
         self.assertEqual(getattr(container, 'spam', None), None)
         self.assertEqual(put.PUT(), '')
-        request.response.setBody('')
+        request.response.setResult('')
         file = container.spam
         self.assertEqual(file.__class__, File)
         self.assertEqual(file.name, 'spam')
@@ -73,7 +73,7 @@
     def test_bad_content_header(self):
         container = Container()
         content = "some content\n for testing"
-        request = TestRequest(StringIO(content), StringIO(),
+        request = TestRequest(StringIO(content),
                               {'CONTENT_TYPE': 'test/foo',
                                'CONTENT_LENGTH': str(len(content)),
                                'HTTP_CONTENT_FOO': 'Bar',
@@ -82,7 +82,7 @@
         put = zope.app.http.put.NullPUT(null, request)
         self.assertEqual(getattr(container, 'spam', None), None)
         self.assertEqual(put.PUT(), '')
-        request.response.setBody('')
+        request.response.setResult('')
 
         # Check HTTP Response
         self.assertEqual(request.response.getStatus(), 501)
@@ -92,26 +92,26 @@
     def test(self):
         file = File("thefile", "text/x", "initial content")
         content = "some content\n for testing"
-        request = TestRequest(StringIO(content), StringIO(),
+        request = TestRequest(StringIO(content),
                               {'CONTENT_TYPE': 'test/foo',
                                'CONTENT_LENGTH': str(len(content)),
                                })
         put = zope.app.http.put.FilePUT(file, request)
         self.assertEqual(put.PUT(), '')
-        request.response.setBody('')
+        request.response.setResult('')
         self.assertEqual(file.data, content)
 
     def test_bad_content_header(self):
         file = File("thefile", "text/x", "initial content")
         content = "some content\n for testing"
-        request = TestRequest(StringIO(content), StringIO(),
+        request = TestRequest(StringIO(content),
                               {'CONTENT_TYPE': 'test/foo',
                                'CONTENT_LENGTH': str(len(content)),
                                'HTTP_CONTENT_FOO': 'Bar',
                                })
         put = zope.app.http.put.FilePUT(file, request)
         self.assertEqual(put.PUT(), '')
-        request.response.setBody('')
+        request.response.setResult('')
         self.assertEqual(file.data, "initial content")
 
         # Check HTTP Response

Modified: Zope3/trunk/src/zope/app/i18n/browser/tests/test_translate.py
===================================================================
--- Zope3/trunk/src/zope/app/i18n/browser/tests/test_translate.py	2005-09-07 19:34:16 UTC (rev 38356)
+++ Zope3/trunk/src/zope/app/i18n/browser/tests/test_translate.py	2005-09-07 20:14:34 UTC (rev 38357)
@@ -38,8 +38,8 @@
     def __init__(self, context, request):
         self.context = context
         self.request = request
-        
 
+
 class TranslateTest(PlacelessSetup, unittest.TestCase):
 
     def setUp(self):
@@ -71,7 +71,7 @@
 
 
     def _getRequest(self, **kw):
-        request = BrowserRequest(StringIO(''), StringIO(), kw)
+        request = BrowserRequest(StringIO(''), kw)
         request._cookies = {'edit_languages': 'en,de'}
         request._traversed_names = ['foo', 'bar']
         return request

Modified: Zope3/trunk/src/zope/app/publication/browser.py
===================================================================
--- Zope3/trunk/src/zope/app/publication/browser.py	2005-09-07 19:34:16 UTC (rev 38356)
+++ Zope3/trunk/src/zope/app/publication/browser.py	2005-09-07 20:14:34 UTC (rev 38357)
@@ -69,7 +69,7 @@
     def afterCall(self, request, ob):
         super(BrowserPublication, self).afterCall(request, ob)
         if request.method == 'HEAD':
-            request.response.setBody('')
+            request.response.setResult('')
 
 # For now, have a factory that returns a singleton
 class PublicationFactory(object):
@@ -130,7 +130,7 @@
 
     Any interfaces that are directly provided by the request coming into this
     method are replaced by the applied layer/skin interface:
-    
+
       >>> request = Request()
       >>> class IFoo(Interface):
       ...     pass

Modified: Zope3/trunk/src/zope/app/publication/httpfactory.py
===================================================================
--- Zope3/trunk/src/zope/app/publication/httpfactory.py	2005-09-07 19:34:16 UTC (rev 38356)
+++ Zope3/trunk/src/zope/app/publication/httpfactory.py	2005-09-07 20:14:34 UTC (rev 38357)
@@ -63,8 +63,21 @@
         self._db = db
         self._publication_cache = {}
 
-    def __call__(self, input_stream, output_steam, env):
+    def __call__(self, input_stream, env, output_stream=None):
         """See `zope.app.publication.interfaces.IPublicationRequestFactory`"""
+        # BBB: This is backward-compatibility support for the deprecated
+        # output stream.
+        try:
+            env.get
+        except AttributeError:
+            import warnings
+            warnings.warn("Can't pass output streams to requests anymore. "
+                          "This will go away in Zope 3.4.",
+                          DeprecationWarning,
+                          2)
+            env, output_stream = output_stream, env
+
+
         method = env.get('REQUEST_METHOD', 'GET').upper()
         request_class, publication_class = chooseClasses(method, env)
 
@@ -73,7 +86,7 @@
             publication = publication_class(self._db)
             self._publication_cache[publication_class] = publication
 
-        request = request_class(input_stream, output_steam, env)
+        request = request_class(input_stream, env)
         request.setPublication(publication)
         if IBrowserRequest.providedBy(request):
             # only browser requests have skins

Modified: Zope3/trunk/src/zope/app/publication/tests/test_browserpublication.py
===================================================================
--- Zope3/trunk/src/zope/app/publication/tests/test_browserpublication.py	2005-09-07 19:34:16 UTC (rev 38356)
+++ Zope3/trunk/src/zope/app/publication/tests/test_browserpublication.py	2005-09-07 20:14:34 UTC (rev 38357)
@@ -256,61 +256,42 @@
             id = 'bob'
 
         # With a normal request, we should get a body:
-        output = StringIO()
-        request = TestRequest(StringIO(''), output, {'PATH_INFO': '/'})
+        request = TestRequest(StringIO(''), {'PATH_INFO': '/'})
         request.setPrincipal(User())
-        request.response.setBody("spam")
+        request.response.setResult(u"spam")
         pub.afterCall(request, None)
-        request.response.outputBody()
-        self.assertEqual(
-            output.getvalue(),
-            'Status: 200 Ok\r\n'
-            'Content-Length: 4\r\n'
-            'Content-Type: text/plain;charset=utf-8\r\n'
-            'X-Content-Type-Warning: guessed from content\r\n'
-            'X-Powered-By: Zope (www.zope.org), Python (www.python.org)\r\n'
-            '\r\nspam'
-            )
+        self.assertEqual(request.response.consumeBody(), 'spam' )
 
         # But with a HEAD request, the body should be empty
-        output = StringIO()
-        request = TestRequest(StringIO(''), output, {'PATH_INFO': '/'})
+        request = TestRequest(StringIO(''), {'PATH_INFO': '/'})
         request.setPrincipal(User())
         request.method = 'HEAD'
-        request.response.setBody("spam")
+        request.response.setResult(u"spam")
         pub.afterCall(request, None)
-        request.response.outputBody()
-        self.assertEqual(
-            output.getvalue(),
-            'Status: 200 Ok\r\n'
-            'Content-Length: 0\r\n'
-            'Content-Type: text/plain;charset=utf-8\r\n'
-            'X-Content-Type-Warning: guessed from content\r\n'
-            'X-Powered-By: Zope (www.zope.org), Python (www.python.org)\r\n'
-            '\r\n'
-            )
+        self.assertEqual(request.response.consumeBody(), '')
 
     def testUnicode_NO_HTTP_CHARSET(self):
         # Test so that a unicode body doesn't cause a UnicodeEncodeError
-        output = StringIO()
-        request = TestRequest(StringIO(''), output, {})
-        request.response.setBody(u"\u0442\u0435\u0441\u0442")
-        request.response.outputBody()
+        request = TestRequest(StringIO(''), {})
+        request.response.setResult(u"\u0442\u0435\u0441\u0442")
+        headers = request.response.getHeaders()
+        headers.sort()
         self.assertEqual(
-            output.getvalue(),
-            'Status: 200 Ok\r\n'
-            'Content-Length: 8\r\n'
-            'Content-Type: text/plain;charset=utf-8\r\n'
-            'X-Content-Type-Warning: guessed from content\r\n'
-            'X-Powered-By: Zope (www.zope.org), Python (www.python.org)\r\n'
-            '\r\n\xd1\x82\xd0\xb5\xd1\x81\xd1\x82')
+            headers,
+            [('Content-Length', '8'),
+             ('Content-Type', 'text/plain;charset=utf-8'),
+             ('X-Content-Type-Warning', 'guessed from content'),
+             ('X-Powered-By', 'Zope (www.zope.org), Python (www.python.org)')])
+        self.assertEqual(
+            request.response.consumeBody(),
+            '\xd1\x82\xd0\xb5\xd1\x81\xd1\x82')
 
 
 class HTTPPublicationRequestFactoryTests(BasePublicationTests):
 
     def testGetBackSamePublication(self):
         factory = HTTPPublicationRequestFactory(db=None)
-        args = (None, None, {})
+        args = (None, {})
         self.assert_(id(factory(*args).publication) ==
                      id(factory(*args).publication))
 

Modified: Zope3/trunk/src/zope/app/publication/tests/test_http.py
===================================================================
--- Zope3/trunk/src/zope/app/publication/tests/test_http.py	2005-09-07 19:34:16 UTC (rev 38356)
+++ Zope3/trunk/src/zope/app/publication/tests/test_http.py	2005-09-07 20:14:34 UTC (rev 38357)
@@ -47,7 +47,7 @@
 
     def test_callObject(self):
         pub = zope.app.publication.http.HTTPPublication(None)
-        request = HTTPRequest(StringIO(''), StringIO(), {})
+        request = HTTPRequest(StringIO(''), {})
         request.method = 'SPAM'
 
         ztapi.provideView(I, IHTTPRequest, Interface, 'SPAM', V)

Modified: Zope3/trunk/src/zope/app/publication/tests/test_httpfactory.py
===================================================================
--- Zope3/trunk/src/zope/app/publication/tests/test_httpfactory.py	2005-09-07 19:34:16 UTC (rev 38356)
+++ Zope3/trunk/src/zope/app/publication/tests/test_httpfactory.py	2005-09-07 20:14:34 UTC (rev 38357)
@@ -33,9 +33,8 @@
 from zope.app.publication import interfaces
 
 class DummyRequestFactory(object):
-    def __call__(self, input_stream, output_steam, env):
+    def __call__(self, input_stream, env):
         self.input_stream = input_stream
-        self.output_steam = output_steam
         self.env = env
         return self
 
@@ -79,34 +78,32 @@
         env['REQUEST_METHOD'] = 'POST'
         env['CONTENT_TYPE'] = 'text/xml'
         input = StringIO('')
-        output = StringIO()
         env['HTTP_SOAPACTION'] = 'foo'
-        self.assertEqual(httpfactory(input, output, env), soaprequestfactory)
+        self.assertEqual(httpfactory(input, env), soaprequestfactory)
         del env['HTTP_SOAPACTION']
-        self.assertEqual(httpfactory(input, output, env), xmlrpcrequestfactory)
+        self.assertEqual(httpfactory(input, env), xmlrpcrequestfactory)
         env['CONTENT_TYPE'] = 'text/foo'
         self.assertEqual(
-            httpfactory(input, output, env), browserrequestfactory)
+            httpfactory(input, env), browserrequestfactory)
         env['REQUEST_METHOD'] = 'FLOO'
-        self.assertEqual(httpfactory(input, output, env), httprequestfactory)
+        self.assertEqual(httpfactory(input, env), httprequestfactory)
 
     def test_browser(self):
-        r = self.__factory(StringIO(''), StringIO(), self.__env)
+        r = self.__factory(StringIO(''), self.__env)
         self.assertEqual(r.__class__, BrowserRequest)
         self.assertEqual(r.publication.__class__, BrowserPublication)
 
         for method in ('GET', 'HEAD', 'POST', 'get', 'head', 'post'):
             self.__env['REQUEST_METHOD'] = method
-            r = self.__factory(StringIO(''), StringIO(), self.__env)
+            r = self.__factory(StringIO(''), self.__env)
             self.assertEqual(r.__class__, BrowserRequest)
             self.assertEqual(r.publication.__class__, BrowserPublication)
-            
 
     def test_http(self):
 
         for method in ('PUT', 'put', 'ZZZ'):
             self.__env['REQUEST_METHOD'] = method
-            r = self.__factory(StringIO(''), StringIO(), self.__env)
+            r = self.__factory(StringIO(''), self.__env)
             self.assertEqual(r.__class__, HTTPRequest)
             self.assertEqual(r.publication.__class__, HTTPPublication)
 
@@ -114,26 +111,23 @@
         self.__env['CONTENT_TYPE'] = 'text/xml'
         for method in ('POST', 'post'):
             self.__env['REQUEST_METHOD'] = method
-            r = self.__factory(StringIO(''), StringIO(), self.__env)
+            r = self.__factory(StringIO(''), self.__env)
             self.assertEqual(r.__class__, XMLRPCRequest)
             self.assertEqual(r.publication.__class__, XMLRPCPublication)
 
-
         # content type doesn't matter for non post
         for method in ('GET', 'HEAD', 'get', 'head'):
             self.__env['REQUEST_METHOD'] = method
-            r = self.__factory(StringIO(''), StringIO(), self.__env)
+            r = self.__factory(StringIO(''), self.__env)
             self.assertEqual(r.__class__, BrowserRequest)
             self.assertEqual(r.publication.__class__, BrowserPublication)
 
         for method in ('PUT', 'put', 'ZZZ'):
             self.__env['REQUEST_METHOD'] = method
-            r = self.__factory(StringIO(''), StringIO(), self.__env)
+            r = self.__factory(StringIO(''), self.__env)
             self.assertEqual(r.__class__, HTTPRequest)
             self.assertEqual(r.publication.__class__, HTTPPublication)
 
-    
-        
 
 def test_suite():
     return TestSuite((

Modified: Zope3/trunk/src/zope/app/publication/tests/test_zopepublication.py
===================================================================
--- Zope3/trunk/src/zope/app/publication/tests/test_zopepublication.py	2005-09-07 19:34:16 UTC (rev 38356)
+++ Zope3/trunk/src/zope/app/publication/tests/test_zopepublication.py	2005-09-07 20:14:34 UTC (rev 38357)
@@ -31,7 +31,6 @@
 from zope.publisher.base import TestPublication, TestRequest
 from zope.publisher.http import IHTTPRequest, HTTPCharsets
 from zope.publisher.interfaces import IRequest, IPublishTraverse
-from zope.publisher.browser import BrowserResponse
 from zope.security import simplepolicies
 from zope.security.management import setSecurityPolicy, queryInteraction
 from zope.security.management import endInteraction
@@ -131,8 +130,7 @@
         ztapi.provideNamespaceHandler('resource', resource)
         ztapi.provideNamespaceHandler('etc', etc)
 
-        self.out = StringIO()
-        self.request = TestRequest('/f1/f2', outstream=self.out)
+        self.request = TestRequest('/f1/f2')
         self.user = Principal('test.principal')
         self.request.setPrincipal(self.user)
         from zope.interface import Interface
@@ -169,8 +167,7 @@
             pass
         self.publication.handleException(
             self.object, self.request, sys.exc_info(), retry_allowed=False)
-        self.request.response.outputBody()
-        value = self.out.getvalue().split()
+        value = ''.join(self.request.response._result).split()
         self.assertEqual(' '.join(value[:6]),
                          'Traceback (most recent call last): File')
         self.assertEqual(' '.join(value[-8:]),
@@ -194,8 +191,7 @@
             pass
         self.publication.handleException(
             self.object, self.request, sys.exc_info(), retry_allowed=False)
-        self.request.response.outputBody()
-        self.assertEqual(self.out.getvalue(), view_text)
+        self.assertEqual(self.request.response._result, view_text)
 
     def testHandlingSystemErrors(self):
 
@@ -204,7 +200,7 @@
 
         from zope.testing import loggingsupport
         handler = loggingsupport.InstalledHandler('SiteError')
-        
+
         self.testViewOnException()
 
         self.assertEqual(
@@ -216,13 +212,11 @@
         # installed an error reporting utility.  That's OK.
 
         handler.uninstall()
-        self.out.seek(0)
-        self.out.truncate(0)
         handler = loggingsupport.InstalledHandler('SiteError')
 
         # Now, we'll register an exception view that indicates that we
         # have a system error.
-        
+
         from zope.interface import Interface, implements
         class E2(Exception):
             pass
@@ -240,10 +234,10 @@
 
             def isSystemError(self):
                 return True
-            
+
             def __call__(self):
                 return view_text
-        
+
         ztapi.provideView(E2, self.presentation_type, Interface,
                           'name', MyView)
         try:
@@ -251,7 +245,6 @@
         except:
             self.publication.handleException(
                 self.object, self.request, sys.exc_info(), retry_allowed=False)
-        self.request.response.outputBody()
 
         # Now, since the view was a system error view, we should have
         # a log entry for the E2 error (as well as the missing
@@ -263,7 +256,7 @@
             'SiteError ERROR\n'
             '  http://test.url'
             )
-    
+
         handler.uninstall()
 
     def testNoViewOnClassicClassException(self):
@@ -284,11 +277,10 @@
             pass
         self.publication.handleException(
             self.object, self.request, sys.exc_info(), retry_allowed=False)
-        self.request.response.outputBody()
         # check we don't get the view we registered
-        self.failIf(self.out.getvalue() == view_text)
+        self.failIf(''.join(self.request.response._result) == view_text)
         # check we do actually get something
-        self.failIf(self.out.getvalue() == '')
+        self.failIf(''.join(self.request.response._result) == '')
 
     def testExceptionSideEffects(self):
         from zope.publisher.interfaces import IExceptionSideEffects
@@ -327,21 +319,20 @@
         self.assertEqual(self.request, adapter.request)
 
     def testExceptionResetsResponse(self):
-        self.request._response = BrowserResponse(
-            self.request.response._outstream)
-        self.request.response.setHeader('Content-Type', 'application/pdf')
-        self.request.response.setCookie('spam', 'eggs')
+        from zope.publisher.browser import TestRequest
+        request = TestRequest()
+        request.response.setHeader('Content-Type', 'application/pdf')
+        request.response.setCookie('spam', 'eggs')
         from ZODB.POSException import ConflictError
         try:
             raise ConflictError
         except:
             pass
         self.publication.handleException(
-            self.object, self.request, sys.exc_info(), retry_allowed=False)
-        self.request.response.outputBody()
-        self.assertEqual(self.request.response.getHeader('Content-Type'),
+            self.object, request, sys.exc_info(), retry_allowed=False)
+        self.assertEqual(request.response.getHeader('Content-Type'),
                          'text/html;charset=utf-8')
-        self.assertEqual(self.request.response._cookies, {})
+        self.assertEqual(request.response._cookies, {})
 
     def testAbortOrCommitTransaction(self):
         txn = transaction.get()
@@ -352,7 +343,7 @@
         self.publication.handleException(
             self.object, self.request, sys.exc_info(), retry_allowed=False)
         # assert that we get a new transaction
-        self.assert_(txn is not transaction.get())    
+        self.assert_(txn is not transaction.get())
 
     def testAbortTransactionWithErrorReportingUtility(self):
         # provide our fake error reporting utility
@@ -397,23 +388,22 @@
         f1['f2'] = Folder()
         sm1 = setup.createSiteManager(f1)
         setup.addUtility(sm1, '', IAuthenticationUtility, AuthUtility1())
-        
+
         f2 = f1['f2']
         sm2 = setup.createSiteManager(f2)
         setup.addUtility(sm2, '', IAuthenticationUtility, AuthUtility2())
         transaction.commit()
-        
-        
+
         from zope.app.container.interfaces import ISimpleReadContainer
         from zope.app.container.traversal import ContainerTraverser
-        
+
         ztapi.provideView(ISimpleReadContainer, IRequest, IPublishTraverse,
                           '', ContainerTraverser)
-        
+
         from zope.app.folder.interfaces import IFolder
         from zope.security.checker import defineChecker, InterfaceChecker
         defineChecker(Folder, InterfaceChecker(IFolder))
-        
+
         self.publication.beforeTraversal(self.request)
         self.assertEqual(list(queryInteraction().participations),
                          [self.request])
@@ -430,7 +420,7 @@
         self.assertEqual(list(queryInteraction().participations),
                          [self.request])
         self.publication.endRequest(self.request, ob)
-        self.assertEqual(queryInteraction(), None)        
+        self.assertEqual(queryInteraction(), None)
 
     def testTransactionCommitAfterCall(self):
         root = self.db.open().root()

Modified: Zope3/trunk/src/zope/app/publication/zopepublication.py
===================================================================
--- Zope3/trunk/src/zope/app/publication/zopepublication.py	2005-09-07 19:34:16 UTC (rev 38356)
+++ Zope3/trunk/src/zope/app/publication/zopepublication.py	2005-09-07 20:14:34 UTC (rev 38357)
@@ -320,7 +320,7 @@
                     # arguments.  The positional arguments were meant
                     # for the published object, not an exception view.
                     body = mapply(view, (), request)
-                    response.setBody(body)
+                    response.setResult(body)
                     transaction.commit()
                     if (ISystemErrorView.providedBy(view)
                         and view.isSystemError()):

Modified: Zope3/trunk/src/zope/app/publisher/xmlrpc/README.txt
===================================================================
--- Zope3/trunk/src/zope/app/publisher/xmlrpc/README.txt	2005-09-07 19:34:16 UTC (rev 38356)
+++ Zope3/trunk/src/zope/app/publisher/xmlrpc/README.txt	2005-09-07 20:14:34 UTC (rev 38357)
@@ -19,11 +19,11 @@
 
   >>> from zope.configuration import xmlconfig
   >>> ignored = xmlconfig.string("""
-  ... <configure 
+  ... <configure
   ...     xmlns="http://namespaces.zope.org/zope"
   ...     xmlns:xmlrpc="http://namespaces.zope.org/xmlrpc"
   ...     >
-  ...   <!-- We only need to do this include in this example, 
+  ...   <!-- We only need to do this include in this example,
   ...        Normally the include has already been done for us. -->
   ...   <include package="zope.app.publisher.xmlrpc" file="meta.zcml" />
   ...
@@ -43,7 +43,7 @@
   ... Authorization: Basic bWdyOm1ncnB3
   ... Content-Length: 73
   ... Content-Type: application/x-www-form-urlencoded
-  ... 
+  ...
   ... type_name=BrowserAdd__zope.app.folder.folder.Folder&new_value=f1""")
   HTTP/1.1 303 See Other
   ...
@@ -53,7 +53,7 @@
   ... Authorization: Basic bWdyOm1ncnB3
   ... Content-Length: 73
   ... Content-Type: application/x-www-form-urlencoded
-  ... 
+  ...
   ... type_name=BrowserAdd__zope.app.folder.folder.Folder&new_value=f2""")
   HTTP/1.1 303 See Other
   ...
@@ -65,7 +65,7 @@
   ... Authorization: Basic bWdyOm1ncnB3
   ... Content-Length: 102
   ... Content-Type: text/xml
-  ... 
+  ...
   ... <?xml version='1.0'?>
   ... <methodCall>
   ... <methodName>contents</methodName>
@@ -98,7 +98,7 @@
   ... POST / HTTP/1.0
   ... Content-Length: 102
   ... Content-Type: text/xml
-  ... 
+  ...
   ... <?xml version='1.0'?>
   ... <methodCall>
   ... <methodName>contents</methodName>
@@ -109,7 +109,7 @@
   HTTP/1.0 401 Unauthorized
   Content-Length: 126
   Content-Type: text/xml;charset=utf-8
-  Www-Authenticate: basic realm='Zope'
+  WWW-Authenticate: basic realm='Zope'
   <BLANKLINE>
   <?xml version='1.0'?>
   <methodResponse>
@@ -141,7 +141,7 @@
 
 The 'zope.app.publisher.xmlrpc' package provides a base class,
 `MethodPublisher`,  that provides the necessary traversal support.  In
-particulat, it has an adapter that simply traverses to attributes. 
+particulat, it has an adapter that simply traverses to attributes.
 
 If an XML-RPC view isn't going to be public, then it also has to
 implement 'zope.app.location.ILocation' so that security grants can be
@@ -162,11 +162,11 @@
 as as a named view:
 
   >>> ignored = xmlconfig.string("""
-  ... <configure 
+  ... <configure
   ...     xmlns="http://namespaces.zope.org/zope"
   ...     xmlns:xmlrpc="http://namespaces.zope.org/xmlrpc"
   ...     >
-  ...   <!-- We only need to do this include in this example, 
+  ...   <!-- We only need to do this include in this example,
   ...        Normally the include has already been done for us. -->
   ...   <include package="zope.app.publisher.xmlrpc" file="meta.zcml" />
   ...
@@ -187,7 +187,7 @@
   ... Authorization: Basic bWdyOm1ncnB3
   ... Content-Length: 102
   ... Content-Type: text/xml
-  ... 
+  ...
   ... <?xml version='1.0'?>
   ... <methodCall>
   ... <methodName>contents</methodName>
@@ -218,7 +218,7 @@
   ... POST /listing/ HTTP/1.0
   ... Content-Length: 102
   ... Content-Type: text/xml
-  ... 
+  ...
   ... <?xml version='1.0'?>
   ... <methodCall>
   ... <methodName>contents</methodName>
@@ -229,7 +229,7 @@
   HTTP/1.0 401 Unauthorized
   Content-Length: 126
   Content-Type: text/xml;charset=utf-8
-  Www-Authenticate: basic realm='Zope'
+  WWW-Authenticate: basic realm='Zope'
   <BLANKLINE>
   <?xml version='1.0'?>
   <methodResponse>
@@ -258,11 +258,11 @@
 
   >>> from zope.configuration import xmlconfig
   >>> ignored = xmlconfig.string("""
-  ... <configure 
+  ... <configure
   ...     xmlns="http://namespaces.zope.org/zope"
   ...     xmlns:xmlrpc="http://namespaces.zope.org/xmlrpc"
   ...     >
-  ...   <!-- We only need to do this include in this example, 
+  ...   <!-- We only need to do this include in this example,
   ...        Normally the include has already been done for us. -->
   ...   <include package="zope.app.publisher.xmlrpc" file="meta.zcml" />
   ...
@@ -283,7 +283,7 @@
   ... Authorization: Basic bWdyOm1ncnB3
   ... Content-Length: 159
   ... Content-Type: text/xml
-  ... 
+  ...
   ... <?xml version='1.0'?>
   ... <methodCall>
   ... <methodName>add</methodName>
@@ -327,11 +327,11 @@
 
   >>> from zope.configuration import xmlconfig
   >>> ignored = xmlconfig.string("""
-  ... <configure 
+  ... <configure
   ...     xmlns="http://namespaces.zope.org/zope"
   ...     xmlns:xmlrpc="http://namespaces.zope.org/xmlrpc"
   ...     >
-  ...   <!-- We only need to do this include in this example, 
+  ...   <!-- We only need to do this include in this example,
   ...        Normally the include has already been done for us. -->
   ...   <include package="zope.app.publisher.xmlrpc" file="meta.zcml" />
   ...
@@ -351,7 +351,7 @@
   ... Authorization: Basic bWdyOm1ncnB3
   ... Content-Length: 104
   ... Content-Type: text/xml
-  ... 
+  ...
   ... <?xml version='1.0'?>
   ... <methodCall>
   ... <methodName>your_fault</methodName>
@@ -386,7 +386,7 @@
 Unfortunately, `xmlrpclib` does not support Python 2.3's new
 `datetime.datetime` class (it should be made to, really).  DateTime
 values need to be encoded as `xmlrpclib.DateTime` instances:
-  
+
   >>> import xmlrpclib
 
   >>> class DateTimeDemo:
@@ -401,11 +401,11 @@
 
   >>> from zope.configuration import xmlconfig
   >>> ignored = xmlconfig.string("""
-  ... <configure 
+  ... <configure
   ...     xmlns="http://namespaces.zope.org/zope"
   ...     xmlns:xmlrpc="http://namespaces.zope.org/xmlrpc"
   ...     >
-  ...   <!-- We only need to do this include in this example, 
+  ...   <!-- We only need to do this include in this example,
   ...        Normally the include has already been done for us. -->
   ...   <include package="zope.app.publisher.xmlrpc" file="meta.zcml" />
   ...
@@ -425,7 +425,7 @@
   ... Authorization: Basic bWdyOm1ncnB3
   ... Content-Length: 100
   ... Content-Type: text/xml
-  ... 
+  ...
   ... <?xml version='1.0'?>
   ... <methodCall>
   ... <methodName>epoch</methodName>

Modified: Zope3/trunk/src/zope/app/recorder/__init__.py
===================================================================
--- Zope3/trunk/src/zope/app/recorder/__init__.py	2005-09-07 19:34:16 UTC (rev 38356)
+++ Zope3/trunk/src/zope/app/recorder/__init__.py	2005-09-07 20:14:34 UTC (rev 38357)
@@ -27,7 +27,7 @@
 from zope.app.publication.httpfactory import HTTPPublicationRequestFactory
 from zope.app.server.servertype import ServerType
 from zope.server.http.commonaccesslogger import CommonAccessLogger
-from zope.server.http.publisherhttpserver import PublisherHTTPServer
+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
@@ -88,7 +88,7 @@
     parser_class = RecordingHTTPRequestParser
 
 
-class RecordingHTTPServer(PublisherHTTPServer):
+class RecordingHTTPServer(WSGIHTTPServer):
     """Zope Publisher-specific HTTP server that can record requests."""
 
     channel_class = RecordingHTTPServerChannel
@@ -99,7 +99,7 @@
 
         Wraps PublisherHTTPServer.executeRequest().
         """
-        PublisherHTTPServer.executeRequest(self, task)
+        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

Modified: Zope3/trunk/src/zope/app/recorder/tests.py
===================================================================
--- Zope3/trunk/src/zope/app/recorder/tests.py	2005-09-07 19:34:16 UTC (rev 38356)
+++ Zope3/trunk/src/zope/app/recorder/tests.py	2005-09-07 20:14:34 UTC (rev 38357)
@@ -27,36 +27,6 @@
 from zope.app.publisher.browser import BrowserView
 
 
-def doctest_RecordingHTTPServer():
-    r"""Unit tests for RecordingHTTPServer.
-
-    We will use stubs instead of real channel and request parser objects, to
-    keep the test fixture small.
-
-        >>> from zope.app.recorder import RecordingHTTPTask
-        >>> channel = ChannelStub()
-        >>> request_data = RequestDataStub()
-        >>> task = RecordingHTTPTask(channel, request_data)
-
-    RecordingHTTPTask is a thin wrapper around HTTPTask.  It records all data
-    written through task.write, plus the response header, of course.
-
-        >>> task.write('request body\n')
-        >>> task.write('goes in here')
-
-    We need to strip CR characters, as they confuse doctest.
-
-        >>> print task.getRawResponse().replace('\r', '')
-        HTTP/1.1 200 Ok
-        Connection: close
-        Server: Stub Server
-        <BLANKLINE>
-        request body
-        goes in here
-
-    """
-
-
 def doctest_RecordingHTTPRequestParser():
     r"""Unit tests for RecordingHTTPRequestParser.
 
@@ -99,10 +69,23 @@
     Further, to keep things simple, we will override the constructor and
     prevent it from listening on sockets.
 
+        >>> def application(environ, start_response):
+        ...     return ()
+
+        >>> from zope.publisher.publish import publish
         >>> from zope.app.recorder import RecordingHTTPServer
         >>> class RecordingHTTPServerForTests(RecordingHTTPServer):
         ...     def __init__(self):
-        ...         self.request_factory = RecorderRequest
+        ...         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 ()
+
         >>> server = RecordingHTTPServerForTests()
 
     We will need a request parser
@@ -140,13 +123,13 @@
         '/'
         >>> print rq.response_string.replace('\r', '')
         HTTP/1.1 599 No status set
-        Content-Length: 0
-        X-Powered-By: Zope (www.zope.org), Python (www.python.org)
+        Connection: close
         Server: Stub Server
+        X-Powered-By: Zope (www.zope.org), Python (www.python.org)
         <BLANKLINE>
         <BLANKLINE>
         >>> rq.status
-        599
+        '599'
         >>> rq.reason
         'No status set'
 

Modified: Zope3/trunk/src/zope/app/server/configure.zcml
===================================================================
--- Zope3/trunk/src/zope/app/server/configure.zcml	2005-09-07 19:34:16 UTC (rev 38356)
+++ Zope3/trunk/src/zope/app/server/configure.zcml	2005-09-07 20:14:34 UTC (rev 38357)
@@ -1,30 +1,38 @@
 <configure xmlns="http://namespaces.zope.org/zope">
 
-  <utility 
+  <utility
       component=".servercontrol.serverControl"
       provides="zope.app.applicationcontrol.interfaces.IServerControl" />
 
   <utility
-      name="HTTP"
-      component=".http.http"
+      name="WSGI-HTTP"
+      component=".wsgi.http"
       provides=".servertype.IServerType"
       />
 
   <utility
-      name="PostmortemDebuggingHTTP"
-      component=".http.pmhttp"
+      name="WSGI-PostmortemDebuggingHTTP"
+      component=".wsgi.pmhttp"
       provides=".servertype.IServerType"
       />
 
   <utility
-      name="WSGI-HTTP"
+      name="FTP"
+      component=".ftp.server"
+      provides=".servertype.IServerType"
+      />
+
+  <!-- BBB: An alias kept around until Zope 3.4 -->
+  <utility
+      name="HTTP"
       component=".wsgi.http"
       provides=".servertype.IServerType"
       />
 
+  <!-- BBB: An alias kept around until Zope 3.4 -->
   <utility
-      name="FTP"
-      component=".ftp.server"
+      name="PostmortemDebuggingHTTP"
+      component=".wsgi.pmhttp"
       provides=".servertype.IServerType"
       />
 

Modified: Zope3/trunk/src/zope/app/server/ftp.py
===================================================================
--- Zope3/trunk/src/zope/app/server/ftp.py	2005-09-07 19:34:16 UTC (rev 38356)
+++ Zope3/trunk/src/zope/app/server/ftp.py	2005-09-07 20:14:34 UTC (rev 38357)
@@ -28,13 +28,12 @@
 
     FTP request factories for a given database create FTP requets with
     publications on the given database:
-        
+
       >>> from ZODB.tests.util import DB
       >>> db = DB()
       >>> factory = FTPRequestFactory(db)
       >>> from cStringIO import StringIO
-      >>> request = factory(StringIO(''), StringIO(),
-      ...                   {'credentials': None, 'path': '/'})
+      >>> request = factory(StringIO(''), {'credentials': None, 'path': '/'})
       >>> request.publication.db is db
       True
       >>> db.close()
@@ -45,8 +44,8 @@
     def __init__(self, db):
         self.publication = FTPPublication(db)
 
-    def __call__(self, input_stream, output_steam, env):
-        request = FTPRequest(input_stream, output_steam, env)
+    def __call__(self, input_stream, env):
+        request = FTPRequest(input_stream, env)
         request.setPublication(self.publication)
         return request
 

Deleted: Zope3/trunk/src/zope/app/server/http.py
===================================================================
--- Zope3/trunk/src/zope/app/server/http.py	2005-09-07 19:34:16 UTC (rev 38356)
+++ Zope3/trunk/src/zope/app/server/http.py	2005-09-07 20:14:34 UTC (rev 38357)
@@ -1,33 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2004 Zope Corporation and Contributors.
-# All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 2.1 (ZPL).  A copy of the ZPL should accompany this distribution.
-# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
-# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
-# FOR A PARTICULAR PURPOSE.
-#
-##############################################################################
-"""HTTP server factories
-
-$Id$
-"""
-
-from zope.app.publication.httpfactory import HTTPPublicationRequestFactory
-from zope.app.server.servertype import ServerType
-from zope.server.http.commonaccesslogger import CommonAccessLogger
-from zope.server.http.publisherhttpserver import PMDBHTTPServer
-from zope.server.http.publisherhttpserver import PublisherHTTPServer
-
-http = ServerType(PublisherHTTPServer,
-                  HTTPPublicationRequestFactory,
-                  CommonAccessLogger,
-                  8080, True)
-
-pmhttp = ServerType(PMDBHTTPServer,
-                    HTTPPublicationRequestFactory,
-                    CommonAccessLogger,
-                    8013, True)

Modified: Zope3/trunk/src/zope/app/server/wsgi.py
===================================================================
--- Zope3/trunk/src/zope/app/server/wsgi.py	2005-09-07 19:34:16 UTC (rev 38356)
+++ Zope3/trunk/src/zope/app/server/wsgi.py	2005-09-07 20:14:34 UTC (rev 38357)
@@ -19,7 +19,7 @@
 
 import zope.interface
 from zope.server.http.commonaccesslogger import CommonAccessLogger
-from zope.server.http.wsgihttpserver import WSGIHTTPServer
+from zope.server.http import wsgihttpserver
 
 from zope.app.wsgi import WSGIPublisherApplication
 
@@ -61,7 +61,12 @@
                       )
 
 
-http = ServerType(WSGIHTTPServer,
+http = ServerType(wsgihttpserver.WSGIHTTPServer,
                   WSGIPublisherApplication,
                   CommonAccessLogger,
                   8080, True)
+
+pmhttp = ServerType(wsgihttpserver.PMDBWSGIHTTPServer,
+                    WSGIPublisherApplication,
+                    CommonAccessLogger,
+                    8013, True)

Modified: Zope3/trunk/src/zope/app/session/http.py
===================================================================
--- Zope3/trunk/src/zope/app/session/http.py	2005-09-07 19:34:16 UTC (rev 38356)
+++ Zope3/trunk/src/zope/app/session/http.py	2005-09-07 20:14:34 UTC (rev 38357)
@@ -90,15 +90,15 @@
         This creates one if necessary:
 
           >>> from zope.publisher.http import HTTPRequest
-          >>> request = HTTPRequest(None, None, {}, None)
+          >>> request = HTTPRequest(None, {}, None)
           >>> bim = CookieClientIdManager()
           >>> id = bim.getClientId(request)
           >>> id == bim.getClientId(request)
           True
 
         The id is retained accross requests:
-        
-          >>> request2 = HTTPRequest(None, None, {}, None)
+
+          >>> request2 = HTTPRequest(None, {}, None)
           >>> request2._cookies = dict(
           ...   [(name, cookie['value'])
           ...    for (name, cookie) in request.response._cookies.items()
@@ -142,13 +142,13 @@
 
     def getRequestId(self, request):
         """Return the browser id encoded in request as a string
-        
+
         Return None if an id is not set.
 
         For example:
 
             >>> from zope.publisher.http import HTTPRequest
-            >>> request = HTTPRequest(None, None, {}, None)
+            >>> request = HTTPRequest(None, {}, None)
             >>> bim = CookieClientIdManager()
 
         Because no cookie has been set, we get no id:
@@ -169,7 +169,7 @@
         When we set the request id, we also set a response cookie.  We
         can simulate getting this cookie back in a subsequent request:
 
-            >>> request2 = HTTPRequest(None, None, {}, None)
+            >>> request2 = HTTPRequest(None, {}, None)
             >>> request2._cookies = dict(
             ...   [(name, cookie['value'])
             ...    for (name, cookie) in request.response._cookies.items()
@@ -210,7 +210,7 @@
         invalid value is silently ignored:
 
             >>> from zope.publisher.http import HTTPRequest
-            >>> request = HTTPRequest(None, None, {}, None)
+            >>> request = HTTPRequest(None, {}, None)
             >>> bim = CookieClientIdManager()
             >>> bim.getRequestId(request)
             >>> bim.setRequestId(request, 'invalid id')
@@ -233,7 +233,7 @@
         Expiry time of 0 means never (well - close enough)
 
             >>> bim.cookieLifetime = 0
-            >>> request = HTTPRequest(None, None, {}, None)
+            >>> request = HTTPRequest(None, {}, None)
             >>> bid = bim.getClientId(request)
             >>> cookie = request.response.getCookie(bim.namespace)
             >>> cookie['expires']
@@ -242,7 +242,7 @@
         A non-zero value means to expire after than number of seconds:
 
             >>> bim.cookieLifetime = 3600
-            >>> request = HTTPRequest(None, None, {}, None)
+            >>> request = HTTPRequest(None, {}, None)
             >>> bid = bim.getClientId(request)
             >>> cookie = request.response.getCookie(bim.namespace)
             >>> import rfc822

Modified: Zope3/trunk/src/zope/app/session/session.py
===================================================================
--- Zope3/trunk/src/zope/app/session/session.py	2005-09-07 19:34:16 UTC (rev 38356)
+++ Zope3/trunk/src/zope/app/session/session.py	2005-09-07 20:14:34 UTC (rev 38357)
@@ -49,7 +49,7 @@
 
 class ClientId(str):
     """See zope.app.interfaces.utilities.session.IClientId
-   
+
         >>> import tests
         >>> request = tests.setUp()
 
@@ -93,7 +93,7 @@
 
         To ensure stale data is removed, we can wind
         back the clock using undocumented means...
-            
+
             >>> sd.lastAccessTime = sd.lastAccessTime - 64
             >>> sdc._v_last_sweep = sdc._v_last_sweep - 4
 
@@ -104,7 +104,7 @@
                 [...]
             KeyError: 'clientid'
 
-        Ensure lastAccessTime on the ISessionData is being updated 
+        Ensure lastAccessTime on the ISessionData is being updated
         occasionally. The ISessionDataContainer maintains this whenever
         the ISessionData is set or retrieved.
 
@@ -182,7 +182,7 @@
 
     def sweep(self):
         """Clean out stale data
-       
+
             >>> sdc = PersistentSessionDataContainer()
             >>> sdc['1'] = SessionData()
             >>> sdc['2'] = SessionData()
@@ -219,8 +219,8 @@
 
 class RAMSessionDataContainer(PersistentSessionDataContainer):
     """A SessionDataContainer that stores data in RAM.
-    
-    Currently session data is not shared between Zope clients, so 
+
+    Currently session data is not shared between Zope clients, so
     server affinity will need to be maintained to use this in a ZEO cluster.
 
         >>> sdc = RAMSessionDataContainer()
@@ -268,11 +268,11 @@
 
     def __getitem__(self, pkg_id):
         """See zope.app.session.interfaces.ISession
-       
+
             >>> import tests
             >>> request = tests.setUp(PersistentSessionDataContainer)
-            >>> request2 = tests.HTTPRequest(None, None, {}, None)
-        
+            >>> request2 = tests.HTTPRequest(None, {}, None)
+
             >>> ISession.providedBy(Session(request))
             True
 

Modified: Zope3/trunk/src/zope/app/session/tests.py
===================================================================
--- Zope3/trunk/src/zope/app/session/tests.py	2005-09-07 19:34:16 UTC (rev 38356)
+++ Zope3/trunk/src/zope/app/session/tests.py	2005-09-07 20:14:34 UTC (rev 38357)
@@ -47,7 +47,7 @@
     sdc = session_data_container_class()
     for product_id in ('', 'products.foo', 'products.bar', 'products.baz'):
         ztapi.provideUtility(ISessionDataContainer, sdc, product_id)
-    request = HTTPRequest(None, None, {}, None)
+    request = HTTPRequest(None, {}, None)
     return request
 
 def tearDown():

Modified: Zope3/trunk/src/zope/app/testing/functional.py
===================================================================
--- Zope3/trunk/src/zope/app/testing/functional.py	2005-09-07 19:34:16 UTC (rev 38356)
+++ Zope3/trunk/src/zope/app/testing/functional.py	2005-09-07 20:14:34 UTC (rev 38357)
@@ -55,30 +55,39 @@
 from zope.publisher.interfaces.browser import IBrowserRequest
 from zope.app.component.hooks import setSite, getSite
 
-HTTPTaskStub = StringIO
 
-
 class ResponseWrapper(object):
     """A wrapper that adds several introspective methods to a response."""
 
-    def __init__(self, response, outstream, path):
+    def __init__(self, response, path, omit=()):
         self._response = response
-        self._outstream = outstream
         self._path = path
+        self.omit = omit
+        self._body = None
 
     def getOutput(self):
         """Returns the full HTTP output (headers + body)"""
-        return self._outstream.getvalue()
+        body = self.getBody()
+        omit = self.omit
+        headers = [x
+                   for x in self._response.getHeaders()
+                   if x[0].lower() not in omit]
+        headers.sort()
+        headers = '\n'.join([("%s: %s" % (n, v)) for (n, v) in headers])
+        statusline = '%s %s' % (self._response._request['SERVER_PROTOCOL'],
+                                self._response.getStatusString())
+        if body:
+            return '%s\n%s\n\n%s' %(statusline, headers, body)
+        else:
+            return '%s\n%s\n' % (statusline, headers)
 
     def getBody(self):
         """Returns the response body"""
-        output = self._outstream.getvalue()
-        idx = output.find('\r\n\r\n')
-        if idx == -1:
-            return None
-        else:
-            return output[idx+4:]
+        if self._body is None:
+            self._body = ''.join(self._response.consumeBody())
 
+        return self._body
+
     def getPath(self):
         """Returns the path of the request"""
         return self._path
@@ -86,7 +95,9 @@
     def __getattr__(self, attr):
         return getattr(self._response, attr)
 
+    __str__ = getOutput
 
+
 class IManagerSetup(zope.interface.Interface):
     """Utility for enabling up a functional testing manager with needed grants
 
@@ -238,8 +249,7 @@
         """Returns the site which is used to look up local components"""
         return getSite()
 
-    def makeRequest(self, path='', basic=None, form=None, env={},
-                    outstream=None):
+    def makeRequest(self, path='', basic=None, form=None, env={}):
         """Creates a new request object.
 
         Arguments:
@@ -251,16 +261,13 @@
                     (You can emulate HTTP request header
                        X-Header: foo
                      by adding 'HTTP_X_HEADER': 'foo' to env)
-          outstream -- a stream where the HTTP response will be written
         """
-        if outstream is None:
-            outstream = HTTPTaskStub()
         environment = {"HTTP_HOST": 'localhost',
                        "HTTP_REFERER": 'localhost',
                        "HTTP_COOKIE": self.httpCookie(path)}
         environment.update(env)
         app = FunctionalTestSetup().getApplication()
-        request = app._request(path, '', outstream,
+        request = app._request(path, '',
                                environment=environment,
                                basic=basic, form=form,
                                request=BrowserRequest)
@@ -281,7 +288,6 @@
           getBody()      -- returns the full response body as a string
           getPath()      -- returns the path used in the request
         """
-        outstream = HTTPTaskStub()
         old_site = self.getSite()
         self.setSite(None)
         # A cookie header has been sent - ensure that future requests
@@ -292,9 +298,8 @@
             self.loadCookies(env['HTTP_COOKIE'])
             del env['HTTP_COOKIE'] # Added again in makeRequest
 
-        request = self.makeRequest(path, basic=basic, form=form, env=env,
-                                   outstream=outstream)
-        response = ResponseWrapper(request.response, outstream, path)
+        request = self.makeRequest(path, basic=basic, form=form, env=env)
+        response = ResponseWrapper(request.response, path)
         if env.has_key('HTTP_COOKIE'):
             self.loadCookies(env['HTTP_COOKIE'])
         publish(request, handle_errors=handle_errors)
@@ -362,7 +367,7 @@
 
                 # Make sure we don't have pending changes
                 abort()
-                
+
                 # The request should always be closed to free resources
                 # held by the request
                 if request:
@@ -376,7 +381,7 @@
     """Functional test case for HTTP requests."""
 
     def makeRequest(self, path='', basic=None, form=None, env={},
-                    instream=None, outstream=None):
+                    instream=None):
         """Creates a new request object.
 
         Arguments:
@@ -389,17 +394,14 @@
                        X-Header: foo
                      by adding 'HTTP_X_HEADER': 'foo' to env)
           instream  -- a stream from where the HTTP request will be read
-          outstream -- a stream where the HTTP response will be written
         """
-        if outstream is None:
-            outstream = HTTPTaskStub()
         if instream is None:
             instream = ''
         environment = {"HTTP_HOST": 'localhost',
                        "HTTP_REFERER": 'localhost'}
         environment.update(env)
         app = FunctionalTestSetup().getApplication()
-        request = app._request(path, instream, outstream,
+        request = app._request(path, instream,
                                environment=environment,
                                basic=basic, form=form,
                                request=HTTPRequest, publication=HTTPPublication)
@@ -419,67 +421,13 @@
           getBody()      -- returns the full response body as a string
           getPath()      -- returns the path used in the request
         """
-        outstream = HTTPTaskStub()
         request = self.makeRequest(path, basic=basic, form=form, env=env,
-                                   instream=request_body, outstream=outstream)
-        response = ResponseWrapper(request.response, outstream, path)
+                                   instream=request_body)
+        response = ResponseWrapper(request.response, path)
         publish(request, handle_errors=handle_errors)
         return response
 
 
-class HTTPHeaderOutput:
-
-    interface.implements(zope.publisher.interfaces.http.IHeaderOutput)
-
-    def __init__(self, protocol, omit):
-        self.headers = {}
-        self.headersl = []
-        self.protocol = protocol
-        self.omit = omit
-
-    def setResponseStatus(self, status, reason):
-        self.status, self.reason = status, reason
-
-    def setResponseHeaders(self, mapping):
-        self.headers.update(dict(
-            [('-'.join([s.capitalize() for s in name.split('-')]), v)
-             for name, v in mapping.items()
-             if name.lower() not in self.omit]
-        ))
-
-    def appendResponseHeaders(self, lst):
-        headers = [split_header(header) for header in lst]
-        self.headersl.extend(
-            [('-'.join([s.capitalize() for s in name.split('-')]), v)
-             for name, v in headers
-             if name.lower() not in self.omit]
-        )
-
-    def __str__(self):
-        out = ["%s: %s" % header for header in self.headers.items()]
-        out.extend(["%s: %s" % header for header in self.headersl])
-        out.sort()
-        out.insert(0, "%s %s %s" % (self.protocol, self.status, self.reason))
-        return '\n'.join(out)
-
-class DocResponseWrapper(ResponseWrapper):
-    """Response Wrapper for use in doc tests
-    """
-
-    def __init__(self, response, outstream, path, header_output):
-        ResponseWrapper.__init__(self, response, outstream, path)
-        self.header_output = header_output
-
-    def __str__(self):
-        body = self.getOutput()
-        if body:
-            return "%s\n\n%s" % (self.header_output, body)
-        return "%s\n" % (self.header_output)
-
-    def getBody(self):
-        return self.getOutput()
-
-
 headerre = re.compile(r'(\S+): (.+)$')
 def split_header(header):
     return headerre.match(header).group(1, 2)
@@ -525,7 +473,7 @@
     def testLinks(self):
         response = self.publish('/')
         self.assertEquals(response.getStatus(), 200)
-        self.checkForBrokenLinks(response.getBody(), response.getPath())
+        self.checkForBrokenLinks(response.consumeBody(), response.getPath())
 
 
 def sample_test_suite():
@@ -571,8 +519,6 @@
         if environment.has_key(auth_key):
             environment[auth_key] = auth_header(environment[auth_key])
 
-        outstream = HTTPTaskStub()
-
         old_site = getSite()
         setSite(None)
 
@@ -581,7 +527,7 @@
         app = FunctionalTestSetup().getApplication()
 
         request = app._request(
-            path, instream, outstream,
+            path, instream,
             environment=environment,
             request=request_cls, publication=publication_cls)
         if IBrowserRequest.providedBy(request):
@@ -593,11 +539,10 @@
                 raise ValueError("only one set of form values can be provided")
             request.form = form
 
-        header_output = HTTPHeaderOutput(
-            protocol, ('x-content-type-warning', 'x-powered-by'))
-        request.response.setHeaderOutput(header_output)
-        response = DocResponseWrapper(
-            request.response, outstream, path, header_output)
+        response = ResponseWrapper(
+            request.response, path,
+            omit=('x-content-type-warning', 'x-powered-by'),
+            )
 
         publish(request, handle_errors=handle_errors)
         self.saveCookies(response)
@@ -612,8 +557,8 @@
         """Choose and return a request class and a publication class"""
         # note that `path` is unused by the default implementation (BBB)
         return chooseClasses(method, environment)
- 
 
+
 def FunctionalDocFileSuite(*paths, **kw):
     globs = kw.setdefault('globs', {})
     globs['http'] = HTTPCaller()

Modified: Zope3/trunk/src/zope/app/testing/tests.py
===================================================================
--- Zope3/trunk/src/zope/app/testing/tests.py	2005-09-07 19:34:16 UTC (rev 38356)
+++ Zope3/trunk/src/zope/app/testing/tests.py	2005-09-07 20:14:34 UTC (rev 38357)
@@ -123,7 +123,7 @@
   <BLANKLINE>
   <BLANKLINE>
 '''
-      
+
 class FunctionalHTTPDocTest(unittest.TestCase):
 
     def test_dochttp(self):
@@ -136,34 +136,6 @@
         self.assert_(got == expected)
 
 
-class DocResponseWrapperTestCase(unittest.TestCase):
-
-    def setUp(self):
-        self.body_output = StringIO.StringIO()
-        self.path = "/foo/bar/"
-        self.response = object()
-
-        self.wrapper = functional.DocResponseWrapper(
-            self.response, self.body_output, self.path, HEADERS)
-
-    def test__str__(self):
-        self.assertEqual(str(self.wrapper),
-                         HEADERS + "\n")
-        self.body_output.write(BODY)
-        self.assertEqual(str(self.wrapper),
-                         "%s\n\n%s" % (HEADERS, BODY))
-
-    def test_getBody(self):
-        self.assertEqual(self.wrapper.getBody(), "")
-        self.body_output.write(BODY)
-        self.assertEqual(self.wrapper.getBody(), BODY)
-
-    def test_getOutput(self):
-        self.assertEqual(self.wrapper.getOutput(), "")
-        self.body_output.write(BODY)
-        self.assertEqual(self.wrapper.getOutput(), BODY)
-
-
 class AuthHeaderTestCase(unittest.TestCase):
 
     def test_auth_encoded(self):
@@ -209,7 +181,6 @@
 def test_suite():
     return unittest.TestSuite((
         unittest.makeSuite(FunctionalHTTPDocTest),
-        unittest.makeSuite(DocResponseWrapperTestCase),
         unittest.makeSuite(AuthHeaderTestCase),
         unittest.makeSuite(HTTPCallerTestCase),
         ))

Modified: Zope3/trunk/src/zope/app/wsgi/README.txt
===================================================================
--- Zope3/trunk/src/zope/app/wsgi/README.txt	2005-09-07 19:34:16 UTC (rev 38356)
+++ Zope3/trunk/src/zope/app/wsgi/README.txt	2005-09-07 20:14:34 UTC (rev 38357)
@@ -3,22 +3,14 @@
 =====================
 
 This package contains an interpretation of the WSGI specification (PEP-0333)
-for the Zope application server by providing a WSGI application object. First,
-we create a stream that will contain the response:
+for the Zope application server by providing a WSGI application object. The
+first step is to initialize the WSGI-compliant Zope application that is called
+from the server. To do that, we first have to create and open a ZODB
+connection:
 
-  >>> import StringIO
-  >>> data = StringIO.StringIO('')
-
-Usually this stream is created by the HTTP server and refers to the output
-stream sent to the server client.
-
-Now that we have our output stream, we need to initialize the WSGI-compliant
-Zope application that is called from the server. To do that, we first have to
-create and open a ZODB connection:
-
   >>> from ZODB.MappingStorage import MappingStorage
   >>> from ZODB.DB import DB
-  
+
   >>> storage = MappingStorage('test.db')
   >>> db = DB(storage, cache_size=4000)
 
@@ -31,44 +23,37 @@
 and the function that initializes the response and returns a function with
 which the output data can be written.
 
-Even though this is commonly done by the server, our first task is to create
-an appropriate environment for the request.
+Even though this is commonly done by the server, we now have to create an
+appropriate environment for the request.
 
+  >>> import cStringIO
   >>> environ = {
   ...     'PATH_INFO': '/',
-  ...     'wsgi.input': StringIO.StringIO('')}
+  ...     'wsgi.input': cStringIO.StringIO('')}
 
 Next we create a WSGI-compliant ``start_response()`` method that accepts the
 status of the response to the HTTP request and the headers that are part of
-the response stream. The headers are expected to be a list of
-2-tuples. However, for the purpose of this demonstration we simply ignore all
-the arguments and push a simple message to the stream. The
-``start_response()`` funtion must also return a ``write()`` callable that
-accepts further data.
+the response stream. The headers are expected to be a list of 2-tuples. The
+``start_response()`` method must also return a ``write()`` function that
+directly writes the output to the server. However, the Zope 3 implementation
+will not utilize this function, since it is strongly discouraged by
+PEP-0333. The second method of getting data to the server is by returning an
+iteratable from the application call. Sp we simply ignore all the arguments
+and return ``None`` as the write method.
 
   >>> def start_response(status, headers):
-  ...     data.write('status and headers.\n\n')
-  ...     return data.write
-  ...
+  ...     return None
 
 Now we can send the fabricated HTTP request to the application for processing:
 
-  >>> app(environ, start_response)
-  ''
-
-We expect the output of this call to be always an empty string, since all the
-output is written to the output stream directly. Looking at the output 
-
-  >>> print data.getvalue()
-  status and headers.
-  <BLANKLINE>
+  >>> print ''.join(app(environ, start_response))
   <html><head><title>Unauthorized</title></head>
   <body><h2>Unauthorized</h2>
   A server error occurred.
   </body></html>
   <BLANKLINE>
 
-we can see that application really crashed and did not know what to do. This
+We can see that application really crashed and did not know what to do. This
 is okay, since we have not setup anything. Getting a request successfully
 processed would require us to bring up a lot of Zope 3's system, which would
 be just a little bit too much for this demonstration.
@@ -108,93 +93,6 @@
   i.e. by calling a method on the server that sets the application.
 
 
-The ``IWSGIOutput`` Component
------------------------------
-
-Under the hood the WSGI support uses a component that implements
-``IWSGIOutput`` that manages the response headers and provides the output
-stream by implementing the ``write()`` method. In the following text the
-functionality of this class is introduced:  
-
-First, we reset our output stream:
-
-  >>> data.__init__('')
-
-Then we initialize an instance of the WSGI output object:
-
-  >>> output = wsgi.WSGIOutput(start_response)
-
-You can set the response status
-
-  >>> output.setResponseStatus("200", "OK")
-  >>> output._statusString
-  '200 OK'
-
-or set arbitrary headers as a mapping:
-
-  >>> output.setResponseHeaders({'a':'b', 'c':'d'})
-
-The headers must be returned as a list of tuples:
-
-  >>> output.getHeaders()
-  [('a', 'b'), ('c', 'd')]
-  
-Calling ``setResponseHeaders()`` again adds new values:
-
-  >>> output.setResponseHeaders({'x':'y', 'c':'d'})
-  >>> h = output.getHeaders()
-  >>> h.sort()
-  >>> h
-  [('a', 'b'), ('c', 'd'), ('x', 'y')]
-
-Headers that can potentially repeat are added using
-``appendResponseHeaders()``:
-
-  >>> output.appendResponseHeaders(['foo: bar'])
-  >>> h = output.getHeaders()
-  >>> h.sort()
-  >>> h    
-  [('a', 'b'), ('c', 'd'), ('foo', ' bar'), ('x', 'y')]
-  >>> output.appendResponseHeaders(['foo: bar'])
-  >>> h = output.getHeaders()
-  >>> h.sort()
-  >>> h    
-  [('a', 'b'), ('c', 'd'), ('foo', ' bar'), ('foo', ' bar'), ('x', 'y')]
-
-Headers containing a colon should also work
-
-  >>> output.appendResponseHeaders(['my: brain:hurts'])
-  >>> h = output.getHeaders()
-  >>> h.sort()
-  >>> h
-  [('a', 'b'), ('c', 'd'), ('foo', ' bar'), ('foo', ' bar'), 
-   ('my', ' brain:hurts'), ('x', 'y')]
-
-The headers should not be written to the output
-
-  >>> output.wroteResponseHeader()
-  False
-  >>> data.getvalue()
-  ''
-
-Let's now write something to the output stream:
-
-  >>> output.write('Now for something')
-
-The headers should be sent and the data written to the stream:
-
-  >>> output.wroteResponseHeader()
-  True
-  >>> data.getvalue()
-  'status and headers.\n\nNow for something'
-
-Calling write again the headers should not be sent again
-
-  >>> output.write(' completly different!')
-  >>> data.getvalue()
-  'status and headers.\n\nNow for something completly different!'
-
-
 About WSGI
 ----------
 

Modified: Zope3/trunk/src/zope/app/wsgi/__init__.py
===================================================================
--- Zope3/trunk/src/zope/app/wsgi/__init__.py	2005-09-07 19:34:16 UTC (rev 38356)
+++ Zope3/trunk/src/zope/app/wsgi/__init__.py	2005-09-07 20:14:34 UTC (rev 38357)
@@ -22,61 +22,7 @@
 from zope.app.publication.httpfactory import HTTPPublicationRequestFactory
 from zope.app.wsgi import interfaces
 
-class WSGIOutput(object):
-    """This class handles the output generated by the publisher. It is used to
-    collect the headers and as an outstream to output the response body.
-    """
-    implements(interfaces.IWSGIOutput)
 
-    def __init__(self, start_response):
-        self._headers = {}
-        self._accumulatedHeaders = []
-        self._statusString = ""
-        self._headersSent = False
-
-        self.wsgi_write = None
-        self.start_response = start_response
-
-    def setResponseStatus(self, status, reason):
-        """See zope.publisher.interfaces.http.IHeaderOutput"""
-        self._statusString = str(status) + ' ' + reason
-
-    def setResponseHeaders(self, mapping):
-        """See zope.publisher.interfaces.http.IHeaderOutput"""
-        self._headers.update(mapping)
-
-    def appendResponseHeaders(self, lst):
-        """See zope.publisher.interfaces.http.IHeaderOutput"""
-        self._accumulatedHeaders.extend(lst)
-
-    def wroteResponseHeader(self):
-        """See zope.publisher.interfaces.http.IHeaderOutput"""
-        return self._headersSent
-
-    def setAuthUserName(self, name):
-        """See zope.publisher.interfaces.http.IHeaderOutput"""
-        pass
-
-    def getHeaders(self):
-        """See zope.app.wsgi.interfaces.IWSGIOutput"""
-        response_headers = self._headers.items()
-
-        accum = [tuple(line.split(':', 1))
-                 for line in self._accumulatedHeaders]
-            
-        response_headers.extend(accum)
-        return response_headers
-
-    def write(self, data):
-        """See zope.app.wsgi.interfaces.IWSGIOutput"""
-        if not self._headersSent:
-            self.wsgi_write = self.start_response(self._statusString,
-                                                  self.getHeaders())
-            self._headersSent = True
-
-        self.wsgi_write(data)
-
-
 class WSGIPublisherApplication(object):
     """A WSGI application implemenation for the zope publisher
 
@@ -85,7 +31,7 @@
     The class relies on a properly initialized request factory.
     """
     implements(interfaces.IWSGIApplication)
-    
+
     def __init__(self, db=None, factory=HTTPPublicationRequestFactory):
         self.requestFactory = None
 
@@ -94,17 +40,16 @@
 
     def __call__(self, environ, start_response):
         """See zope.app.wsgi.interfaces.IWSGIApplication"""
-        # wsgiOutput has two purposes: (1) it is the response headers output
-        # manager and (2) it functions as the output stream for the publisher. 
-        wsgiOutput = WSGIOutput(start_response)
+        request = self.requestFactory(environ['wsgi.input'], environ)
 
-        request = self.requestFactory(
-            environ['wsgi.input'], wsgiOutput, environ)
+        # Let's support post-mortem debugging
+        handle_errors = environ.get('wsgi.handleErrors', True)
 
-        request.response.setHeaderOutput(wsgiOutput)
+        request = publish(request, handle_errors=handle_errors)
+        response = request.response
 
-        publish(request)
+        # Start the WSGI server response
+        start_response(response.getStatusString(), response.getHeaders())
 
-        # since the response is written using the WSGI ``write()`` callable
-        # return an empty iterable (see PEP 333).
-        return ""
+        # Return the result body iterable.
+        return response.consumeBodyIter()

Modified: Zope3/trunk/src/zope/publisher/base.py
===================================================================
--- Zope3/trunk/src/zope/publisher/base.py	2005-09-07 19:34:16 UTC (rev 38356)
+++ Zope3/trunk/src/zope/publisher/base.py	2005-09-07 20:14:34 UTC (rev 38357)
@@ -21,6 +21,7 @@
 import traceback
 from cStringIO import StringIO
 
+from zope.deprecation import deprecation
 from zope.interface import implements, providedBy
 from zope.interface.common.mapping import IReadMapping, IEnumerableMapping
 
@@ -36,52 +37,55 @@
     """
 
     __slots__ = (
-        '_body',      # The response body
-        '_outstream', # The output stream
-        '_request',    # The associated request (if any)
+        '_result',    # The result of the application call
+        '_request',   # The associated request (if any)
         )
 
     implements(IResponse)
 
+    def __init__(self, outstream=None):
+        self._request = None
+        # BBB: This is backward-compatibility support for the deprecated
+        # output stream.
+        if outstream is not None:
+            import warnings
+            warnings.warn("Can't pass output streams to requests anymore. "
+                          "This will go away in Zope 3.4.",
+                          DeprecationWarning,
+                          2)
 
-    def __init__(self, outstream):
-        self._body = ''
-        self._outstream = outstream
-
-    def outputBody(self):
+    def setResult(self, result):
         'See IPublisherResponse'
-        self._outstream.write(self._getBody())
+        self._result = result
 
-    def setBody(self, body):
-        'See IPublisherResponse'
-        self._body = body
-
-    # This method is not part of this interface
-    def _getBody(self):
-        'Returns a string representing the currently set body.'
-        return self._body
-
-    def reset(self):
-        'See IPublisherResponse'
-        self._body = ""
-
     def handleException(self, exc_info):
         'See IPublisherResponse'
+        f = StringIO()
         traceback.print_exception(
-            exc_info[0], exc_info[1], exc_info[2], 100, self)
+            exc_info[0], exc_info[1], exc_info[2], 100, f)
+        self.setResult(f.getvalue())
 
     def internalError(self):
         'See IPublisherResponse'
         pass
 
+    def reset(self):
+        'See IPublisherResponse'
+        pass
+
     def retry(self):
         'See IPublisherResponse'
-        return self.__class__(self.outstream)
+        return self.__class__()
 
-    def write(self, string):
-        'See IApplicationResponse'
-        self._body += string
+    # BBB: Backward-compatibility for old body API
+    def setBody(self, body):
+        return self.setResult(body)
+    setBody = deprecation.deprecated(
+        setBody,
+        'setBody() has been deprecated in favor of setResult(). '
+        'This will go away in Zope 3.4.')
 
+
 class RequestDataGetter(object):
 
     implements(IReadMapping)
@@ -196,18 +200,32 @@
 
     environment = RequestDataProperty(RequestEnvironment)
 
-    def __init__(self, body_instream, outstream, environ, response=None,
-                 positional=()):
+    def __init__(self, body_instream, environ, response=None,
+                 positional=None, outstream=None):
+
+        # BBB: This is backward-compatibility support for the deprecated
+        # output stream.
+        if not hasattr(environ, 'get'):
+            import warnings
+            warnings.warn("Can't pass output streams to requests anymore. "
+                          "This will go away in Zope 3.4.",
+                          DeprecationWarning,
+                          2)
+            environ, response, positional = response, positional, outstream
+
+
         self._traversal_stack = []
         self._last_obj_traversed = None
         self._traversed_names = []
         self._environ = environ
 
-        self._args = positional
+        self._args = positional or ()
+
         if response is None:
-            self._response = self._createResponse(outstream)
+            self._response = self._createResponse()
         else:
             self._response = response
+
         self._response._request = self
 
         self._body_instream = body_instream
@@ -228,7 +246,6 @@
 
     publication = property(_getPublication)
 
-
     def processInputs(self):
         'See IPublisherRequest'
         # Nothing to do here
@@ -283,9 +300,8 @@
         for held in self._held:
             if IHeld.providedBy(held):
                 held.release()
-        
+
         self._held = None
-        self._response = None
         self._body_instream = None
         self._publication = None
 
@@ -381,9 +397,9 @@
 
     has_key = __contains__
 
-    def _createResponse(self, outstream):
+    def _createResponse(self):
         # Should be overridden by subclasses
-        return BaseResponse(outstream)
+        return BaseResponse()
 
     def __nonzero__(self):
         # This is here to avoid calling __len__ for boolean tests
@@ -398,7 +414,7 @@
         path = self.get(attr, "/").strip()
         if path.endswith('/'):
             # Remove trailing backslash, so that we will not get an empty
-            # last entry when splitting the path. 
+            # last entry when splitting the path.
             path = path[:-1]
             self._endswithslash = True
         else:
@@ -424,18 +440,40 @@
 
     __slots__ = ('_presentation_type', )
 
-    def __init__(self, path, body_instream=None, outstream=None, environ=None):
+    def __init__(self, path, body_instream=None, environ=None, outstream=None):
+
+        # BBB: This is backward-compatibility support for the deprecated
+        # output stream.
         if environ is None:
             environ = {}
+        else:
+            if not hasattr(environ, 'get'):
+                import warnings
+                warnings.warn("Can't pass output streams to requests anymore. "
+                              "This will go away in Zope 3.4.",
+                              DeprecationWarning,
+                              2)
+                environ, outstream = outstream, environ
+
         environ['PATH_INFO'] = path
         if body_instream is None:
             body_instream = StringIO('')
-        if outstream is None:
-            outstream = StringIO()
 
-        super(TestRequest, self).__init__(body_instream, outstream, environ)
+        super(TestRequest, self).__init__(body_instream, environ)
+        self.response._outstream = outstream
 
+    def _createResponse(self):
+        return BBBResponse()
 
+class BBBResponse(BaseResponse):
+
+    def outputBody(self):
+        import warnings
+        warnings.warn("Can't pass output streams to requests anymore",
+                      DeprecationWarning,
+                      2)
+        self._outstream.write(self._result)
+
 class DefaultPublication(object):
     """A stub publication.
 

Modified: Zope3/trunk/src/zope/publisher/browser.py
===================================================================
--- Zope3/trunk/src/zope/publisher/browser.py	2005-09-07 19:34:16 UTC (rev 38356)
+++ Zope3/trunk/src/zope/publisher/browser.py	2005-09-07 20:14:34 UTC (rev 38357)
@@ -211,18 +211,16 @@
 
     # Set this to True in a subclass to redirect GET requests when the
     # effective and actual URLs differ.
-    use_redirect = False 
+    use_redirect = False
 
-    def __init__(self, body_instream, outstream, environ, response=None):
+    def __init__(self, body_instream, environ, response=None):
         self.form = {}
         self.charsets = None
-        super(BrowserRequest, self).__init__(
-            body_instream, outstream, environ, response)
+        super(BrowserRequest, self).__init__(body_instream, environ, response)
 
 
-    def _createResponse(self, outstream):
-        # Should be overridden by subclasses
-        return BrowserResponse(outstream)
+    def _createResponse(self):
+        return BrowserResponse()
 
     def _decode(self, text):
         """Try to decode the text using one of the available charsets."""
@@ -281,7 +279,7 @@
         # Check whether this field is a file upload object
         # Note: A field exists for files, even if no filename was
         # passed in and no data was uploaded. Therefore we can only
-        # tell by the empty filename that no upload was made. 
+        # tell by the empty filename that no upload was made.
         key = item.name
         if (hasattr(item, 'file') and hasattr(item, 'filename')
             and hasattr(item,'headers')):
@@ -600,9 +598,8 @@
     """Browser request with a constructor convenient for testing
     """
 
-    def __init__(self,
-                 body_instream=None, outstream=None, environ=None, form=None,
-                 skin=None,
+    def __init__(self, body_instream=None, environ=None, form=None,
+                 skin=None, outstream=None,
                  **kw):
 
         _testEnv =  {
@@ -612,19 +609,28 @@
             'GATEWAY_INTERFACE':  'TestFooInterface/1.0',
             }
 
-        if environ:
+        if environ is not None:
+            # BBB: This is backward-compatibility support for the deprecated
+            # output stream.
+            try:
+                environ.get
+            except AttributeError:
+                import warnings
+                warnings.warn("Can't pass output streams to requests anymore. "
+                              "This will go away in Zope 3.4.",
+                              DeprecationWarning,
+                              2)
+                environ, form, skin, outstream = form, skin, outstream, environ
+
             _testEnv.update(environ)
+
         if kw:
             _testEnv.update(kw)
         if body_instream is None:
             from StringIO import StringIO
             body_instream = StringIO('')
 
-        if outstream is None:
-            from StringIO import StringIO
-            outstream = StringIO()
-
-        super(TestRequest, self).__init__(body_instream, outstream, _testEnv)
+        super(TestRequest, self).__init__(body_instream, _testEnv)
         if form:
             self.form.update(form)
 
@@ -642,11 +648,15 @@
         else:
             directlyProvides(self, IDefaultBrowserLayer)
 
-    def setPrincipal(self, principal):
-        # HTTPRequest needs to notify the HTTPTask of the username.
-        # We don't want to have to stub HTTPTask in the tests.
-        BaseRequest.setPrincipal(self, principal)
+        # BBB: Goes away in 3.4.
+        self.response.outstream = outstream
 
+    # BBB: Remove in 3.4. The super version will be ok.
+    def _createResponse(self):
+        return BBBResponse()
+
+
+
 class BrowserResponse(HTTPResponse):
     """Browser response
     """
@@ -655,60 +665,21 @@
         '_base', # The base href
         )
 
-    def setBody(self, body):
-        """Sets the body of the response
-
-        Sets the return body equal to the (string) argument "body". Also
-        updates the "content-length" return header and sets the status to
-        200 if it has not already been set.
-        """
-        if body is None:
-            return
-
-        if not isinstance(body, StringTypes):
-            body = unicode(body)
-
-        if 'content-type' not in self._headers:
-            c = (self.__isHTML(body) and 'text/html' or 'text/plain')
-            if self._charset is not None:
-                c += ';charset=' + self._charset
-            self.setHeader('content-type', c)
+    def _implicitResult(self, body):
+        content_type = self.getHeader('content-type')
+        if content_type is None:
+            if isHTML(body):
+                content_type = 'text/html'
+            else:
+                content_type = 'text/plain'
             self.setHeader('x-content-type-warning', 'guessed from content')
-            # TODO: emit a warning once all page templates are changed to
-            # specify their content type explicitly.
+            self.setHeader('content-type', content_type)
 
+        body, headers = super(BrowserResponse, self)._implicitResult(body)
         body = self.__insertBase(body)
-        self._body = body
-        self._updateContentLength()
-        if not self._status_set:
-            self.setStatus(200)
+        return body, headers
 
 
-    def __isHTML(self, str):
-        """Try to determine whether str is HTML or not."""
-        s = str.lstrip().lower()
-        if s.startswith('<!doctype html'):
-            return True
-        if s.startswith('<html') and (s[5:6] in ' >'):
-            return True
-        if s.startswith('<!--'):
-            idx = s.find('<html')
-            return idx > 0 and (s[idx+5:idx+6] in ' >')
-        else:
-            return False
-
-
-    def __wrapInHTML(self, title, content):
-        t = escape(title)
-        return (
-            "<html><head><title>%s</title></head>\n"
-            "<body><h2>%s</h2>\n"
-            "%s\n"
-            "</body></html>\n" %
-            (t, t, content)
-            )
-
-
     def __insertBase(self, body):
         # Only insert a base tag if content appears to be html.
         content_type = self.getHeader('content-type', '')
@@ -756,6 +727,36 @@
         super(BrowserResponse, self).reset()
         self._base = ''
 
+class BBBResponse(BrowserResponse):
+
+    def outputBody(self):
+        import warnings
+        warnings.warn("Can't pass output streams to requests anymore",
+                      DeprecationWarning,
+                      2)
+        self.outstream.write("Status: %s\r\n" % self.getStatusString())
+        headers = self.getHeaders()
+        headers.sort()
+        self.outstream.write(
+            "\r\n".join([("%s: %s" % h) for h in headers])
+            + "\r\n\r\n"
+            )
+        self.outstream.write(''.join(self.getBody()))
+
+
+def isHTML(str):
+     """Try to determine whether str is HTML or not."""
+     s = str.lstrip().lower()
+     if s.startswith('<!doctype html'):
+         return True
+     if s.startswith('<html') and (s[5:6] in ' >'):
+         return True
+     if s.startswith('<!--'):
+         idx = s.find('<html')
+         return idx > 0 and (s[idx+5:idx+6] in ' >')
+     else:
+         return False
+
 def normalize_lang(lang):
     lang = lang.strip().lower()
     lang = lang.replace('_', '-')

Modified: Zope3/trunk/src/zope/publisher/ftp.py
===================================================================
--- Zope3/trunk/src/zope/publisher/ftp.py	2005-09-07 19:34:16 UTC (rev 38356)
+++ Zope3/trunk/src/zope/publisher/ftp.py	2005-09-07 20:14:34 UTC (rev 38357)
@@ -29,7 +29,7 @@
     def getResult(self):
         if getattr(self, '_exc', None) is not None:
             raise self._exc[0], self._exc[1], self._exc[2]
-        return self._getBody()
+        return self._result
 
     def handleException(self, exc_info):
         self._exc = exc_info
@@ -39,12 +39,23 @@
 
     __slots__ = '_auth'
 
-    def __init__(self, body_instream, outstream, environ, response=None):
-        self._auth = environ.get('credentials')
+    def __init__(self, body_instream, environ, response=None, bbb=None):
+        # BBB: This is backward-compatibility support for the deprecated
+        # output stream.
+        try:
+            self._auth = environ.get('credentials')
+        except AttributeError:
+            import warnings
+            warnings.warn("Can't pass output streams to requests anymore. "
+                          "This will go away in Zope 3.4.",
+                          DeprecationWarning,
+                          2)
+            environ, response = response, bbb
+            self._auth = environ.get('credentials')
+
         del environ['credentials']
 
-        super(FTPRequest, self).__init__(
-            body_instream, outstream, environ, response)
+        super(FTPRequest, self).__init__(body_instream, environ, response)
 
         path = environ['path']
         if path.startswith('/'):
@@ -55,9 +66,9 @@
             self.setTraversalStack(path)
 
 
-    def _createResponse(self, outstream):
+    def _createResponse(self):
         """Create a specific XML-RPC response object."""
-        return FTPResponse(outstream)
+        return FTPResponse()
 
     def _authUserPW(self):
         'See IFTPCredentials'

Modified: Zope3/trunk/src/zope/publisher/http.py
===================================================================
--- Zope3/trunk/src/zope/publisher/http.py	2005-09-07 19:34:16 UTC (rev 38356)
+++ Zope3/trunk/src/zope/publisher/http.py	2005-09-07 20:14:34 UTC (rev 38357)
@@ -21,6 +21,7 @@
 from cgi import escape
 from Cookie import SimpleCookie
 
+from zope.deprecation import deprecation
 from zope.interface import implements
 
 from zope.publisher import contenttype
@@ -30,13 +31,14 @@
 from zope.publisher.interfaces.http import IHTTPPublisher
 
 from zope.publisher.interfaces import Redirect
-from zope.publisher.interfaces.http import IHTTPResponse
+from zope.publisher.interfaces.http import IHTTPResponse, IResult
 from zope.publisher.interfaces.http import IHTTPApplicationResponse
 from zope.publisher.interfaces.logginginfo import ILoggingInfo
 from zope.i18n.interfaces import IUserPreferredCharsets
 from zope.i18n.interfaces import IUserPreferredLanguages
 from zope.i18n.locales import locales, LoadLocaleError
 
+from zope.publisher import contenttype
 from zope.publisher.base import BaseRequest, BaseResponse
 from zope.publisher.base import RequestDataProperty, RequestDataMapper
 from zope.publisher.base import RequestDataGetter
@@ -234,10 +236,21 @@
 
     retry_max_count = 3    # How many times we're willing to retry
 
-    def __init__(self, body_instream, outstream, environ, response=None):
-        super(HTTPRequest, self).__init__(
-            body_instream, outstream, environ, response)
+    def __init__(self, body_instream, environ, response=None, outstream=None):
+        # BBB: This is backward-compatibility support for the deprecated
+        # output stream.
+        try:
+            environ.get
+        except AttributeError:
+            import warnings
+            warnings.warn("Can't pass output streams to requests anymore. "
+                          "This will go away in Zope 3.4.",
+                          DeprecationWarning,
+                          2)
+            environ, response = response, outstream
 
+        super(HTTPRequest, self).__init__(body_instream, environ, response)
+
         self._orig_env = environ
         environ = sane_environment(environ)
 
@@ -258,7 +271,6 @@
         self.__setupLocale()
 
     def __setupLocale(self):
-        self.response.setCharsetUsingRequest(self)
         envadapter = IUserPreferredLanguages(self, None)
         if envadapter is None:
             self._locale = None
@@ -372,7 +384,6 @@
         new_response = self.response.retry()
         request = self.__class__(
             body_instream=self._body_instream,
-            outstream=None,
             environ=self._orig_env,
             response=new_response,
             )
@@ -435,15 +446,16 @@
     def setPrincipal(self, principal):
         'See IPublicationRequest'
         super(HTTPRequest, self).setPrincipal(principal)
-
-        if self.response.http_transaction is not None:
-            logging_info = ILoggingInfo(principal)
+        logging_info = ILoggingInfo(principal, None)
+        if logging_info is None:
+            message = '-'
+        else:
             message = logging_info.getLogMessage()
-            self.response.http_transaction.setAuthUserName(message)
+        self.response.authUser = message
 
-    def _createResponse(self, outstream):
+    def _createResponse(self):
         # Should be overridden by subclasses
-        return HTTPResponse(outstream)
+        return HTTPResponse()
 
 
     def getURL(self, level=0, path_only=False):
@@ -540,45 +552,57 @@
     implements(IHTTPResponse, IHTTPApplicationResponse)
 
     __slots__ = (
+        'authUser',             # Authenticated user string
+        # BBB: Remove for Zope 3.4.
         '_header_output',       # Hook object to collaborate with a server
                                 # for header generation.
         '_headers',
         '_cookies',
-        '_accumulated_headers', # Headers that can have multiples
-        '_wrote_headers',
         '_status',              # The response status (usually an integer)
         '_reason',              # The reason that goes with the status
         '_status_set',          # Boolean: status explicitly set
         '_charset',             # String: character set for the output
-        'http_transaction',     # HTTPTask object
         )
 
 
-    def __init__(self, outstream, header_output=None, http_transaction=None):
+    def __init__(self, header_output=None, http_transaction=None):
+        # BBB: Both, header_output and http_transaction have been deprecated.
+        if header_output is not None:
+            import warnings
+            warnings.warn(
+                "The header output API is completely deprecated. It's "
+                "intentions were not clear and it duplicated APIs in the "
+                "response, which you should use instead. "
+                "This will go away in Zope 3.4.",
+                DeprecationWarning, 2)
+
+        if http_transaction is not None:
+            import warnings
+            warnings.warn(
+                "Storing the HTTP transaction here was a *huge* hack to "
+                "support transporting the authenticated user string "
+                "to the server. You should never rely on this variable "
+                "anyways. "
+                "This will go away in Zope 3.4.",
+                DeprecationWarning, 2)
+
         self._header_output = header_output
-        self.http_transaction = http_transaction
 
-        super(HTTPResponse, self).__init__(outstream)
+        super(HTTPResponse, self).__init__()
         self.reset()
 
+
     def reset(self):
         'See IResponse'
         super(HTTPResponse, self).reset()
         self._headers = {}
         self._cookies = {}
-        self._accumulated_headers = []
-        self._wrote_headers = False
         self._status = 599
         self._reason = 'No status set'
         self._status_set = False
         self._charset = None
+        self.authUser = '-'
 
-    def setHeaderOutput(self, header_output):
-        self._header_output = header_output
-
-    def setHTTPTransaction(self, http_transaction):
-        self.http_transaction = http_transaction
-
     def setStatus(self, status, reason=None):
         'See IHTTPResponse'
         if status is None:
@@ -607,67 +631,57 @@
         'See IHTTPResponse'
         return self._status
 
+    def getStatusString(self):
+        'See IHTTPResponse'
+        return '%i %s' % (self._status, self._reason)
 
     def setHeader(self, name, value, literal=False):
         'See IHTTPResponse'
-
         name = str(name)
         value = str(value)
 
-        key = name.lower()
-        if key == 'set-cookie':
-            self.addHeader(name, value)
-        else:
-            name = literal and name or key
-            self._headers[name]=value
+        if not literal:
+            name = name.lower()
 
+        self._headers[name] = [value]
 
+
     def addHeader(self, name, value):
         'See IHTTPResponse'
-        accum = self._accumulated_headers
-        accum.append('%s: %s' % (name, value))
+        values = self._headers.setdefault(name, [])
+        values.append(value)
 
 
     def getHeader(self, name, default=None, literal=False):
         'See IHTTPResponse'
         key = name.lower()
         name = literal and name or key
-        return self._headers.get(name, default)
+        result = self._headers.get(name)
+        if result:
+            return result[0]
+        return default
 
+
     def getHeaders(self):
         'See IHTTPResponse'
-        result = {}
+        result = []
         headers = self._headers
 
-        result["X-Powered-By"] = "Zope (www.zope.org), Python (www.python.org)"
+        result.append(
+            ("X-Powered-By", "Zope (www.zope.org), Python (www.python.org)"))
 
-        for key, val in headers.items():
+        for key, values in headers.items():
             if key.lower() == key:
                 # only change non-literal header names
-                key = key.capitalize()
-                start = 0
-                location = key.find('-', start)
-                while location >= start:
-                    key = "%s-%s" % (key[:location],
-                                     key[location+1:].capitalize())
-                    start = location + 1
-                    location = key.find('-', start)
-            result[key] = val
+                key = '-'.join([k.capitalize() for k in key.split('-')])
+            result.extend([(key, val) for val in values])
 
+        result.extend([tuple(cookie.split(': ', 1))
+                       for cookie in self._cookie_list()])
+
         return result
 
 
-    def appendToHeader(self, name, value, delimiter=','):
-        'See IHTTPResponse'
-        headers = self._headers
-        if name in headers:
-            h = self._header[name]
-            h = "%s%s\r\n\t%s" % (h, delimiter, value)
-        else:
-            h = value
-        self.setHeader(name, h)
-
-
     def appendToCookie(self, name, value):
         'See IHTTPResponse'
         cookies = self._cookies
@@ -711,52 +725,83 @@
         return self._cookies.get(name, default)
 
 
-    def setCharset(self, charset=None):
+    def setResult(self, result):
+        r = IResult(result, None)
+        if r is None:
+            if isinstance(result, basestring):
+                body, headers = self._implicitResult(result)
+                r = DirectResult((body,), headers)
+            elif result is None:
+                body, headers = self._implicitResult('')
+                r = DirectResult((body,), headers)
+            else:
+                raise TypeError('The result should be adaptable to IResult.')
+        self._result = r
+        self._headers.update(dict([(k, [v]) for (k, v) in r.headers]))
+        if not self._status_set:
+            self.setStatus(200)
+
+
+    def consumeBody(self):
         'See IHTTPResponse'
-        self._charset = charset
+        return ''.join(self._result.body)
 
-    def _updateContentType(self):
-        if self._charset:
-            ctype = self.getHeader('content-type', '')
-            if ctype.lower().startswith('text'):
-                ctinfo = contenttype.parseOrdered(ctype)
-                for param, value in ctinfo[2]:
-                    if param == "charset":
-                        break
-                else:
-                    ctinfo[2].append(("charset", self._charset))
-                    self.setHeader('content-type', contenttype.join(ctinfo))
 
-    def setCharsetUsingRequest(self, request):
+    def consumeBodyIter(self):
         'See IHTTPResponse'
-        envadapter = IUserPreferredCharsets(request, None)
-        if envadapter is None:
-            return
+        return self._result.body
 
-        try:
-            charset = envadapter.getPreferredCharsets()[0]
-        except IndexError:
-            # Exception caused by empty list! This is okay though, since the
-            # browser just could have sent a '*', which means we can choose
-            # the encoding, which we do here now.
-            charset = 'utf-8'
-        self.setCharset(charset)
 
-    def setBody(self, body):
-        self._body = unicode(body)
-        if not self._status_set:
-            self.setStatus(200)
+    # BBB: Backward-compatibility for old body API
+    _body = property(consumeBody)
+    _body = deprecation.deprecated(
+        _body,
+        '`_body` has been deprecated in favor of `consumeBody()`. '
+        'This will go away in Zope 3.4.')
 
+
+    def _implicitResult(self, body):
+        encoding = getCharsetUsingRequest(self._request) or 'utf-8'
+        content_type = self.getHeader('content-type')
+
+        if isinstance(body, unicode):
+            try:
+                if not content_type.startswith('text/'):
+                    raise ValueError(
+                        'Unicode results must have a text content type.')
+            except AttributeError:
+                    raise ValueError(
+                        'Unicode results must have a text content type.')
+
+
+            major, minor, params = contenttype.parse(content_type)
+
+            if 'charset' in params:
+                encoding = params['charset']
+            else:
+                content_type += ';charset=%s' %encoding
+
+            body = body.encode(encoding)
+
+        if content_type:
+            headers = [('content-type', content_type),
+                       ('content-length', str(len(body)))]
+        else:
+            headers = [('content-length', str(len(body)))]
+
+        return body, headers
+
+
     def handleException(self, exc_info):
         """
         Calls self.setBody() with an error response.
         """
         t, v = exc_info[:2]
         if isinstance(t, ClassType):
-            title = tname = t.__name__
             if issubclass(t, Redirect):
                 self.redirect(v.getLocation())
                 return
+            title = tname = t.__name__
         else:
             title = tname = unicode(t)
 
@@ -765,7 +810,7 @@
         self.setStatus(tname)
 
         body = self._html(title, "A server error occurred." )
-        self.setBody(body)
+        self.setResult(body)
 
 
     def internalError(self):
@@ -788,17 +833,8 @@
         """
         Returns a response object to be used in a retry attempt
         """
-        return self.__class__(self._outstream,
-                              self._header_output)
+        return self.__class__()
 
-    def _updateContentLength(self, data=None):
-        if data is None:
-            blen = str(len(self._body))
-        else:
-            blen = str(len(data))
-        if blen.endswith('L'):
-            blen = blen[:-1]
-        self.setHeader('content-length', blen)
 
     def redirect(self, location, status=None):
         """Causes a redirection without raising an error"""
@@ -809,9 +845,10 @@
                 status=302
             else:
                 status=303
-                
+
         self.setStatus(status)
         self.setHeader('Location', location)
+        self.setResult(DirectResult(()))
         return location
 
     def _cookie_list(self):
@@ -834,114 +871,7 @@
                 c[name][k] = str(v)
         return str(c).splitlines()
 
-    def getHeaderText(self, m):
-        lst = ['Status: %s %s' % (self._status, self._reason)]
-        items = m.items()
-        items.sort()
-        lst.extend(['%s: %s' % i for i in items])
-        lst.extend(self._cookie_list())
-        lst.extend(self._accumulated_headers)
-        return ('%s\r\n\r\n' % '\r\n'.join(lst))
 
-
-    def outputHeaders(self):
-        """This method outputs all headers.
-        Since it is a final output method, it must take care of all possible
-        unicode strings and encode them! 
-        """
-        if self._charset is None:
-            self.setCharset('utf-8')
-        self._updateContentType()
-        encode = self._encode
-        headers = self.getHeaders()
-        # Clean these headers from unicode by possibly encoding them
-        headers = dict([(encode(key), encode(val))
-                        for key, val in headers.iteritems()])
-        # Cleaning done.
-        header_output = self._header_output
-        if header_output is not None:
-            # Use the IHeaderOutput interface.
-            header_output.setResponseStatus(self._status, encode(self._reason))
-            header_output.setResponseHeaders(headers)
-            cookie_list = map(encode, self._cookie_list())
-            header_output.appendResponseHeaders(cookie_list)
-            accumulated_headers = map(encode, self._accumulated_headers)
-            header_output.appendResponseHeaders(accumulated_headers)
-        else:
-            # Write directly to outstream.
-            headers_text = self.getHeaderText(headers)
-            self._outstream.write(encode(headers_text))
-
-    def write(self, string):
-        """See IApplicationResponse
-
-        Return data as a stream
-
-        HTML data may be returned using a stream-oriented interface.
-        This allows the browser to display partial results while
-        computation of a response to proceed.
-
-        The published object should first set any output headers or
-        cookies on the response object and encode the string into
-        appropriate encoding.
-
-        Note that published objects must not generate any errors
-        after beginning stream-oriented output.
-
-        """
-        if not self._wrote_headers:
-            self.outputHeaders()
-            self._wrote_headers = True
-
-        self._outstream.write(string)
-
-    def output(self, data):
-        """Output the data to the world.
-        
-        There are a couple of steps we have to do:
-
-        1. Check that there is a character encoding for the data. If not,
-           choose UTF-8. Note that if the charset is None, this is a sign of a
-           bug! The method setCharsetUsingRequest() specifically sets the
-           encoding to UTF-8, if none was found in the HTTP header. This
-           method should always be called when reading the HTTP request.
-
-        2. Now that the encoding has been finalized, we can output the
-           headers.
-
-        3. If the content type is text-based, let's encode the data and send
-           it also out the door.
-
-        4. Make sure that a Content-Length or Transfer-Encoding header is
-           present.
-        """
-        if self._charset is None:
-            self.setCharset('utf-8')
-
-        if self.getHeader('content-type', '').startswith('text'):
-            data = self._encode(data)
-            self._updateContentLength(data)
-        
-        if (not ('content-length' in self._headers)
-            and not ('transfer-encoding' in self._headers)):
-            self._updateContentLength()
-
-        self.write(data)
-
-
-    def outputBody(self):
-        """Outputs the response body."""
-        self.output(self._body)
-
-
-    def _encode(self, text):
-        # Any method that calls this method has the responsibility to set
-        # the _charset variable (if None) to a non-None value (usually UTF-8)
-        if isinstance(text, unicode):
-            return text.encode(self._charset)
-        return text
-
-
 def sort_charsets(x, y):
     if y[1] == 'utf-8':
         return 1
@@ -998,3 +928,42 @@
         # always good to use UTF-8.
         charsets.sort(sort_charsets)
         return [c[1] for c in charsets]
+
+
+def getCharsetUsingRequest(request):
+    'See IHTTPResponse'
+    envadapter = IUserPreferredCharsets(request, None)
+    if envadapter is None:
+        return
+
+    try:
+        charset = envadapter.getPreferredCharsets()[0]
+    except IndexError:
+        # Exception caused by empty list! This is okay though, since the
+        # browser just could have sent a '*', which means we can choose
+        # the encoding, which we do here now.
+        charset = 'utf-8'
+    return charset
+
+
+class DirectResult(object):
+    """A generic result object.
+
+    The result's body can be any iteratable. It is the responsibility of the
+    application to specify all headers related to the content, such as the
+    content type and length.
+    """
+    implements(IResult)
+
+    def __init__(self, body, headers=()):
+        self.body = body
+        self.headers = headers
+
+
+def StrResult(body, headers=()):
+    """A simple string result that represents any type of data.
+
+    It is the responsibility of the application to specify all the headers,
+    including content type and length.
+    """
+    return DirectResult((body,), headers)

Modified: Zope3/trunk/src/zope/publisher/interfaces/__init__.py
===================================================================
--- Zope3/trunk/src/zope/publisher/interfaces/__init__.py	2005-09-07 19:34:16 UTC (rev 38356)
+++ Zope3/trunk/src/zope/publisher/interfaces/__init__.py	2005-09-07 20:14:34 UTC (rev 38357)
@@ -171,7 +171,7 @@
 
         This method should return an object having the specified name and
         `self` as parent. The method can use the request to determine the
-        correct object. 
+        correct object.
         """
 
 
@@ -183,18 +183,13 @@
         The request must be an IPublisherRequest.
         """
 
-class IPublisherResponse(Interface):
-    """Interface used by the publsher
-    """
+class IResponse(Interface):
+    """Interface used by the publsher"""
 
-    def setBody(result):
+    def setResult(result):
         """Sets the response result value.
         """
 
-    def reset():
-        """Resets response state on exceptions.
-        """
-
     def handleException(exc_info):
         """Handles an otherwise unhandled exception.
 
@@ -209,8 +204,10 @@
         Should report back to the client that an internal error occurred.
         """
 
-    def outputBody():
-        """Outputs the response to the client
+    def reset():
+        """Reset the output result.
+
+        Reset the response by nullifying already set variables.
         """
 
     def retry():
@@ -247,7 +244,6 @@
 
         This is called before traversing each object.  The ob argument
         is the object that is about to be traversed.
-        
         """
 
     def traverseName(request, ob, name):
@@ -289,15 +285,6 @@
         """
 
 
-class IApplicationResponse(Interface):
-    """Features that support application logic
-    """
-
-    def write(string):
-        """Output a string to the response body.
-        """
-
-
 class IPublicationRequest(IPresentationRequest, IParticipation):
     """Interface provided by requests to IPublication objects
     """
@@ -316,7 +303,6 @@
 
         The object should be an IHeld.  If it is an IHeld, it's
         release method will be called when it is released.
-        
         """
 
     def getTraversalStack():
@@ -449,23 +435,20 @@
         virtue of including the dotted name of a package as a prefex.  A
         package name is used to limit the authority for picking names for
         a package to the people using that package.
-    
+
         For example, when implementing annotations for hypothetical
         request-persistent adapters in a hypothetical zope.persistentadapter
         package, the key would be (or at least begin with) the following::
-    
+
           "zope.persistentadapter"
         """)
 
-class IResponse(IPublisherResponse, IApplicationResponse):
-    """The basic response contract
-    """
 
-
 class IRequest(IPublisherRequest, IPublicationRequest, IApplicationRequest):
     """The basic request contract
     """
-    
+
+
 class ILayer(IInterface):
     """A grouping of related views for a request."""
 

Modified: Zope3/trunk/src/zope/publisher/interfaces/http.py
===================================================================
--- Zope3/trunk/src/zope/publisher/interfaces/http.py	2005-09-07 19:34:16 UTC (rev 38356)
+++ Zope3/trunk/src/zope/publisher/interfaces/http.py	2005-09-07 20:14:34 UTC (rev 38357)
@@ -19,7 +19,6 @@
 from zope.interface import Attribute
 
 from zope.publisher.interfaces import IApplicationRequest
-from zope.publisher.interfaces import IApplicationResponse
 from zope.publisher.interfaces import IPublishTraverse
 from zope.publisher.interfaces import IRequest
 from zope.publisher.interfaces import IResponse
@@ -36,7 +35,7 @@
     def setVirtualHostRoot(names):
         """Marks the currently traversed object as the root of a virtual host.
 
-        Any path elements traversed up to that 
+        Any path elements traversed up to that
 
         Set the names which compose the application path.
         These are the path elements that appear in the beginning of
@@ -47,7 +46,7 @@
 
     def getVirtualHostRoot():
         """Returns the object which is the virtual host root for this request
-        
+
         Return None if setVirtualHostRoot hasn't been called.
         """
 
@@ -217,7 +216,7 @@
         The challenge is the value of the WWW-Authenticate header."""
 
 
-class IHTTPApplicationResponse(IApplicationResponse):
+class IHTTPApplicationResponse(Interface):
     """HTTP Response
     """
 
@@ -282,6 +281,8 @@
     passed into the object must be used.
     """
 
+    authUser = Attribute('The authenticated user message.')
+
     def getStatus():
         """Returns the current HTTP status code as an integer.
         """
@@ -297,6 +298,9 @@
         correct integer value.
         """
 
+    def getStatusString():
+        """Return the status followed by the reason."""
+
     def setHeader(name, value, literal=False):
         """Sets an HTTP return header "name" with value "value"
 
@@ -322,7 +326,7 @@
         """
 
     def getHeaders():
-        """Returns a mapping of correctly-cased header names to values.
+        """Returns a list of header name, value tuples.
         """
 
     def appendToCookie(name, value):
@@ -363,29 +367,40 @@
         yet.
         """
 
-    def appendToHeader(name, value, delimiter=","):
-        """Appends a value to a header
-
-        Sets an HTTP return header "name" with value "value",
-        appending it following a comma if there was a previous value
-        set for the header.
+    def setResult(result):
+        """Sets the response result value that is adaptable to ``IResult``.
         """
 
-    def setCharset(charset=None):
-        """Set the character set into which the response body should be
-           encoded. If None is passed in then no encoding will be done to
-           the output body.
+    def consumeBody():
+        """Returns the response body as a string.
 
-           The default character set is None.
+        Note that this function can be only requested once, since it is
+        constructed from the result.
         """
 
-    def setCharsetUsingRequest(request):
-        """This convinience function determines the character set based on the
-           HTTP header information.
+    def consumeBodyIter():
+        """Returns the response body as an iterable.
+
+        Note that this function can be only requested once, since it is
+        constructed from the result.
         """
 
-    def setHTTPTransaction(http_transaction):
-        """Sets an HTTP transaction.
 
-        Returns an HTTPTask or None. It is used for logging.
-        """
+class IResult(Interface):
+    """HTTP result.
+
+    The result provides the result in a form suitable for delivery to HTTP
+    clients.
+
+    IMPORTANT: The result object may be held indefinitely by a server and may
+    be accessed by arbitrary threads. For that reason the result should not
+    hold on to any application resources and should be prepared to be invoked
+    from any thread.
+    """
+
+    headers = Attribute('A sequence of tuples of result headers, such as'
+                        '"Content-Type" and "Content-Length", etc.')
+
+    body = Attribute('An iterable that provides the body data of the'
+                     'response.')
+

Modified: Zope3/trunk/src/zope/publisher/publish.py
===================================================================
--- Zope3/trunk/src/zope/publisher/publish.py	2005-09-07 19:34:16 UTC (rev 38356)
+++ Zope3/trunk/src/zope/publisher/publish.py	2005-09-07 20:14:34 UTC (rev 38357)
@@ -138,7 +138,7 @@
                             result = publication.callObject(request, object)
                             response = request.response
                             if result is not response:
-                                response.setBody(result)
+                                response.setResult(result)
 
                             publication.afterCall(request, object)
 
@@ -180,10 +180,13 @@
                     raise
 
         response = request.response
-        response.outputBody()
         if to_raise is not None:
             raise to_raise[0], to_raise[1], to_raise[2]
 
     finally:
         to_raise = None  # Avoid circ. ref.
         request.close()  # Close database connections, etc.
+
+    # Return the request, since it might be a different object than the one
+    # that was passed in.
+    return request

Modified: Zope3/trunk/src/zope/publisher/tests/basetestipublicationrequest.py
===================================================================
--- Zope3/trunk/src/zope/publisher/tests/basetestipublicationrequest.py	2005-09-07 19:34:16 UTC (rev 38356)
+++ Zope3/trunk/src/zope/publisher/tests/basetestipublicationrequest.py	2005-09-07 20:14:34 UTC (rev 38357)
@@ -24,7 +24,7 @@
 
 class Held:
     implements(IHeld)
-    
+
     released = False
 
     def release(self):
@@ -67,7 +67,8 @@
         request.close()
 
         self.failUnless(resource2.released)
-        self.failUnless(sys.getrefcount(response) < rcresponse)
+        # Responses are not unreferenced during close()
+        self.failUnless(sys.getrefcount(response) >= rcresponse)
         self.assertEqual(sys.getrefcount(resource), rcresource)
         self.assertEqual(sys.getrefcount(resource2), rcresource2)
 

Modified: Zope3/trunk/src/zope/publisher/tests/httprequest.py
===================================================================
--- Zope3/trunk/src/zope/publisher/tests/httprequest.py	2005-09-07 19:34:16 UTC (rev 38356)
+++ Zope3/trunk/src/zope/publisher/tests/httprequest.py	2005-09-07 20:14:34 UTC (rev 38357)
@@ -31,16 +31,13 @@
 
 class TestRequest(HTTPRequest):
 
-    def __init__(self, body_instream=None, outstream=None, environ=None, **kw):
+    def __init__(self, body_instream=None, environ=None, **kw):
         if body_instream is None:
             body_instream = StringIO('')
-        if outstream is None:
-            outstream = StringIO()
 
-
         env = {}
         env.update(_testEnv)
         if environ: env.update(environ)
         env.update(kw)
 
-        super(TestRequest, self).__init__(body_instream, outstream, env)
+        super(TestRequest, self).__init__(body_instream, env)

Modified: Zope3/trunk/src/zope/publisher/tests/publication.py
===================================================================
--- Zope3/trunk/src/zope/publisher/tests/publication.py	2005-09-07 19:34:16 UTC (rev 38356)
+++ Zope3/trunk/src/zope/publisher/tests/publication.py	2005-09-07 20:14:34 UTC (rev 38357)
@@ -54,7 +54,7 @@
     def handleException(self, object, request, exc_info, retry_allowed=1):
         '''See interface IPublication'''
         try:
-            request.response.setBody("%s: %s" % (exc_info[:2]))
+            request.response.setResult("%s: %s" % (exc_info[:2]))
         finally:
             exc_info = 0
 

Modified: Zope3/trunk/src/zope/publisher/tests/test_baserequest.py
===================================================================
--- Zope3/trunk/src/zope/publisher/tests/test_baserequest.py	2005-09-07 19:34:16 UTC (rev 38356)
+++ Zope3/trunk/src/zope/publisher/tests/test_baserequest.py	2005-09-07 20:14:34 UTC (rev 38357)
@@ -35,7 +35,7 @@
 
     def _Test__new(self, **kw):
         from zope.publisher.base import BaseRequest
-        return BaseRequest(StringIO(''), StringIO(), kw)
+        return BaseRequest(StringIO(''), kw)
 
     def _Test__expectedViewType(self):
         return None # we don't expect
@@ -43,10 +43,10 @@
     def test_IApplicationRequest_body(self):
         from zope.publisher.base import BaseRequest
 
-        request = BaseRequest(StringIO('spam'), StringIO(), {})
+        request = BaseRequest(StringIO('spam'), {})
         self.assertEqual(request.body, 'spam')
 
-        request = BaseRequest(StringIO('spam'), StringIO(), {})
+        request = BaseRequest(StringIO('spam'), {})
         self.assertEqual(request.bodyFile.read(), 'spam')
 
     def test_IPublicationRequest_getPositionalArguments(self):

Modified: Zope3/trunk/src/zope/publisher/tests/test_baseresponse.py
===================================================================
--- Zope3/trunk/src/zope/publisher/tests/test_baseresponse.py	2005-09-07 19:34:16 UTC (rev 38356)
+++ Zope3/trunk/src/zope/publisher/tests/test_baseresponse.py	2005-09-07 20:14:34 UTC (rev 38357)
@@ -26,7 +26,7 @@
 class TestBaseResponse(TestCase):
 
     def test_interface(self):
-        verifyObject(IResponse, BaseResponse(StringIO()))
+        verifyObject(IResponse, BaseResponse())
 
 
 def test_suite():

Modified: Zope3/trunk/src/zope/publisher/tests/test_browserrequest.py
===================================================================
--- Zope3/trunk/src/zope/publisher/tests/test_browserrequest.py	2005-09-07 19:34:16 UTC (rev 38356)
+++ Zope3/trunk/src/zope/publisher/tests/test_browserrequest.py	2005-09-07 20:14:34 UTC (rev 38357)
@@ -81,12 +81,12 @@
         class Item(object):
             """Required docstring for the publisher."""
             def __call__(self, a, b):
-                return "%s, %s" % (`a`, `b`)
+                return u"%s, %s" % (`a`, `b`)
 
         class Item3(object):
             """Required docstring for the publisher."""
             def __call__(self, *args):
-                return "..."
+                return u"..."
 
         class View(object):
             """Required docstring for the publisher."""
@@ -95,7 +95,7 @@
 
             def index(self, a, b):
                 """Required docstring for the publisher."""
-                return "%s, %s" % (`a`, `b`)
+                return u"%s, %s" % (`a`, `b`)
 
         class Item2(object):
             """Required docstring for the publisher."""
@@ -111,17 +111,15 @@
         self.app.folder.item2 = Item2()
         self.app.folder.item3 = Item3()
 
-    def _createRequest(self, extra_env={}, body="", outstream=None):
+    def _createRequest(self, extra_env={}, body=""):
         env = self._testEnv.copy()
         env.update(extra_env)
         if len(body):
             env['CONTENT_LENGTH'] = str(len(body))
 
         publication = Publication(self.app)
-        if outstream is None:
-            outstream = StringIO()
         instream = StringIO(body)
-        request = TestBrowserRequest(instream, outstream, env)
+        request = TestBrowserRequest(instream, env)
         request.setPublication(publication)
         return request
 

Modified: Zope3/trunk/src/zope/publisher/tests/test_browserresponse.py
===================================================================
--- Zope3/trunk/src/zope/publisher/tests/test_browserresponse.py	2005-09-07 19:34:16 UTC (rev 38356)
+++ Zope3/trunk/src/zope/publisher/tests/test_browserresponse.py	2005-09-07 20:14:34 UTC (rev 38357)
@@ -18,16 +18,15 @@
 
 from unittest import TestCase, TestSuite, main, makeSuite
 from zope.publisher.browser import BrowserResponse
-from StringIO import StringIO
 from zope.interface.verify import verifyObject
 
 # TODO: Waaa need more tests
 
 class TestBrowserResponse(TestCase):
 
-    def test_contentType_DWIM_in_setBody(self):
-        response = BrowserResponse(StringIO())
-        response.setBody(
+    def test_contentType_DWIM_in_setResult(self):
+        response = BrowserResponse()
+        response.setResult(
             """<html>
             <blah>
             </html>
@@ -35,8 +34,8 @@
         self.assert_(response.getHeader('content-type').startswith("text/html")
                      )
 
-        response = BrowserResponse(StringIO())
-        response.setBody(
+        response = BrowserResponse()
+        response.setResult(
             """<html foo="1"
             bar="x">
             <blah>
@@ -45,8 +44,8 @@
         self.assert_(response.getHeader('content-type').startswith("text/html")
                      )
 
-        response = BrowserResponse(StringIO())
-        response.setBody(
+        response = BrowserResponse()
+        response.setResult(
             """<html foo="1"
             bar="x">
             <blah>
@@ -55,8 +54,8 @@
         self.assert_(response.getHeader('content-type').startswith("text/html")
                      )
 
-        response = BrowserResponse(StringIO())
-        response.setBody(
+        response = BrowserResponse()
+        response.setResult(
             """<!doctype html>
             <html foo="1"
             bar="x">
@@ -66,59 +65,27 @@
         self.assert_(response.getHeader('content-type').startswith("text/html")
                      )
 
-        response = BrowserResponse(StringIO())
-        response.setBody(
+        response = BrowserResponse()
+        response.setResult(
             """Hello world
             """)
         self.assert_(response.getHeader('content-type').startswith(
             "text/plain")
                      )
 
-        response = BrowserResponse(StringIO())
-        response.setBody(
+        response = BrowserResponse()
+        response.setResult(
             """<p>Hello world
             """)
-        self.assert_(response.getHeader('content-type').startswith(
-            "text/plain")
-                     )
+        self.assert_(
+            response.getHeader('content-type').startswith("text/plain")
+            )
 
-    def test_writeDataDirectlyToResponse(self):
-        # In this test we are going to simulate the behavior of a view that
-        # writes its data directly to the output pipe, instead of going
-        # through the entire machinery. This is particularly interesting for
-        # views returning large amount of binary data. 
-        output = StringIO()
-        response = BrowserResponse(output)
-        data = 'My special data.'
-
-        # If you write the data yourself directly, then you are responsible
-        # for setting the status and any other HTTP header yourself as well.
-        response.setHeader('content-type', 'text/plain')
-        response.setHeader('content-length', str(len(data)))
-        response.setStatus(200)
-        
-        # Write the data directly to the output stream from the view
-        response.write(data)
-
-        # Then the view returns `None` and the publisher calls
-        response.setBody(None)
-
-        # Now, if we got here already everything should be fine. The `None`
-        # value for the body should have been ignored and our putput value
-        # should just be our data:
-        self.assertEqual(
-            output.getvalue(),
-            'Status: 200 Ok\r\nContent-Length: 16\r\n'
-            'Content-Type: text/plain;charset=utf-8\r\n'
-            'X-Powered-By: Zope (www.zope.org), Python (www.python.org)\r\n'
-            '\r\n'
-            'My special data.')
-
     def test_interface(self):
         from zope.publisher.interfaces.http import IHTTPResponse
         from zope.publisher.interfaces.http import IHTTPApplicationResponse
         from zope.publisher.interfaces import IResponse
-        rp = BrowserResponse(StringIO())
+        rp = BrowserResponse()
         verifyObject(IHTTPResponse, rp)
         verifyObject(IHTTPApplicationResponse, rp)
         verifyObject(IResponse, rp)

Modified: Zope3/trunk/src/zope/publisher/tests/test_ftp.py
===================================================================
--- Zope3/trunk/src/zope/publisher/tests/test_ftp.py	2005-09-07 19:34:16 UTC (rev 38356)
+++ Zope3/trunk/src/zope/publisher/tests/test_ftp.py	2005-09-07 20:14:34 UTC (rev 38357)
@@ -24,20 +24,16 @@
 
     def setUp(self):
         self.__input = StringIO('')
-        self.__output = StringIO()
         env = {'credentials': ('bob', '123'),
                'path': '/a/b/c',
                'command': 'foo',
                }
-        self.__request = zope.publisher.ftp.FTPRequest(
-            self.__input, self.__output, env)
+        self.__request = zope.publisher.ftp.FTPRequest(self.__input, env)
 
     def test_response(self):
         response = self.__request.response
-        response.setBody(123.456)
-        response.outputBody()
+        response.setResult(123.456)
         self.assertEqual(response.getResult(), 123.456)
-        self.failIf(self.__output.getvalue())
 
         try:
             raise ValueError('spam')

Modified: Zope3/trunk/src/zope/publisher/tests/test_http.py
===================================================================
--- Zope3/trunk/src/zope/publisher/tests/test_http.py	2005-09-07 19:34:16 UTC (rev 38356)
+++ Zope3/trunk/src/zope/publisher/tests/test_http.py	2005-09-07 20:14:34 UTC (rev 38357)
@@ -20,7 +20,7 @@
 
 from zope.interface import implements
 from zope.publisher.interfaces.logginginfo import ILoggingInfo
-from zope.publisher.http import HTTPRequest, HTTPResponse
+from zope.publisher.http import HTTPRequest, HTTPResponse, StrResult
 from zope.publisher.publish import publish
 from zope.publisher.base import DefaultPublication
 from zope.publisher.interfaces.http import IHTTPRequest, IHTTPResponse
@@ -32,7 +32,7 @@
 from zope.interface.verify import verifyObject
 
 from StringIO import StringIO
-from Cookie import SimpleCookie, CookieError
+from Cookie import CookieError
 
 
 class UserStub(object):
@@ -71,7 +71,7 @@
             """Required docstring for the publisher."""
 
         class Item(object):
-            """Required docstring for the publisher."""            
+            """Required docstring for the publisher."""
             def __call__(self, a, b):
                 return "%s, %s" % (`a`, `b`)
 
@@ -80,27 +80,32 @@
         self.app.folder.item = Item()
         self.app.xxx = Item()
 
-    def _createRequest(self, extra_env={}, body="", outstream=None):
+    def _createRequest(self, extra_env={}, body=""):
         env = self._testEnv.copy()
         env.update(extra_env)
         if len(body):
             env['CONTENT_LENGTH'] = str(len(body))
 
         publication = DefaultPublication(self.app)
-        if outstream is None:
-            outstream = StringIO()
         instream = StringIO(body)
-        request = HTTPRequest(instream, outstream, env)
+        request = HTTPRequest(instream, env)
         request.setPublication(publication)
         return request
 
     def _publisherResults(self, extra_env={}, body=""):
-        outstream = StringIO()
-        request = self._createRequest(extra_env, body, outstream=outstream)
-        publish(request, handle_errors=0)
-        return outstream.getvalue()
+        request = self._createRequest(extra_env, body)
+        response = request.response
+        publish(request, handle_errors=False)
+        headers = response.getHeaders()
+        headers.sort()
+        return (
+            "Status: %s\r\n" % response.getStatusString()
+            +
+            "\r\n".join([("%s: %s" % h) for h in headers]) + "\r\n\r\n"
+            +
+            ''.join(response.consumeBody())
+            )
 
-
     def test_repr(self):
         request = self._createRequest()
         expect = '<%s.%s instance URL=http://foobar.com>' % (
@@ -125,7 +130,7 @@
         location = request.response.redirect('http://foobar.com/redirected')
         self.assertEquals(location, 'http://foobar.com/redirected')
         self.assertEquals(request.response.getStatus(), 302)
-        self.assertEquals(request.response._headers['location'], location)
+        self.assertEquals(request.response.getHeader('location'), location)
 
         # test HTTP/1.1
         env = {'SERVER_PROTOCOL':'HTTP/1.1'}
@@ -278,19 +283,9 @@
         self.assertEquals(lpw, (login, password))
 
     def testSetPrincipal(self):
-        class HTTPTaskStub(object):
-            auth_user_name = None
-            def setAuthUserName(self, name):
-                self.auth_user_name = name
-
-        task = HTTPTaskStub()
-        req = self._createRequest(outstream=task)
+        req = self._createRequest()
         req.setPrincipal(UserStub("jim"))
-        self.assert_(not req.response._outstream.auth_user_name)
-        req = self._createRequest(outstream=task)
-        req.response.setHTTPTransaction(task)
-        req.setPrincipal(UserStub("jim"))
-        self.assertEquals(req.response.http_transaction.auth_user_name, "jim")
+        self.assertEquals(req.response.authUser, 'jim')
 
     def test_method(self):
         r = self._createRequest(extra_env={'REQUEST_METHOD':'SPAM'})
@@ -420,75 +415,40 @@
 class TestHTTPResponse(unittest.TestCase):
 
     def testInterface(self):
-        rp = HTTPResponse(StringIO())
+        rp = HTTPResponse()
         verifyObject(IHTTPResponse, rp)
         verifyObject(IHTTPApplicationResponse, rp)
         verifyObject(IResponse, rp)
 
     def _createResponse(self):
-        stream = StringIO()
-        response = HTTPResponse(stream)
-        return response, stream
+        response = HTTPResponse()
+        return response
 
-    def _parseResult(self, result):
-        hdrs_text, body = result.split("\r\n\r\n", 1)
-        headers = {}
-        for line in hdrs_text.splitlines():
-            key, val = line.split(":", 1)
-            key = key.strip()
-            val = val.strip()
-            if headers.has_key(key):
-                if type(headers[key]) == type([]):
-                    headers[key].append(val)
-                else:
-                    headers[key] = [headers[key], val]
-            else:
-                headers[key] = val
-        return headers, body
+    def _parseResult(self, response):
+        return dict(response.getHeaders()), ''.join(response.consumeBody())
 
-    def _getResultFromResponse(self, body, charset=None, headers=None):
-        response, stream = self._createResponse()
-        if charset is not None:
-            response.setCharset(charset)
+    def _getResultFromResponse(self, body, charset='utf-8', headers=None):
+        response = self._createResponse()
+        assert(charset == 'utf-8')
         if headers is not None:
             for hdr, val in headers.iteritems():
                 response.setHeader(hdr, val)
-        response.setBody(body)
-        response.outputBody()
-        return self._parseResult(stream.getvalue())
+        response.setResult(body)
+        return self._parseResult(response)
 
-    def testWrite(self):
-        response, stream = self._createResponse()
-        data = 'a'*10
-        # We have to set all the headers ourself
-        response.setHeader('Content-Type', 'text/plain;charset=us-ascii')
-        response.setHeader('Content-Length', str(len(data)))
-
-        # Stream the data
-        for ch in data:
-            response.write(ch)
-
-        headers, body = self._parseResult(stream.getvalue())
-        # Check that the data have been written, and that the header
-        # has been preserved   
-        self.assertEqual(headers['Content-Type'], 'text/plain;charset=us-ascii')
-        self.assertEqual(headers['Content-Length'], str(len(data)))
-        self.assertEqual(body, data)
-
     def testWrite_noContentLength(self):
-        response, stream = self._createResponse()
-        data = 'a'*10
+        response = self._createResponse()
         # We have to set all the headers ourself, we choose not to provide a
         # content-length header
         response.setHeader('Content-Type', 'text/plain;charset=us-ascii')
 
-        # Stream the data
-        for ch in data:
-            response.write(ch)
+        # Output the data
+        data = 'a'*10
+        response.setResult(StrResult(data))
 
-        headers, body = self._parseResult(stream.getvalue())
+        headers, body = self._parseResult(response)
         # Check that the data have been written, and that the header
-        # has been preserved   
+        # has been preserved
         self.assertEqual(headers['Content-Type'], 'text/plain;charset=us-ascii')
         self.assertEqual(body, data)
 
@@ -516,17 +476,17 @@
         eq("", headers.get("Content-Type", ""))
         eq("test", body)
 
-        headers, body = self._getResultFromResponse("test",
+        headers, body = self._getResultFromResponse(u"test",
             headers={"content-type": "text/plain"})
         eq("text/plain;charset=utf-8", headers["Content-Type"])
         eq("test", body)
 
-        headers, body = self._getResultFromResponse("test", "utf-8",
+        headers, body = self._getResultFromResponse(u"test", "utf-8",
             {"content-type": "text/html"})
         eq("text/html;charset=utf-8", headers["Content-Type"])
         eq("test", body)
 
-        headers, body = self._getResultFromResponse("test", "utf-8",
+        headers, body = self._getResultFromResponse(u"test", "utf-8",
             {"content-type": "text/plain;charset=cp1251"})
         eq("text/plain;charset=cp1251", headers["Content-Type"])
         eq("test", body)
@@ -539,23 +499,19 @@
     def _getCookieFromResponse(self, cookies):
         # Shove the cookies through request, parse the Set-Cookie header
         # and spit out a list of headers for examination
-        response, stream = self._createResponse()
+        response = self._createResponse()
         for name, value, kw in cookies:
             response.setCookie(name, value, **kw)
-        response.setBody('test')
-        response.outputBody()
-        headers, body = self._parseResult(stream.getvalue())
-        c = SimpleCookie()
-        cookie_headers = headers["Set-Cookie"]
-        if type(cookie_headers) != type([]):
-            cookie_headers = [cookie_headers]
-        return cookie_headers
+        response.setResult('test')
+        return [header[1]
+                for header in response.getHeaders()
+                if header[0] == "Set-Cookie"]
 
     def testSetCookie(self):
         c = self._getCookieFromResponse([
                 ('foo', 'bar', {}),
                 ])
-        self.failUnless('foo=bar;' in c, 'foo=bar not in %r' % c)
+        self.failUnless('foo=bar;' in c, 'foo=bar; not in %r' % c)
 
         c = self._getCookieFromResponse([
                 ('foo', 'bar', {}),

Modified: Zope3/trunk/src/zope/publisher/tests/test_ipublication.py
===================================================================
--- Zope3/trunk/src/zope/publisher/tests/test_ipublication.py	2005-09-07 19:34:16 UTC (rev 38356)
+++ Zope3/trunk/src/zope/publisher/tests/test_ipublication.py	2005-09-07 20:14:34 UTC (rev 38357)
@@ -48,7 +48,7 @@
 
     def _Test__request(self):
         from zope.publisher.base import BaseRequest
-        request = BaseRequest(StringIO(''), StringIO(), {})
+        request = BaseRequest(StringIO(''), {})
         request.setTraversalStack(['Engineering', 'ZopeCorp'])
         publication = self._Test__new()
         request.setPublication(publication)

Modified: Zope3/trunk/src/zope/publisher/tests/test_publisher.py
===================================================================
--- Zope3/trunk/src/zope/publisher/tests/test_publisher.py	2005-09-07 19:34:16 UTC (rev 38356)
+++ Zope3/trunk/src/zope/publisher/tests/test_publisher.py	2005-09-07 20:14:34 UTC (rev 38357)
@@ -18,7 +18,7 @@
 import unittest
 
 from zope.publisher.publish import publish
-from zope.publisher.base import BaseRequest
+from zope.publisher.base import TestRequest
 from zope.publisher.base import DefaultPublication
 from zope.publisher.interfaces import Unauthorized, NotFound, DebugError
 from zope.publisher.interfaces import IPublication
@@ -57,22 +57,20 @@
         self.app._item = Item()
         self.app.noDocString = NoDocstringItem()
 
-    def _createRequest(self, path, outstream=None, **kw):
-        if outstream is None:
-            outstream = StringIO()
+    def _createRequest(self, path, **kw):
         publication = TestPublication(self.app)
         path = path.split('/')
         path.reverse()
-        request = BaseRequest(StringIO(''), outstream, kw)
+        request = TestRequest(StringIO(''), **kw)
         request.setTraversalStack(path)
         request.setPublication(publication)
         return request
 
     def _publisherResults(self, path, **kw):
-        outstream = StringIO()
-        request = self._createRequest(path, outstream=outstream, **kw)
-        publish(request, handle_errors=0)
-        return outstream.getvalue()
+        request = self._createRequest(path, **kw)
+        response = request.response
+        publish(request, handle_errors=False)
+        return response._result
 
     def testImplementsIPublication(self):
         self.failUnless(IPublication.providedBy(

Modified: Zope3/trunk/src/zope/publisher/tests/test_xmlrpcrequest.py
===================================================================
--- Zope3/trunk/src/zope/publisher/tests/test_xmlrpcrequest.py	2005-09-07 19:34:16 UTC (rev 38356)
+++ Zope3/trunk/src/zope/publisher/tests/test_xmlrpcrequest.py	2005-09-07 20:14:34 UTC (rev 38357)
@@ -104,17 +104,15 @@
         self.app.folder.item2 = Item2()
 
 
-    def _createRequest(self, extra_env={}, body="", outstream=None):
+    def _createRequest(self, extra_env={}, body=""):
         env = self._testEnv.copy()
         env.update(extra_env)
         if len(body):
             env['CONTENT_LENGTH'] = str(len(body))
 
         publication = Publication(self.app)
-        if outstream is None:
-            outstream = StringIO()
         instream = StringIO(body)
-        request = TestXMLRPCRequest(instream, outstream, env)
+        request = TestXMLRPCRequest(instream, env)
         request.setPublication(publication)
         return request
 

Modified: Zope3/trunk/src/zope/publisher/xmlrpc.py
===================================================================
--- Zope3/trunk/src/zope/publisher/xmlrpc.py	2005-09-07 19:34:16 UTC (rev 38356)
+++ Zope3/trunk/src/zope/publisher/xmlrpc.py	2005-09-07 20:14:34 UTC (rev 38357)
@@ -25,7 +25,7 @@
 from zope.publisher.interfaces.xmlrpc import IXMLRPCPublisher
 from zope.publisher.interfaces.xmlrpc import IXMLRPCRequest
 
-from zope.publisher.http import HTTPRequest, HTTPResponse
+from zope.publisher.http import HTTPRequest, HTTPResponse, DirectResult
 
 from zope.security.proxy import isinstance
 
@@ -34,9 +34,9 @@
 
     _args = ()
 
-    def _createResponse(self, outstream):
+    def _createResponse(self):
         """Create a specific XML-RPC response object."""
-        return XMLRPCResponse(outstream)
+        return XMLRPCResponse()
 
     def processInputs(self):
         'See IPublisherRequest'
@@ -52,8 +52,7 @@
 
 class TestRequest(XMLRPCRequest):
 
-    def __init__(self, body_instream=None, outstream=None, environ=None,
-                 response=None, **kw):
+    def __init__(self, body_instream=None, environ=None, response=None, **kw):
 
         _testEnv =  {
             'SERVER_URL':         'http://127.0.0.1',
@@ -69,21 +68,17 @@
         if body_instream is None:
             body_instream = StringIO('')
 
-        if outstream is None:
-            outstream = StringIO()
+        super(TestRequest, self).__init__(body_instream, _testEnv, response)
 
-        super(TestRequest, self).__init__(
-            body_instream, outstream, _testEnv, response)
 
-
 class XMLRPCResponse(HTTPResponse):
     """XMLRPC response.
 
     This object is responsible for converting all output to valid XML-RPC.
     """
 
-    def setBody(self, body):
-        """Sets the body of the response
+    def setResult(self, result):
+        """Sets the result of the response
 
         Sets the return body equal to the (string) argument "body". Also
         updates the "content-length" return header.
@@ -94,7 +89,7 @@
         If is_error is true then the HTML will be formatted as a Zope error
         message instead of a generic HTML page.
         """
-        body = premarshal(body)
+        body = premarshal(result)
         if isinstance(body, xmlrpclib.Fault):
             # Convert Fault object to XML-RPC response.
             body = xmlrpclib.dumps(body, methodresponse=True)
@@ -109,38 +104,32 @@
                 # We really want to catch all exceptions at this point!
                 self.handleException(sys.exc_info())
                 return
-        # Set our body to the XML-RPC message, and fix our MIME type.
-        self.setHeader('content-type', 'text/xml')
 
-        self._body = body
-        self._updateContentLength()
+        super(XMLRPCResponse, self).setResult(
+            DirectResult((body,),
+                         [('content-type', 'text/xml;charset=utf-8'),
+                          ('content-length', str(len(body)))])
+            )
 
-        if not self._status_set:
-            self.setStatus(200)
 
-
     def handleException(self, exc_info):
         """Handle Errors during publsihing and wrap it in XML-RPC XML
 
         >>> import sys
-        >>> from StringIO import StringIO
-        >>> output = StringIO()
-        >>> resp = XMLRPCResponse(output)
+        >>> resp = XMLRPCResponse()
         >>> try:
         ...     raise AttributeError('xyz')
         ... except:
         ...     exc_info = sys.exc_info()
         ...     resp.handleException(exc_info)
-        ...     resp.outputBody()
-        ...     lines = output.getvalue().split('\\n')
-        ...     for line in lines:
-        ...         if 'Status:' in line or 'Content-Type:' in line:
-        ...             print line.strip()
-        ...         if '<value><string>' in line:
-        ...             print line[:61].strip()
-        Status: 200 Ok
-        Content-Type: text/xml;charset=utf-8
-        <value><string>Unexpected Zope exception: AttributeError: xyz
+
+        >>> resp.getStatusString()
+        '200 Ok'
+        >>> resp.getHeader('content-type')
+        'text/xml;charset=utf-8'
+        >>> body = ''.join(resp.consumeBody())
+        >>> 'Unexpected Zope exception: AttributeError: xyz' in body
+        True
         """
         t, value = exc_info[:2]
         s = '%s: %s' % (getattr(t, '__name__', t), value)
@@ -161,7 +150,7 @@
             fault_text = Fault(-3, "Unknown Zope fault type")
 
         # Do the damage.
-        self.setBody(fault_text)
+        self.setResult(fault_text)
         # XML-RPC prefers a status of 200 ("ok") even when reporting errors.
         self.setStatus(200)
 

Modified: Zope3/trunk/src/zope/server/ftp/tests/test_publisher.py
===================================================================
--- Zope3/trunk/src/zope/server/ftp/tests/test_publisher.py	2005-09-07 19:34:16 UTC (rev 38356)
+++ Zope3/trunk/src/zope/server/ftp/tests/test_publisher.py	2005-09-07 20:14:34 UTC (rev 38357)
@@ -80,16 +80,13 @@
 
     _exc = _body = None
 
-    def setBody(self, result):
-        self._body = result
+    def setResult(self, result):
+        self._result = result
 
-    def outputBody(self):
-        pass
-
     def getResult(self):
         if self._exc:
             raise self._exc[0], self._exc[1]
-        return self._body
+        return self._result
 
 class RequestFactory(object):
 

Modified: Zope3/trunk/src/zope/server/http/publisherhttpserver.py
===================================================================
--- Zope3/trunk/src/zope/server/http/publisherhttpserver.py	2005-09-07 19:34:16 UTC (rev 38356)
+++ Zope3/trunk/src/zope/server/http/publisherhttpserver.py	2005-09-07 20:14:34 UTC (rev 38357)
@@ -15,70 +15,57 @@
 
 $Id$
 """
-from zope.server.http.httpserver import HTTPServer
+import zope.deprecation
+from zope.server.http import wsgihttpserver
 from zope.publisher.publish import publish
 import zope.security.management
 
 
-class PublisherHTTPServer(HTTPServer):
-    """Zope Publisher-specific HTTP Server"""
+class PublisherHTTPServer(wsgihttpserver.WSGIHTTPServer):
 
     def __init__(self, request_factory, sub_protocol=None, *args, **kw):
 
-        # This 'adjustment' to args[0] (the hostname for the HTTP server)
-        # under Windows is to get Zope to accept connections from other machines
-        # when the host name is omitted from the server address (in zope.conf).
-        # The address comes in as 'localhost' from ZConfig by way of some
-        # dubious special handling. See collector issue 383 for more info.
-        import sys
-        if sys.platform[:3] == "win" and args[0] == 'localhost':
-            args = ('',) + args[1:]
+        def application(environ, start_response):
+            request = request_factory(environ['wsgi.input'], environ)
+            request = publish(request)
+            response = request.response
+            start_response(response.getStatusString(), response.getHeaders())
+            return response.consumeBody()
 
+        return super(PublisherHTTPServer, self).__init__(
+            application, sub_protocol, *args, **kw)
 
-        # The common HTTP
-        self.request_factory = request_factory
 
-        # An HTTP server is not limited to serving up HTML; it can be
-        # used for other protocols, like XML-RPC, SOAP and so as well
-        # Here we just allow the logger to output the sub-protocol type.
-        if sub_protocol:
-            self.SERVER_IDENT += ' (%s)' %str(sub_protocol)
+class PMDBHTTPServer(wsgihttpserver.WSGIHTTPServer):
 
-        HTTPServer.__init__(self, *args, **kw)
+    def __init__(self, request_factory, sub_protocol=None, *args, **kw):
 
-    def executeRequest(self, task):
-        """Overrides HTTPServer.executeRequest()."""
-        env = task.getCGIEnvironment()
-        instream = task.request_data.getBodyStream()
+        def application(environ, start_response):
+            request = request_factory(environ['wsgi.input'], environ)
+            try:
+                request = publish(request, handle_errors=False)
+            except:
+                import sys, pdb
+                print "%s:" % sys.exc_info()[0]
+                print sys.exc_info()[1]
+                zope.security.management.restoreInteraction()
+                try:
+                    pdb.post_mortem(sys.exc_info()[2])
+                    raise
+                finally:
+                    zope.security.management.endInteraction()
 
-        request = self.request_factory(instream, task, env)
-        response = request.response
-        response.setHeaderOutput(task)
-        response.setHTTPTransaction(task)
-        publish(request)
+            response = request.response
+            start_response(response.getStatusString(), response.getHeaders())
+            return response.consumeBody()
 
+        return super(PublisherHTTPServer, self).__init__(
+            application, sub_protocol, *args, **kw)
 
-class PMDBHTTPServer(PublisherHTTPServer):
-    """Enter the post-mortem debugger when there's an error"""
 
-    def executeRequest(self, task):
-        """Overrides HTTPServer.executeRequest()."""
-        env = task.getCGIEnvironment()
-        instream = task.request_data.getBodyStream()
-
-        request = self.request_factory(instream, task, env)
-        response = request.response
-        response.setHeaderOutput(task)
-        try:
-            publish(request, handle_errors=False)
-        except:
-            import sys, pdb
-            print "%s:" % sys.exc_info()[0]
-            print sys.exc_info()[1]
-            zope.security.management.restoreInteraction()
-            try:
-                pdb.post_mortem(sys.exc_info()[2])
-                raise
-            finally:
-                zope.security.management.endInteraction()
-
+# BBB: Backward-compatibility.
+zope.deprecation.deprecated(
+    ('PublisherHTTPServer', 'PMDBHTTPServer'),
+    'This plain publisher support has been replaced in favor of the '
+    'WSGI HTTP server '
+    'The reference will be gone in X3.4.')

Deleted: Zope3/trunk/src/zope/server/http/tests/test_publisherserver.py
===================================================================
--- Zope3/trunk/src/zope/server/http/tests/test_publisherserver.py	2005-09-07 19:34:16 UTC (rev 38356)
+++ Zope3/trunk/src/zope/server/http/tests/test_publisherserver.py	2005-09-07 20:14:34 UTC (rev 38357)
@@ -1,194 +0,0 @@
-##############################################################################
-#
-# Copyright (c) 2001 Zope Corporation and Contributors.  All Rights Reserved.
-#
-# This software is subject to the provisions of the Zope Public License,
-# Version 1.1 (ZPL).  A copy of the ZPL should accompany this distribution.
-# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
-# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
-# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
-# FOR A PARTICULAR PURPOSE.
-##############################################################################
-"""Test Puvlisher-based HTTP Server
-
-$Id$
-"""
-import unittest
-from asyncore import socket_map, poll
-from threading import Thread
-
-from zope.server.taskthreads import ThreadedTaskDispatcher
-from zope.server.http.publisherhttpserver import PublisherHTTPServer
-
-from zope.component.testing import PlacelessSetup
-import zope.component
-
-from zope.i18n.interfaces import IUserPreferredCharsets
-
-from zope.publisher.http import IHTTPRequest
-from zope.publisher.http import HTTPCharsets
-from zope.publisher.browser import BrowserRequest
-from zope.publisher.base import DefaultPublication
-from zope.publisher.interfaces import Redirect, Retry
-from zope.publisher.http import HTTPRequest
-
-from httplib import HTTPConnection
-
-from time import sleep
-
-td = ThreadedTaskDispatcher()
-
-LOCALHOST = '127.0.0.1'
-
-HTTPRequest.STAGGER_RETRIES = 0  # Don't pause.
-
-
-class Conflict(Exception):
-    """
-    Pseudo ZODB conflict error.
-    """
-
-
-class PublicationWithConflict(DefaultPublication):
-
-    def handleException(self, object, request, exc_info, retry_allowed=1):
-        if exc_info[0] is Conflict and retry_allowed:
-            # This simulates a ZODB retry.
-            raise Retry(exc_info)
-        else:
-            DefaultPublication.handleException(self, object, request, exc_info,
-                                               retry_allowed)
-
-class Accepted(Exception):
-    pass
-
-class tested_object(object):
-    """Docstring required by publisher."""
-    tries = 0
-
-    def __call__(self, REQUEST):
-        return 'URL invoked: %s' % REQUEST.URL
-
-    def redirect_method(self, REQUEST):
-        "Generates a redirect using the redirect() method."
-        REQUEST.response.redirect("http://somewhere.com/redirect")
-
-    def redirect_exception(self):
-        "Generates a redirect using an exception."
-        raise Redirect("http://somewhere.com/exception")
-
-    def conflict(self, REQUEST, wait_tries):
-        """
-        Returns 202 status only after (wait_tries) tries.
-        """
-        if self.tries >= int(wait_tries):
-            raise Accepted
-        else:
-            self.tries += 1
-            raise Conflict
-
-
-class Tests(PlacelessSetup, unittest.TestCase):
-
-    def setUp(self):
-        super(Tests, self).setUp()
-        zope.component.provideAdapter(HTTPCharsets, [IHTTPRequest],
-                                      IUserPreferredCharsets, '')
-        obj = tested_object()
-        obj.folder = tested_object()
-        obj.folder.item = tested_object()
-
-        obj._protected = tested_object()
-
-        pub = PublicationWithConflict(obj)
-
-        def request_factory(input_stream, output_steam, env):
-            request = BrowserRequest(input_stream, output_steam, env)
-            request.setPublication(pub)
-            return request
-
-        td.setThreadCount(4)
-        # Bind to any port on localhost.
-        self.server = PublisherHTTPServer(request_factory, 'Browser',
-                                          LOCALHOST, 0, task_dispatcher=td)
-        self.port = self.server.socket.getsockname()[1]
-        self.run_loop = 1
-        self.thread = Thread(target=self.loop)
-        self.thread.start()
-        sleep(0.1)  # Give the thread some time to start.
-
-    def tearDown(self):
-        self.run_loop = 0
-        self.thread.join()
-        td.shutdown()
-        self.server.close()
-
-    def loop(self):
-        while self.run_loop:
-            poll(0.1, socket_map)
-
-    def testResponse(self, path='/', status_expected=200,
-                     add_headers=None, request_body=''):
-        h = HTTPConnection(LOCALHOST, self.port)
-        h.putrequest('GET', path)
-        h.putheader('Accept', 'text/plain')
-        if add_headers:
-            for k, v in add_headers.items():
-                h.putheader(k, v)
-        if request_body:
-            h.putheader('Content-Length', str(int(len(request_body))))
-        h.endheaders()
-        if request_body:
-            h.send(request_body)
-        response = h.getresponse()
-        length = int(response.getheader('Content-Length', '0'))
-        if length:
-            response_body = response.read(length)
-        else:
-            response_body = ''
-
-        # Please do not disable the status code check.  It must work.
-        self.failUnlessEqual(int(response.status), status_expected)
-
-        self.failUnlessEqual(length, len(response_body))
-
-        if (status_expected == 200):
-            if path == '/': path = ''
-            expect_response = 'URL invoked: http://%s:%d%s' % (LOCALHOST,
-                self.port, path)
-            self.failUnlessEqual(response_body, expect_response)
-
-    def testDeeperPath(self):
-        self.testResponse(path='/folder/item')
-
-    def testNotFound(self):
-        self.testResponse(path='/foo/bar', status_expected=404)
-
-    def testUnauthorized(self):
-        self.testResponse(path='/_protected', status_expected=401)
-
-    def testRedirectMethod(self):
-        self.testResponse(path='/redirect_method', status_expected=303)
-
-    def testRedirectException(self):
-        self.testResponse(path='/redirect_exception', status_expected=303)
-        self.testResponse(path='/folder/redirect_exception',
-                          status_expected=303)
-
-    def testConflictRetry(self):
-        # Expect the "Accepted" response since the retries will succeed.
-        self.testResponse(path='/conflict?wait_tries=2', status_expected=202)
-
-    def testFailedConflictRetry(self):
-        # Expect a "Conflict" response since there will be too many
-        # conflicts.
-        self.testResponse(path='/conflict?wait_tries=10', status_expected=409)
-
-
-
-def test_suite():
-    loader = unittest.TestLoader()
-    return loader.loadTestsFromTestCase(Tests)
-
-if __name__=='__main__':
-    unittest.TextTestRunner().run(test_suite())

Added: Zope3/trunk/src/zope/server/http/tests/test_wsgiserver.py
===================================================================
--- Zope3/trunk/src/zope/server/http/tests/test_wsgiserver.py	2005-09-07 19:34:16 UTC (rev 38356)
+++ Zope3/trunk/src/zope/server/http/tests/test_wsgiserver.py	2005-09-07 20:14:34 UTC (rev 38357)
@@ -0,0 +1,197 @@
+##############################################################################
+#
+# Copyright (c) 2001 Zope Corporation and Contributors.  All Rights Reserved.
+#
+# This software is subject to the provisions of the Zope Public License,
+# Version 1.1 (ZPL).  A copy of the ZPL should accompany this distribution.
+# THIS SOFTWARE IS PROVIDED "AS IS" AND ANY AND ALL EXPRESS OR IMPLIED
+# WARRANTIES ARE DISCLAIMED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF TITLE, MERCHANTABILITY, AGAINST INFRINGEMENT, AND FITNESS
+# FOR A PARTICULAR PURPOSE.
+##############################################################################
+"""Test Puvlisher-based HTTP Server
+
+$Id$
+"""
+import unittest
+from asyncore import socket_map, poll
+from threading import Thread
+from time import sleep
+from httplib import HTTPConnection
+
+from zope.server.taskthreads import ThreadedTaskDispatcher
+from zope.server.http.wsgihttpserver import WSGIHTTPServer
+
+from zope.component.testing import PlacelessSetup
+import zope.component
+
+from zope.i18n.interfaces import IUserPreferredCharsets
+
+from zope.publisher.publish import publish
+from zope.publisher.http import IHTTPRequest
+from zope.publisher.http import HTTPCharsets
+from zope.publisher.browser import BrowserRequest
+from zope.publisher.base import DefaultPublication
+from zope.publisher.interfaces import Redirect, Retry
+from zope.publisher.http import HTTPRequest
+
+td = ThreadedTaskDispatcher()
+
+LOCALHOST = '127.0.0.1'
+
+HTTPRequest.STAGGER_RETRIES = 0  # Don't pause.
+
+
+class Conflict(Exception):
+    """
+    Pseudo ZODB conflict error.
+    """
+
+
+class PublicationWithConflict(DefaultPublication):
+
+    def handleException(self, object, request, exc_info, retry_allowed=1):
+        if exc_info[0] is Conflict and retry_allowed:
+            # This simulates a ZODB retry.
+            raise Retry(exc_info)
+        else:
+            DefaultPublication.handleException(self, object, request, exc_info,
+                                               retry_allowed)
+
+class Accepted(Exception):
+    pass
+
+class tested_object(object):
+    """Docstring required by publisher."""
+    tries = 0
+
+    def __call__(self, REQUEST):
+        return 'URL invoked: %s' % REQUEST.URL
+
+    def redirect_method(self, REQUEST):
+        "Generates a redirect using the redirect() method."
+        REQUEST.response.redirect("http://somewhere.com/redirect")
+
+    def redirect_exception(self):
+        "Generates a redirect using an exception."
+        raise Redirect("http://somewhere.com/exception")
+
+    def conflict(self, REQUEST, wait_tries):
+        """
+        Returns 202 status only after (wait_tries) tries.
+        """
+        if self.tries >= int(wait_tries):
+            raise Accepted
+        else:
+            self.tries += 1
+            raise Conflict
+
+
+class Tests(PlacelessSetup, unittest.TestCase):
+
+    def setUp(self):
+        super(Tests, self).setUp()
+        zope.component.provideAdapter(HTTPCharsets, [IHTTPRequest],
+                                      IUserPreferredCharsets, '')
+        obj = tested_object()
+        obj.folder = tested_object()
+        obj.folder.item = tested_object()
+
+        obj._protected = tested_object()
+
+        pub = PublicationWithConflict(obj)
+
+        def application(environ, start_response):
+            request = BrowserRequest(environ['wsgi.input'], environ)
+            request.setPublication(pub)
+            request = publish(request)
+            response = request.response
+            start_response(response.getStatusString(), response.getHeaders())
+            return response.consumeBody()
+
+        td.setThreadCount(4)
+        # Bind to any port on localhost.
+        self.server = WSGIHTTPServer(application, 'Browser',
+                                     LOCALHOST, 0, task_dispatcher=td)
+
+        self.port = self.server.socket.getsockname()[1]
+        self.run_loop = 1
+        self.thread = Thread(target=self.loop)
+        self.thread.start()
+        sleep(0.1)  # Give the thread some time to start.
+
+    def tearDown(self):
+        self.run_loop = 0
+        self.thread.join()
+        td.shutdown()
+        self.server.close()
+
+    def loop(self):
+        while self.run_loop:
+            poll(0.1, socket_map)
+
+    def testResponse(self, path='/', status_expected=200,
+                     add_headers=None, request_body=''):
+        h = HTTPConnection(LOCALHOST, self.port)
+        h.putrequest('GET', path)
+        h.putheader('Accept', 'text/plain')
+        if add_headers:
+            for k, v in add_headers.items():
+                h.putheader(k, v)
+        if request_body:
+            h.putheader('Content-Length', str(int(len(request_body))))
+        h.endheaders()
+        if request_body:
+            h.send(request_body)
+        response = h.getresponse()
+        length = int(response.getheader('Content-Length', '0'))
+        if length:
+            response_body = response.read(length)
+        else:
+            response_body = ''
+
+        # Please do not disable the status code check.  It must work.
+        self.failUnlessEqual(int(response.status), status_expected)
+
+        self.failUnlessEqual(length, len(response_body))
+
+        if (status_expected == 200):
+            if path == '/': path = ''
+            expect_response = 'URL invoked: http://%s:%d%s' % (LOCALHOST,
+                self.port, path)
+            self.failUnlessEqual(response_body, expect_response)
+
+    def testDeeperPath(self):
+        self.testResponse(path='/folder/item')
+
+    def testNotFound(self):
+        self.testResponse(path='/foo/bar', status_expected=404)
+
+    def testUnauthorized(self):
+        self.testResponse(path='/_protected', status_expected=401)
+
+    def testRedirectMethod(self):
+        self.testResponse(path='/redirect_method', status_expected=303)
+
+    def testRedirectException(self):
+        self.testResponse(path='/redirect_exception', status_expected=303)
+        self.testResponse(path='/folder/redirect_exception',
+                          status_expected=303)
+
+    def testConflictRetry(self):
+        # Expect the "Accepted" response since the retries will succeed.
+        self.testResponse(path='/conflict?wait_tries=2', status_expected=202)
+
+    def testFailedConflictRetry(self):
+        # Expect a "Conflict" response since there will be too many
+        # conflicts.
+        self.testResponse(path='/conflict?wait_tries=10', status_expected=409)
+
+
+
+def test_suite():
+    loader = unittest.TestLoader()
+    return loader.loadTestsFromTestCase(Tests)
+
+if __name__=='__main__':
+    unittest.TextTestRunner().run(test_suite())

Modified: Zope3/trunk/src/zope/server/http/wsgihttpserver.py
===================================================================
--- Zope3/trunk/src/zope/server/http/wsgihttpserver.py	2005-09-07 19:34:16 UTC (rev 38356)
+++ Zope3/trunk/src/zope/server/http/wsgihttpserver.py	2005-09-07 20:14:34 UTC (rev 38357)
@@ -19,11 +19,19 @@
 import sys
 from zope.server.http.httpserver import HTTPServer
 from zope.publisher.publish import publish
+import zope.security.management
 
 
+def fakeWrite(body):
+    raise NotImplementedError(
+        "Zope 3's HTTP Server does not support the WSGI write() function.")
+
+
 class WSGIHTTPServer(HTTPServer):
     """Zope Publisher-specific WSGI-compliant HTTP Server"""
 
+    application = None
+
     def __init__(self, application, sub_protocol=None, *args, **kw):
 
         if sys.platform[:3] == "win" and args[0] == 'localhost':
@@ -49,7 +57,41 @@
             task.appendResponseHeaders(['%s: %s' % i for i in headers])
 
             # Return the write method used to write the response data.
-            return task.write
+            return fakeWrite
 
         # Call the application to handle the request and write a response
-        self.application(env, start_response)
+        task.write(''.join(self.application(env, start_response)))
+
+
+class PMDBWSGIHTTPServer(WSGIHTTPServer):
+    """Enter the post-mortem debugger when there's an error"""
+
+    def executeRequest(self, task):
+        """Overrides HTTPServer.executeRequest()."""
+        env = task.getCGIEnvironment()
+        env['wsgi.input'] = task.request_data.getBodyStream()
+        env['wsgi.handleErrors'] = False
+
+        def start_response(status, headers):
+            # Prepare the headers for output
+            status, reason = re.match('([0-9]*) (.*)', status).groups()
+            task.setResponseStatus(status, reason)
+            task.appendResponseHeaders(['%s: %s' % i for i in headers])
+
+            # Return the write method used to write the response data.
+            return fakeWrite
+
+        # Call the application to handle the request and write a response
+        try:
+            task.write(''.join(self.application(env, start_response)))
+        except:
+            import sys, pdb
+            print "%s:" % sys.exc_info()[0]
+            print sys.exc_info()[1]
+            zope.security.management.restoreInteraction()
+            try:
+                pdb.post_mortem(sys.exc_info()[2])
+                raise
+            finally:
+                zope.security.management.endInteraction()
+

Modified: Zope3/trunk/zope.conf.in
===================================================================
--- Zope3/trunk/zope.conf.in	2005-09-07 19:34:16 UTC (rev 38356)
+++ Zope3/trunk/zope.conf.in	2005-09-07 20:14:34 UTC (rev 38357)
@@ -6,7 +6,7 @@
 interrupt-check-interval 200
 
 <server http>
-  type HTTP
+  type WSGI-HTTP
   address 8080
 </server>
 



More information about the Zope3-Checkins mailing list