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"☃", request.transport.getvalue())
+ self.assertIn(b"☃", 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'<script>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'<style>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()