r47307 - merge forward

hawkowl-TA+aISz0psMTMxyoc4vAAJOcrHinNvQL0E9HWUfgJXw@public.gmane.org Sat, 23 Apr 2016 04:23:59 -0600 (MDT)
Newsgroups gmane.comp.python.twisted.commits
Message-ID <[email protected]>
Author: hawkowl
Date: Sat Apr 23 04:23:53 2016
New Revision: 47307

Added:
   branches/hide-request-transport-8191-6/twisted/web/topfiles/8191.misc
Modified:
   branches/hide-request-transport-8191-6/twisted/web/http.py
   branches/hide-request-transport-8191-6/twisted/web/server.py
   branches/hide-request-transport-8191-6/twisted/web/test/requesthelper.py
   branches/hide-request-transport-8191-6/twisted/web/test/test_http.py
   branches/hide-request-transport-8191-6/twisted/web/test/test_proxy.py
   branches/hide-request-transport-8191-6/twisted/web/test/test_web.py
   branches/hide-request-transport-8191-6/twisted/web/wsgi.py

Log:
merge forward

Modified: branches/hide-request-transport-8191-6/twisted/web/http.py
==============================================================================
--- branches/hide-request-transport-8191-6/twisted/web/http.py	(original)
+++ branches/hide-request-transport-8191-6/twisted/web/http.py	Sat Apr 23 04:23:53 2016
@@ -91,6 +91,7 @@
 # twisted imports
 from twisted.python.compat import (
     _PY3, unicode, intToBytes, networkString, nativeString)
+from twisted.python.constants import NamedConstant, Names
 from twisted.python.deprecate import deprecated
 from twisted.python import log
 from twisted.python.versions import Version
@@ -568,6 +569,16 @@
         which this request was received is closed and which is C{True} after
         that.
     @type _disconnected: C{bool}
+
+    @ivar _queuedHeaders: A L{tuple} of items that would normally be passed to
+        L{HTTPChannel.writeHeaders}. Used when the response is queued to store
+        the eventual headers.
+    @type _queuedHeaders: A L{tuple} of HTTP version (:{bytes}), status code
+        (L{int}), reason phrase (L{bytes}), and headers (L{list} of L{tuple}s
+        of C{(bytes, bytes)}.
+
+    @ivar _send100: Whether to send a 100-Continue response when unqueued.
+    @type _send100: L{bool}
     """
     producer = None
     finished = 0
@@ -586,6 +597,8 @@
     content = None
     _forceSSL = 0
     _disconnected = False
+    _queuedHeaders = None
+    _send100 = False
 
     def __init__(self, channel, queued):
         """
@@ -594,7 +607,7 @@
             the transport?
         """
         self.notifications = []
-        self.channel = channel
+        self._channel = channel
         self.queued = queued
         self.requestHeaders = Headers()
         self.received_cookies = {}
@@ -602,9 +615,59 @@
         self.cookies = [] # outgoing cookies
 
         if queued:
-            self.transport = StringTransport()
+            self._transport = StringTransport()
         else:
-            self.transport = self.channel.transport
+            self._transport = self._channel
+
+
+    def __getattr__(self, attr):
+        # This is implemented to deprecate some properties of this object.
+        if attr == 'transport':
+            warnings.warn(
+                "twisted.web.http.Request.transport was deprecated in Twisted "
+                "16.2.0. Call directly into the Request object instead.",
+                category=DeprecationWarning,
+                stacklevel=2
+            )
+            attr = '_transport'
+        elif attr == 'channel':
+            warnings.warn(
+                "twisted.web.http.Request.channel was deprecated in Twisted "
+                "16.2.0. Call directly into the Request object instead.",
+                category=DeprecationWarning,
+                stacklevel=2
+            )
+            attr = '_channel'
+
+        try:
+            return self.__dict__[attr]
+        except KeyError:
+            raise AttributeError(
+                "'%s' object has no attribute '%s'" %
+                (self.__class__.__name__, attr)
+            )
+
+
+    def __setattr__(self, attr, value):
+        # This is implemented to deprecate some properties of this object.
+        if attr == 'transport':
+            warnings.warn(
+                "twisted.web.http.Request.transport was deprecated in Twisted "
+                "16.2.0. Call directly into the Request object instead.",
+                category=DeprecationWarning,
+                stacklevel=2
+            )
+            attr = '_transport'
+        elif attr == 'channel':
+            warnings.warn(
+                "twisted.web.http.Request.channel was deprecated in Twisted "
+                "16.2.0. Call directly into the Request object instead.",
+                category=DeprecationWarning,
+                stacklevel=2
+            )
+            attr = '_channel'
+
+        self.__dict__[attr] = value
 
 
     def _cleanup(self):
@@ -614,8 +677,8 @@
         if self.producer:
             log.err(RuntimeError("Producer was not unregistered for %s" % self.uri))
             self.unregisterProducer()
-        self.channel.requestDone(self)
-        del self.channel
+        self._channel.requestDone(self)
+        del self._channel
         try:
             self.content.close()
         except OSError:
@@ -642,14 +705,19 @@
         self.queued = 0
 
         # set transport to real one and send any buffer data
-        data = self.transport.getvalue()
-        self.transport = self.channel.transport
+        data = self._transport.getvalue()
+        self._transport = self._channel
+        if self._send100:
+            self.channel._send100Continue()
+        if self._queuedHeaders:
+            self._transport.writeHeaders(*self._queuedHeaders)
+            self._queuedHeaders = None
         if data:
-            self.transport.write(data)
+            self._transport.write(data)
 
         # if we have producer, register it with transport
         if (self.producer is not None) and not self.finished:
-            self.transport.registerProducer(self.producer, self.streamingProducer)
+            self._transport.registerProducer(self.producer, self.streamingProducer)
 
         # if we're finished, clean up
         if self.finished:
@@ -733,8 +801,8 @@
 
         # cache the client and server information, we'll need this later to be
         # serialized and sent with the request so CGIs will work remotely
-        self.client = self.channel.transport.getPeer()
-        self.host = self.channel.transport.getHost()
+        self.client = self._channel.getPeer()
+        self.host = self._channel.getHost()
 
         # Argument processing
         args = self.args
@@ -761,7 +829,7 @@
                         self.args.update(cgiArgs)
                 except:
                     # It was a bad request.
-                    _respondToBadRequestAndDisconnect(self.channel.transport)
+                    self._channel._respondToBadRequestAndDisconnect()
                     return
             self.content.seek(0, 0)
 
@@ -811,14 +879,14 @@
             if streaming:
                 producer.pauseProducing()
         else:
-            self.transport.registerProducer(producer, streaming)
+            self._channel.registerProducer(producer, streaming)
 
     def unregisterProducer(self):
         """
         Unregister the producer.
         """
         if not self.queued:
-            self.transport.unregisterProducer()
+            self._channel.unregisterProducer()
         self.producer = None
 
 
@@ -880,11 +948,11 @@
 
         if self.chunked:
             # write last chunk and closing CRLF
-            self.transport.write(b"0\r\n\r\n")
+            self._transport.write(b"0\r\n\r\n")
 
         # log request
-        if hasattr(self.channel, "factory"):
-            self.channel.factory.log(self)
+        if hasattr(self._channel, "factory"):
+            self._channel.factory.log(self)
 
         self.finished = 1
         if not self.queued:
@@ -905,11 +973,10 @@
         if not self.startedWriting:
             self.startedWriting = 1
             version = self.clientproto
-            l = []
-            l.append(
-                version + b" " +
-                intToBytes(self.code) + b" " +
-                self.code_message + b"\r\n")
+            code = intToBytes(self.code)
+            reason = self.code_message
+
+            headers = []
 
             # if we don't have a content length, we send data in
             # chunked mode, so that we can support pipelining in
@@ -917,7 +984,7 @@
             if ((version == b"HTTP/1.1") and
                 (self.responseHeaders.getRawHeaders(b'content-length') is None) and
                 self.method != b"HEAD" and self.code not in NO_BODY_CODES):
-                l.append(b'Transfer-Encoding: chunked\r\n')
+                headers.append((b'Transfer-Encoding', b'chunked'))
                 self.chunked = 1
 
             if self.lastModified is not None:
@@ -941,14 +1008,15 @@
                             category=DeprecationWarning, stacklevel=2)
                         # Backward compatible cast for non-bytes values
                         value = networkString('%s' % (value,))
-                    l.extend([name, b": ", value, b"\r\n"])
+                    headers.append((name, value))
 
             for cookie in self.cookies:
-                l.append(b'Set-Cookie: ' + cookie + b'\r\n')
-
-            l.append(b"\r\n")
+                headers.append((b'Set-Cookie', cookie))
 
-            self.transport.writeSequence(l)
+            if self.queued:
+                self._queuedHeaders = (version, code, reason, headers)
+            else:
+                self._channel.writeHeaders(version, code, reason, headers)
 
             # if this is a "HEAD" request, we shouldn't return any data
             if self.method == b"HEAD":
@@ -963,9 +1031,9 @@
         self.sentLength = self.sentLength + len(data)
         if data:
             if self.chunked:
-                self.transport.writeSequence(toChunk(data))
+                self._transport.writeSequence(toChunk(data))
             else:
-                self.transport.write(data)
+                self._transport.write(data)
 
     def addCookie(self, k, v, expires=None, domain=None, path=None,
                   max_age=None, comment=None, secure=None, httpOnly=False):
@@ -1264,7 +1332,9 @@
         """
         if self._forceSSL:
             return True
-        transport = getattr(getattr(self, 'channel', None), 'transport', None)
+        transport = getattr(
+            getattr(self, '_channel', None), 'transport', None
+        )
         if interfaces.ISSLTransport(transport, None) is not None:
             return True
         return False
@@ -1339,13 +1409,31 @@
         Clean up anything which can't be useful anymore.
         """
         self._disconnected = True
-        self.channel = None
+        self._channel = None
         if self.content is not None:
             self.content.close()
         for d in self.notifications:
             d.errback(reason)
         self.notifications = []
 
+
+    def loseConnection(self):
+        """
+        Pass the loseConnection through to the underlying channel.
+        """
+        self._channel.loseConnection()
+
+
+    def _send100Continue(self):
+        """
+        Sends a "100 Continue" status code, where 100 Continue is supported.
+        """
+        if not self.queued:
+            self._channel._send100Continue()
+        else:
+            self._send100 = True
+
+
 Request.getClient = deprecated(
     Version("Twisted", 15, 0, 0),
     "Twisted Names to resolve hostnames")(Request.getClient)
@@ -1585,6 +1673,25 @@
 
 
 
+class _ChannelSendState(Names):
+    """
+    Defines a collection of states that indicate what portion of a HTTP
+    response has already been sent on L{HTTPChannel}. Used to enforce that
+    callers of methods on the L{HTTPChannel} are calling them at the
+    appropriate times and to prevent callers from sending invalid HTTP
+    responses.
+
+    The state of the channel send state goes from C{IDLE} (the previous
+    response is complete), optionally to C{SENT_100_CONTINUE}, and then to
+    C{SENT_HEADERS} When the response is complete, the state is reset to
+    C{IDLE}.
+    """
+    IDLE = NamedConstant()
+    SENT_100_CONTINUE = NamedConstant()
+    SENT_HEADERS = NamedConstant()
+
+
+
 class HTTPChannel(basic.LineReceiver, policies.TimeoutMixin):
     """
     A receiver for HTTP requests.
@@ -1627,6 +1734,7 @@
         # the request queue
         self.requests = []
         self._transferDecoder = None
+        self._sendState = _ChannelSendState.IDLE
 
 
     def connectionMade(self):
@@ -1642,7 +1750,7 @@
 
         self._receivedHeaderSize += len(line)
         if (self._receivedHeaderSize > self.totalHeadersSize):
-            _respondToBadRequestAndDisconnect(self.transport)
+            self._respondToBadRequestAndDisconnect()
             return
 
         if self.__first_line:
@@ -1666,13 +1774,13 @@
 
             parts = line.split()
             if len(parts) != 3:
-                _respondToBadRequestAndDisconnect(self.transport)
+                self._respondToBadRequestAndDisconnect()
                 return
             command, request, version = parts
             try:
                 command.decode("ascii")
             except UnicodeDecodeError:
-                _respondToBadRequestAndDisconnect(self.transport)
+                self._respondToBadRequestAndDisconnect()
                 return
 
             self._command = command
@@ -1726,7 +1834,7 @@
             try:
                 self.length = int(data)
             except ValueError:
-                _respondToBadRequestAndDisconnect(self.transport)
+                self._respondToBadRequestAndDisconnect()
                 self.length = None
                 return
             self._transferDecoder = _IdentityTransferDecoder(
@@ -1746,7 +1854,7 @@
 
         self._receivedHeaderCount += 1
         if self._receivedHeaderCount > self.maxHeaders:
-            _respondToBadRequestAndDisconnect(self.transport)
+            self._respondToBadRequestAndDisconnect()
             return
 
 
@@ -1777,7 +1885,7 @@
         try:
             self._transferDecoder.dataReceived(data)
         except _MalformedChunkedDataError:
-            _respondToBadRequestAndDisconnect(self.transport)
+            self._respondToBadRequestAndDisconnect()
 
 
     def allHeadersReceived(self):
@@ -1790,7 +1898,7 @@
         expectContinue = req.requestHeaders.getRawHeaders(b'expect')
         if (expectContinue and expectContinue[0].lower() == b'100-continue' and
             self._version == b'HTTP/1.1'):
-            req.transport.write(b"HTTP/1.1 100 Continue\r\n\r\n")
+            req._send100Continue()
 
 
     def checkPersistence(self, request, version):
@@ -1845,6 +1953,8 @@
         del self.requests[0]
 
         if self.persistent:
+            self._sendState = _ChannelSendState.IDLE
+
             # notify next request it can start writing
             if self.requests:
                 self.requests[0].noLongerQueued()
@@ -1864,20 +1974,157 @@
             request.connectionLost(reason)
 
 
+    def writeHeaders(self, version, code, reason, headers):
+        """
+        Called by L{Request} objects to write a complete set of HTTP headers to
+        a transport.
 
-def _respondToBadRequestAndDisconnect(transport):
-    """
-    This is a quick and dirty way of responding to bad requests.
+        @param version: The HTTP version in use.
+        @type version: L{bytes}
 
-    As described by HTTP standard we should be patient and accept the
-    whole request from the client before sending a polite bad request
-    response, even in the case when clients send tons of data.
+        @param code: The HTTP status code to write.
+        @type code: L{bytes}
+
+        @param reason: The HTTP reason phrase to write.
+        @type reason: L{bytes}
+
+        @param headers: The headers to write to the transport.
+        @type headers: L{twisted.web.http_headers.Headers}
+        """
+        assert self._sendState in (
+            _ChannelSendState.IDLE, _ChannelSendState.SENT_100_CONTINUE
+        )
+        responseLine = version + b" " + code + b" " + reason + b"\r\n"
+        headerSequence = [responseLine]
+        headerSequence.extend(
+            name + b': ' + value + b"\r\n" for name, value in headers
+        )
+        headerSequence.append(b"\r\n")
+        self.transport.writeSequence(headerSequence)
+        self._sendState = _ChannelSendState.SENT_HEADERS
 
-    @param transport: Transport handling connection to the client.
-    @type transport: L{interfaces.ITransport}
-    """
-    transport.write(b"HTTP/1.1 400 Bad Request\r\n\r\n")
-    transport.loseConnection()
+
+    def registerProducer(self, producer, streaming):
+        """
+        Register to receive data from a producer.
+
+        This sets self to be a consumer for a producer.  When this object runs
+        out of data (as when a send(2) call on a socket succeeds in moving the
+        last data from a userspace buffer into a kernelspace buffer), it will
+        ask the producer to resumeProducing().
+
+        For L{IPullProducer} providers, C{resumeProducing} will be called once
+        each time data is required.
+
+        For L{IPushProducer} providers, C{pauseProducing} will be called
+        whenever the write buffer fills up and C{resumeProducing} will only be
+        called when it empties.
+
+        @type producer: L{IProducer} provider
+        @param producer: The L{IProducer} that will be producing data.
+
+        @type streaming: L{bool}
+        @param streaming: C{True} if C{producer} provides L{IPushProducer},
+        C{False} if C{producer} provides L{IPullProducer}.
+
+        @raise RuntimeError: If a producer is already registered.
+
+        @return: C{None}
+        """
+        return self.transport.registerProducer(producer, streaming)
+
+
+    def unregisterProducer(self):
+        """
+        Stop consuming data from a producer, without disconnecting.
+
+        @return: C{None}
+        """
+        return self.transport.unregisterProducer()
+
+
+    def write(self, data):
+        """
+        Called by L{Request} objects to write response data.
+
+        @param data: The data chunk to write to the stream.
+        @type data: L{bytes}
+
+        @return: C{None}
+        """
+        assert self._sendState == _ChannelSendState.SENT_HEADERS
+        self.transport.write(data)
+
+
+    def writeSequence(self, iovec):
+        """
+        Write a list of strings to the HTTP response.
+
+        @param iovec: A list of byte strings to write to the stream.
+        @type data: L{list} of L{bytes}
+
+        @return: C{None}
+        """
+        assert self._sendState == _ChannelSendState.SENT_HEADERS
+        self.transport.writeSequence(iovec)
+
+
+    def getPeer(self):
+        """
+        Get the remote address of this connection.
+
+        @return: An L{IAddress} provider.
+        """
+        return self.transport.getPeer()
+
+
+    def getHost(self):
+        """
+        Get the local address of this connection.
+
+        @return: An L{IAddress} provider.
+        """
+        return self.transport.getHost()
+
+
+    def loseConnection(self):
+        """
+        Closes the connection. Will write any data that is pending to be sent
+        on the network, but if this response has not yet been written to the
+        network will not write anything.
+
+        @return: C{None}
+        """
+        # TODO: Does this need to be smarter, particularly about queued
+        # responses?
+        return self.transport.loseConnection()
+
+
+    def _send100Continue(self):
+        """
+        Sends a 100 Continue response, used to signal to clients that further
+        processing will be performed.
+        """
+        assert self._sendState == _ChannelSendState.IDLE
+        self.transport.write(b"HTTP/1.1 100 Continue\r\n\r\n")
+        self._sendState = _ChannelSendState.SENT_100_CONTINUE
+
+
+    def _respondToBadRequestAndDisconnect(self):
+        """
+        This is a quick and dirty way of responding to bad requests.
+
+        As described by HTTP standard we should be patient and accept the
+        whole request from the client before sending a polite bad request
+        response, even in the case when clients send tons of data.
+        """
+        # FIXME: There is nothing in this method to ensure that the response it
+        # sends is not intermingled with some other response body being written
+        # at the same time. Clearly this isn't the end of the world: the method
+        # has been arund a little while. Still, this should probably be
+        # refactored.
+        self.transport.write(b"HTTP/1.1 400 Bad Request\r\n\r\n")
+        self.transport.loseConnection()
 
 
 

Modified: branches/hide-request-transport-8191-6/twisted/web/server.py
==============================================================================
--- branches/hide-request-transport-8191-6/twisted/web/server.py	(original)
+++ branches/hide-request-transport-8191-6/twisted/web/server.py	Sat Apr 23 04:23:53 2016
@@ -110,10 +110,10 @@
 
     def getStateToCopyFor(self, issuer):
         x = self.__dict__.copy()
-        del x['transport']
+        del x['_transport']
         # XXX refactor this attribute out; it's from protocol
         # del x['server']
-        del x['channel']
+        del x['_channel']
         del x['content']
         del x['site']
         self.content.seek(0, 0)
@@ -164,7 +164,7 @@
         """
 
         # get site from channel
-        self.site = self.channel.site
+        self.site = self._channel.site
 
         # set various default headers
         self.setHeader(b'server', version)

Modified: branches/hide-request-transport-8191-6/twisted/web/test/requesthelper.py
==============================================================================
--- branches/hide-request-transport-8191-6/twisted/web/test/requesthelper.py	(original)
+++ branches/hide-request-transport-8191-6/twisted/web/test/requesthelper.py	Sat Apr 23 04:23:53 2016
@@ -53,6 +53,9 @@
         def registerProducer(self, producer, streaming):
             self.producers.append((producer, streaming))
 
+        def unregisterProducer(self):
+            pass
+
         def loseConnection(self):
             self.disconnected = True
 
@@ -71,6 +74,56 @@
         pass
 
 
+    def writeHeaders(self, version, code, reason, headers):
+        response_line = version + b" " + code + b" " + reason + b"\r\n"
+        headerSequence = [response_line]
+        headerSequence.extend(
+            name + b': ' + value + b"\r\n" for name, value in headers
+        )
+        headerSequence.append(b"\r\n")
+        self.transport.writeSequence(headerSequence)
+
+
+    def getPeer(self):
+        return self.transport.getPeer()
+
+
+    def getHost(self):
+        return self.transport.getHost()
+
+
+    def registerProducer(self, producer, streaming):
+        self.transport.registerProducer(producer, streaming)
+
+
+    def unregisterProducer(self):
+        self.transport.unregisterProducer()
+
+
+    def write(self, data):
+        self.transport.write(data)
+
+
+    def writeSequence(self, iovec):
+        self.transport.writeSequence(iovec)
+
+
+    def loseConnection(self):
+        self.transport.loseConnection()
+
+
+    def endRequest(self):
+        pass
+
+
+    @property
+    def producers(self):
+        return self.transport.producers
+
+    def _send100Continue(self):
+        self.transport.write(b"HTTP/1.1 100 Continue\r\n\r\n")
+
+
 
 class DummyRequest(object):
     """

Modified: branches/hide-request-transport-8191-6/twisted/web/test/test_http.py
==============================================================================
--- branches/hide-request-transport-8191-6/twisted/web/test/test_http.py	(original)
+++ branches/hide-request-transport-8191-6/twisted/web/test/test_http.py	Sat Apr 23 04:23:53 2016
@@ -338,6 +338,105 @@
 
 
 
+class ResponseWriteOrderingTests(unittest.TestCase):
+    requests = (
+        b"GET / HTTP/1.1\r\n"
+        b"Accept: text/html\r\n"
+        b"\r\n"
+        b"GET / HTTP/1.1\r\n"
+        b"\r\n")
+
+
+    def setUp(self):
+        self.transport = StringTransport()
+        self.channel = http.HTTPChannel()
+        self.channel.requestFactory = http.Request
+        self.channel.makeConnection(self.transport)
+        self.channel.dataReceived(self.requests)
+
+
+    def test_cannotWriteBeforeHeaders(self):
+        """
+        The L{HTTPChannel} forbids writes before headers are sent.
+        """
+        self.assertRaises(
+            AssertionError,
+            self.channel.write,
+            b"some response data",
+        )
+
+
+    def test_cannot100ContinueAfterHeaders(self):
+        """
+        The L{HTTPChannel} forbids sending 100 Continue after headers were
+        sent.
+        """
+        self.channel.writeHeaders(b"HTTP/1.1", b"200", b"OK", [])
+        self.assertRaises(
+            AssertionError,
+            self.channel._send100Continue,
+        )
+
+
+    def test_cannotSendHeadersTwice(self):
+        """
+        The L{HTTPChannel} forbids sending a header block twice.
+        """
+        self.channel.writeHeaders(b"HTTP/1.1", b"200", b"OK", [])
+        self.assertRaises(
+            AssertionError,
+            self.channel.writeHeaders,
+            b"HTTP/1.1",
+            b"200",
+            b"OK",
+            [],
+        )
+
+
+    def test_completingRequestAllows100ContinueAfterHeaders(self):
+        """
+        The L{HTTPChannel} allows a 100 Continue once a request is complete.
+        """
+        self.channel.writeHeaders(b"HTTP/1.1", b"200", b"OK", [])
+        self.channel.requestDone(self.channel.requests[0])
+        self.channel._send100Continue()
+
+        self.assertEqual(
+            self.transport.value(),
+            b"HTTP/1.1 200 OK\r\n\r\nHTTP/1.1 100 Continue\r\n\r\n",
+        )
+
+
+    def test_completingRequestAllowsNewHeaders(self):
+        """
+        The L{HTTPChannel} allows sending a new header block after headers are
+        complete.
+        """
+        self.channel.writeHeaders(b"HTTP/1.1", b"200", b"OK", [])
+        self.channel.requestDone(self.channel.requests[0])
+        self.channel.writeHeaders(b"HTTP/1.1", b"204", b"No Content", [])
+
+        self.assertEqual(
+            self.transport.value(),
+            b"HTTP/1.1 200 OK\r\n\r\nHTTP/1.1 204 No Content\r\n\r\n",
+        )
+
+
+    def test_canWriteAfterHeaders(self):
+        """
+        The L{HTTPChannel} allows writing data after headers.
+        """
+        headers = [(b'Content-Length', b'5')]
+        self.channel.writeHeaders(b"HTTP/1.1", b"200", b"OK", headers)
+        self.channel.write(b'hello')
+
+        self.assertEqual(
+            self.transport.value(),
+            b"HTTP/1.1 200 OK\r\nContent-Length: 5\r\n\r\nhello",
+        )
+
+
+
 class HTTPLoopbackTests(unittest.TestCase):
 
     expectedHeaders = {b'request': b'/foo/bar',
@@ -1262,7 +1361,7 @@
                 content.append(self.content.read())
                 method.append(self.method)
                 path.append(self.path)
-                decoder.append(self.channel._transferDecoder)
+                decoder.append(self._channel._transferDecoder)
                 testcase.didRequest = True
                 self.finish()
 
@@ -1742,10 +1841,12 @@
         For an HTTP 1.0 request, L{http.Request.write} sends an HTTP 1.0
         Response-Line and whatever response headers are set.
         """
-        req = http.Request(DummyChannel(), False)
+        channel = DummyChannel()
         trans = StringTransport()
+        channel.transport = trans
+        req = http.Request(channel, False)
 
-        req.transport = trans
+        req._transport = trans
 
         req.setResponseCode(200)
         req.clientproto = b"HTTP/1.0"
@@ -1764,10 +1865,12 @@
         L{http.Request.write} casts non-bytes header value to bytes
         transparently.
         """
-        req = http.Request(DummyChannel(), False)
+        channel = DummyChannel()
         trans = StringTransport()
+        channel.transport = trans
+        req = http.Request(channel, False)
 
-        req.transport = trans
+        req._transport = trans
 
         req.setResponseCode(200)
         req.clientproto = b"HTTP/1.0"
@@ -1796,10 +1899,12 @@
         Response-Line, whatever response headers are set, and uses chunked
         encoding for the response body.
         """
-        req = http.Request(DummyChannel(), False)
+        channel = DummyChannel()
         trans = StringTransport()
+        channel.transport = trans
+        req = http.Request(channel, False)
 
-        req.transport = trans
+        req._transport = trans
 
         req.setResponseCode(200)
         req.clientproto = b"HTTP/1.1"
@@ -1821,10 +1926,12 @@
         L{http.Request.write} sends an HTTP Response-Line, whatever response
         headers are set, and a last-modified header with that time.
         """
-        req = http.Request(DummyChannel(), False)
+        channel = DummyChannel()
         trans = StringTransport()
+        channel.transport = trans
+        req = http.Request(channel, False)
 
-        req.transport = trans
+        req._transport = trans
 
         req.setResponseCode(200)
         req.clientproto = b"HTTP/1.0"
@@ -1979,7 +2086,7 @@
         # Then something goes wrong and content should get closed.
         req.connectionLost(Failure(ConnectionLost("Finished")))
         self.assertTrue(content.closed)
-        self.assertIdentical(req.channel, None)
+        self.assertIdentical(req._channel, None)
 
 
     def test_registerProducerTwiceFails(self):
@@ -2032,7 +2139,7 @@
         # This is a roundabout assertion: http.StringTransport doesn't
         # implement registerProducer, so Request.registerProducer can't have
         # tried to call registerProducer on the transport.
-        self.assertIsInstance(req.transport, http.StringTransport)
+        self.assertIsInstance(req._transport, http.StringTransport)
 
 
     def test_registerProducerWhenQueuedDoesntRegisterPullProducer(self):
@@ -2051,7 +2158,7 @@
         # This is a roundabout assertion: http.StringTransport doesn't
         # implement registerProducer, so Request.registerProducer can't have
         # tried to call registerProducer on the transport.
-        self.assertIsInstance(req.transport, http.StringTransport)
+        self.assertIsInstance(req._transport, http.StringTransport)
 
 
     def test_registerProducerWhenNotQueuedRegistersPushProducer(self):
@@ -2063,7 +2170,7 @@
         req = http.Request(DummyChannel(), False)
         producer = DummyProducer()
         req.registerProducer(producer, True)
-        self.assertEqual([(producer, True)], req.transport.producers)
+        self.assertEqual([(producer, True)], req._transport.producers)
 
 
     def test_registerProducerWhenNotQueuedRegistersPullProducer(self):
@@ -2075,7 +2182,7 @@
         req = http.Request(DummyChannel(), False)
         producer = DummyProducer()
         req.registerProducer(producer, False)
-        self.assertEqual([(producer, False)], req.transport.producers)
+        self.assertEqual([(producer, False)], req._transport.producers)
 
 
     def test_connectionLostNotification(self):
@@ -2087,7 +2194,7 @@
         request = http.Request(d, True)
         finished = request.notifyFinish()
         request.connectionLost(Failure(ConnectionLost("Connection done")))
-        self.assertIdentical(request.channel, None)
+        self.assertIdentical(request._channel, None)
         return self.assertFailure(finished, ConnectionLost)
 
 
@@ -2183,10 +2290,10 @@
         producer from the request and the request's transport.
         """
         req = http.Request(DummyChannel(), False)
-        req.transport = StringTransport()
+        req._transport = StringTransport()
         req.registerProducer(DummyProducer(), False)
         req.unregisterProducer()
-        self.assertEqual((None, None), (req.producer, req.transport.producer))
+        self.assertEqual((None, None), (req.producer, req._transport.producer))
 
 
     def test_unregisterNonQueuedStreamingProducer(self):
@@ -2195,10 +2302,10 @@
         producer from the request and the request's transport.
         """
         req = http.Request(DummyChannel(), False)
-        req.transport = StringTransport()
+        req._transport = StringTransport()
         req.registerProducer(DummyProducer(), True)
         req.unregisterProducer()
-        self.assertEqual((None, None), (req.producer, req.transport.producer))
+        self.assertEqual((None, None), (req.producer, req._transport.producer))
 
 
     def test_unregisterQueuedNonStreamingProducer(self):
@@ -2263,6 +2370,141 @@
                       factory.logFile.getvalue())
 
 
+    def test_transportDeprecated(self):
+        """
+        L{http.Request.transport} will raise a L{DeprecationWarning} if
+        accessed.
+        """
+        channel = DummyChannel()
+        trans = StringTransport()
+        channel.transport = trans
+        req = http.Request(channel, False)
+
+        # This should fire a warning.
+        req.transport
+
+        warnings = self.flushWarnings([self.test_transportDeprecated])
+        self.assertEqual(1, len(warnings))
+        self.assertEqual(warnings[0]['category'], DeprecationWarning)
+        self.assertEqual(
+            warnings[0]['message'],
+            "twisted.web.http.Request.transport was deprecated in "
+            "Twisted 16.2.0. Call directly into the Request object instead."
+        )
+
+
+    def test_transportSetDeprecated(self):
+        """
+        L{http.Request.transport} will raise a L{DeprecationWarning} if set.
+        """
+        channel = DummyChannel()
+        trans = StringTransport()
+        channel.transport = trans
+        req = http.Request(channel, False)
+
+        # This should fire a warning.
+        req.transport = object()
+
+        warnings = self.flushWarnings([self.test_transportSetDeprecated])
+        self.assertEqual(1, len(warnings))
+        self.assertEqual(warnings[0]['category'], DeprecationWarning)
+        self.assertEqual(
+            warnings[0]['message'],
+            "twisted.web.http.Request.transport was deprecated in "
+            "Twisted 16.2.0. Call directly into the Request object instead."
+        )
+
+
+    def test_channelDeprecated(self):
+        """
+        L{http.Request.channel} will raise a L{DeprecationWarning} if
+        accessed.
+        """
+        channel = DummyChannel()
+        trans = StringTransport()
+        channel.transport = trans
+        req = http.Request(channel, False)
+
+        # This should fire a warning.
+        req.channel
+
+        warnings = self.flushWarnings([self.test_channelDeprecated])
+        self.assertEqual(1, len(warnings))
+        self.assertEqual(warnings[0]['category'], DeprecationWarning)
+        self.assertEqual(
+            warnings[0]['message'],
+            "twisted.web.http.Request.channel was deprecated in Twisted "
+            "16.2.0. Call directly into the Request object instead."
+        )
+
+
+    def test_channelSetDeprecated(self):
+        """
+        L{http.Request.channel} will raise a L{DeprecationWarning} if set.
+        """
+        channel = DummyChannel()
+        trans = StringTransport()
+        channel.transport = trans
+        req = http.Request(channel, False)
+
+        # This should fire a warning.
+        req.channel = object()
+
+        warnings = self.flushWarnings([self.test_channelSetDeprecated])
+        self.assertEqual(1, len(warnings))
+        self.assertEqual(warnings[0]['category'], DeprecationWarning)
+        self.assertEqual(
+            warnings[0]['message'],
+            "twisted.web.http.Request.channel was deprecated in Twisted "
+            "16.2.0. Call directly into the Request object instead."
+        )
+
+
+    def test_sendHeadersWhenUnqueued(self):
+        """
+        Unqueueing a L{Request} sends headers properly.
+        """
+        transport = StringTransport()
+        channel = DummyChannel()
+        channel.transport = transport
+        request = http.Request(channel, True)
+        request.gotLength(123)
+        request.requestReceived(b"GET", b"/", b"HTTP/1.1")
+        request.write(b'test data')
+
+        self.assertEqual(transport.value(), b'')
+
+        request.noLongerQueued()
+
+        self.assertEqual(
+            transport.value(),
+            b"HTTP/1.1 200 OK\r\nTransfer-Encoding: chunked\r\n\r\n9\r\n"
+            b"test data\r\n"
+        )
+
+
+    def test_send100ContinueWhenUnqueued(self):
+        """
+        Unqueueing a L{Request} sends any 100-Continue repsonse properly.
+        """
+        transport = StringTransport()
+        channel = DummyChannel()
+        channel.transport = transport
+        request = http.Request(channel, True)
+        request.gotLength(123)
+        request.requestReceived(b"GET", b"/", b"HTTP/1.1")
+        request._send100Continue()
+
+        self.assertEqual(transport.value(), b'')
+
+        request.noLongerQueued()
+
+        self.assertEqual(
+            transport.value(),
+            b"HTTP/1.1 100 Continue\r\n\r\n"
+        )
+
+
 
 class MultilineHeadersTests(unittest.TestCase):
     """

Modified: branches/hide-request-transport-8191-6/twisted/web/test/test_proxy.py
==============================================================================
--- branches/hide-request-transport-8191-6/twisted/web/test/test_proxy.py	(original)
+++ branches/hide-request-transport-8191-6/twisted/web/test/test_proxy.py	Sat Apr 23 04:23:53 2016
@@ -123,6 +123,14 @@
         self.lostReason = reason
 
 
+    def getPeer(self):
+        return self.transport.getPeer()
+
+
+    def getHost(self):
+        return self.transport.getHost()
+
+
 
 class ProxyClientTests(TestCase):
     """

Modified: branches/hide-request-transport-8191-6/twisted/web/test/test_web.py
==============================================================================
--- branches/hide-request-transport-8191-6/twisted/web/test/test_web.py	(original)
+++ branches/hide-request-transport-8191-6/twisted/web/test/test_web.py	Sat Apr 23 04:23:53 2016
@@ -554,8 +554,8 @@
         fail = failure.Failure(Exception("Oh no!"))
         request.processingFailed(fail)
 
-        self.assertNotIn(b"Oh no!", request.transport.getvalue())
-        self.assertIn(b"Processing Failed", request.transport.getvalue())
+        self.assertNotIn(b"Oh no!", request._transport.getvalue())
+        self.assertIn(b"Processing Failed", request._transport.getvalue())
 
         # Since we didn't "handle" the exception, flush it to prevent a test
         # failure
@@ -574,7 +574,7 @@
         fail = failure.Failure(Exception("Oh no!"))
         request.processingFailed(fail)
 
-        self.assertIn(b"Oh no!", request.transport.getvalue())
+        self.assertIn(b"Oh no!", request._transport.getvalue())
 
         # Since we didn't "handle" the exception, flush it to prevent a test
         # failure
@@ -594,7 +594,7 @@
         fail = failure.Failure(Exception(u"\u2603"))
         request.processingFailed(fail)
 
-        self.assertIn(b"&#9731;", request.transport.getvalue())
+        self.assertIn(b"&#9731;", request._transport.getvalue())
 
         # Since we didn't "handle" the exception, flush it to prevent a test
         # failure
@@ -823,11 +823,11 @@
     def testGoodMethods(self):
         req = self._getReq()
         req.requestReceived(b'GET', b'/newrender', b'HTTP/1.0')
-        self.assertEqual(req.transport.getvalue().splitlines()[-1], b'hi hi')
+        self.assertEqual(req._transport.getvalue().splitlines()[-1], b'hi hi')
 
         req = self._getReq()
         req.requestReceived(b'HEH', b'/newrender', b'HTTP/1.0')
-        self.assertEqual(req.transport.getvalue().splitlines()[-1], b'ho ho')
+        self.assertEqual(req._transport.getvalue().splitlines()[-1], b'ho ho')
 
     def testBadMethods(self):
         req = self._getReq()
@@ -855,7 +855,7 @@
         req = self._getReq()
         req.requestReceived(b'HEAD', b'/newrender', b'HTTP/1.0')
         self.assertEqual(req.code, 200)
-        self.assertEqual(-1, req.transport.getvalue().find(b'hi hi'))
+        self.assertEqual(-1, req._transport.getvalue().find(b'hi hi'))
 
 
     def test_unsupportedHead(self):
@@ -866,7 +866,7 @@
         resource = HeadlessResource()
         req = self._getReq(resource)
         req.requestReceived(b"HEAD", b"/newrender", b"HTTP/1.0")
-        headers, body = req.transport.getvalue().split(b'\r\n\r\n')
+        body = req._transport.getvalue()
         self.assertEqual(req.code, 200)
         self.assertEqual(body, b'')
 
@@ -887,7 +887,7 @@
 
         request.requestReceived(b"GET", b"/newrender", b"HTTP/1.0")
 
-        headers, body = request.transport.getvalue().split(b'\r\n\r\n')
+        body = request._transport.getvalue()
         self.assertEqual(request.code, 500)
         expected = [
             '',
@@ -988,7 +988,7 @@
         req.requestReceived(b'POST', b'/gettableresource?'
                             b'value=<script>bad', b'HTTP/1.0')
         self.assertEqual(req.code, 405)
-        renderedPage = req.transport.getvalue()
+        renderedPage = req._transport.getvalue()
         self.assertNotIn(b"<script>bad", renderedPage)
         self.assertIn(b'&lt;script&gt;bad', renderedPage)
 
@@ -1003,7 +1003,7 @@
         req = self._getReq()
         req.requestReceived(b'<style>bad', b'/gettableresource', b'HTTP/1.0')
         self.assertEqual(req.code, 501)
-        renderedPage = req.transport.getvalue()
+        renderedPage = req._transport.getvalue()
         self.assertNotIn(b"<style>bad", renderedPage)
         self.assertIn(b'&lt;style&gt;bad', renderedPage)
 

Modified: branches/hide-request-transport-8191-6/twisted/web/wsgi.py
==============================================================================
--- branches/hide-request-transport-8191-6/twisted/web/wsgi.py	(original)
+++ branches/hide-request-transport-8191-6/twisted/web/wsgi.py	Sat Apr 23 04:23:53 2016
@@ -506,7 +506,7 @@
             def wsgiError(started, type, value, traceback):
                 err(Failure(value, type, traceback), "WSGI application error")
                 if started:
-                    self.request.transport.loseConnection()
+                    self.request.loseConnection()
                 else:
                     self.request.setResponseCode(INTERNAL_SERVER_ERROR)
                     self.request.finish()