Ticket #5192: 100Continue.patch

File 100Continue.patch, 12.1 KB (added by darfire, 5 years ago)

Adds 100-Continue to HTTP11ClientProtocol

  • twisted/web/test/test_newclient.py

     
    793793    """
    794794    method = 'GET'
    795795    stopped = False
     796    headers = Headers()
    796797
    797798    def writeTo(self, transport):
    798799        self.finished = Deferred()
     
    811812    returns a succeeded L{Deferred}.  This vaguely emulates the behavior of a
    812813    L{Request} with no body producer.
    813814    """
     815    headers = Headers()
    814816    def writeTo(self, transport):
    815817        transport.write('SOME BYTES')
    816818        return succeed(None)
    817819
    818820
     821class BufferProducer(object):
     822    """
     823    A body producer that outputs a string with a known length.
     824    """
     825    implements(IBodyProducer)
    819826
     827    def __init__(self, data):
     828        self.data = data
     829        self.length = len(data)
     830
     831    def startProducing(self, consumer):
     832        consumer.write(self.data)
     833        return succeed(None)
     834
     835    def pauseProducing(self):
     836        pass
     837
     838    def stopProducing(self):
     839        pass
     840
    820841class HTTP11ClientProtocolTests(TestCase):
    821842    """
    822843    Tests for the HTTP 1.1 client protocol implementation,
     
    879900        L{RequestGenerationFailed} wrapping the underlying failure.
    880901        """
    881902        class BrokenRequest:
     903            headers = Headers()
    882904            def writeTo(self, transport):
    883905                return fail(ArbitraryException())
    884906
     
    901923        a L{Failure} of L{RequestGenerationFailed} wrapping that exception.
    902924        """
    903925        class BrokenRequest:
     926            headers = Headers()
    904927            def writeTo(self, transport):
    905928                raise ArbitraryException()
    906929
     
    13061329        return assertResponseFailed(self, testResult,
    13071330                                    [ConnectionAborted, _DataLoss])
    13081331
     1332    def test_expect100ContinueGot100Continue(self):
     1333        """
     1334        When we expect 100-Continue and we receive an 100-Continue L{Response}
     1335        the user writes the L{Request} body and receives back the second(final)
     1336        L{Response}.
     1337        """
     1338        producer = BufferProducer('X' * 10)
    13091339
     1340        headers = Headers({'host': ['example.com'],
     1341            'expect': ['100-Continue']})
    13101342
     1343        d = self.protocol.request(Request('POST', '/foo', headers, producer))
     1344
     1345        self.transport.clear()
     1346
     1347        def cb100Continue(response):
     1348            self.assertEqual(response.code, 200)
     1349
     1350        d.addCallback(cb100Continue)
     1351
     1352        self.protocol.dataReceived(
     1353                "HTTP/1.1 100 Continue\r\n"
     1354                "Content-Length: 3\r\n"
     1355                "\r\n"
     1356                '123'
     1357                )
     1358
     1359        self.protocol.dataReceived(
     1360                "HTTP/1.1 200 OK\r\n"
     1361                "Content-Length: 0\r\n"
     1362                "\r\n"
     1363                )
     1364
     1365        self.assertEqual(self.transport.value(), 'X' * 10)
     1366
     1367        return d
     1368
     1369
     1370    def test_expect100ContinueGotFinalStatus(self):
     1371        """
     1372        When we expect 100-Continue and we receive a final status L{Response}
     1373        we don't write the L{Request} body anymore and the user receives the
     1374        first L{Response}.
     1375        """
     1376        producer = BufferProducer('X' * 10)
     1377
     1378        headers = Headers({'host': ['example.com'], 'expect': ['100-Continue']})
     1379
     1380        d = self.protocol.request(Request('POST', '/foo', headers, producer))
     1381
     1382        self.transport.clear()
     1383
     1384        def cb100Continue(response):
     1385            self.assertEqual(response.code, 200)
     1386
     1387        d.addCallback(cb100Continue)
     1388
     1389
     1390        self.protocol.dataReceived(
     1391                "HTTP/1.1 200 OK\r\n"
     1392                "Content-Length: 0\r\n"
     1393                "\r\n"
     1394                )
     1395
     1396        self.assertEqual(self.transport.value(), '')
     1397
     1398        return d
     1399
     1400
     1401
    13111402class StringProducer:
    13121403    """
    13131404    L{StringProducer} is a dummy body producer.
  • twisted/web/_newclient.py

     
    3939from twisted.internet.defer import Deferred, succeed, fail, maybeDeferred
    4040from twisted.internet.protocol import Protocol
    4141from twisted.protocols.basic import LineReceiver
     42from twisted.protocols.wire import Discard
    4243from twisted.web.iweb import UNKNOWN_LENGTH, IResponse
    4344from twisted.web.http_headers import Headers
    4445from twisted.web.http import NO_CONTENT, NOT_MODIFIED
     
    559560        transport.writeSequence(requestLines)
    560561
    561562
    562     def _writeToChunked(self, transport):
     563    def _writeBodyToChunked(self, transport):
    563564        """
    564         Write this request to the given transport using chunked
     565        Write this request's body to the given transport using chunked
    565566        transfer-encoding to frame the body.
    566567        """
    567         self._writeHeaders(transport, 'Transfer-Encoding: chunked\r\n')
    568568        encoder = ChunkedEncoder(transport)
    569569        encoder.registerProducer(self.bodyProducer, True)
    570570        d = self.bodyProducer.startProducing(encoder)
     
    583583        return d
    584584
    585585
    586     def _writeToContentLength(self, transport):
     586    def _writeBodyToContentLength(self, transport):
    587587        """
    588         Write this request to the given transport using content-length to frame
    589         the body.
     588        Write this request's body to the given transport using content-length
     589        to frame the body.
    590590        """
    591         self._writeHeaders(
    592             transport,
    593             'Content-Length: %d\r\n' % (self.bodyProducer.length,))
    594591
    595592        # This Deferred is used to signal an error in the data written to the
    596593        # encoder below.  It can only errback and it will only do so before too
     
    702699            been completely written to the transport or with a L{Failure} if
    703700            there is any problem generating the request bytes.
    704701        """
     702
     703        self.writeHeadersTo(transport)
     704        return self.writeBodyTo(transport)
     705
     706    def writeHeadersTo(self, transport):
     707        """
     708        Format this L{Request}'s headers as HTTP/1.1 and write them
     709        synchronously to the given transport
     710        """
     711        TEorCL = None
    705712        if self.bodyProducer is not None:
    706713            if self.bodyProducer.length is UNKNOWN_LENGTH:
    707                 return self._writeToChunked(transport)
     714                TEorCL = "Transfer-Encoding: chunked\r\n"
    708715            else:
    709                 return self._writeToContentLength(transport)
     716                TEorCL = 'Content-Length: %d\r\n' % (self.bodyProducer.length,)
     717
     718        self._writeHeaders(transport, TEorCL)
     719
     720    def writeBodyTo(self, transport):
     721        """
     722        Write this L{Request}'s body to the given transport, framing it
     723        according to the given headers('Transport-Encoding' or
     724        'Content-Length').
     725
     726        @return: A L{Deferred} which fires with C{None} when the request has
     727            been completely written the transport or with a L{Failure} if there
     728            is any problem generating the request body bytes. If bodyProducer
     729            is None the returned L{Deferred} is already fired.
     730        """
     731        if self.bodyProducer is not None:
     732            if self.bodyProducer.length is UNKNOWN_LENGTH:
     733                return self._writeBodyToChunked(transport)
     734            else:
     735                return self._writeBodyToContentLength(transport)
    710736        else:
    711             self._writeHeaders(transport, None)
    712737            return succeed(None)
    713738
    714 
    715739    def stopWriting(self):
    716740        """
    717741        Stop writing this request to the transport.  This can only be called
     
    11511175            self._producer.pauseProducing()
    11521176
    11531177
     1178class DiscardWithDeferred(Protocol):
     1179    """
     1180    A L{Protocol} that discards all received data and that fires a L{Deferred}
     1181    when all data has been received.
    11541182
     1183    @ivar finishedDeferred: L{Deferred} which fires with C{None} when all data
     1184        has been received and with L{Failure} on error.
     1185
     1186    """
     1187
     1188    def __init__(self):
     1189        self.finishedDeferred = Deferred()
     1190
     1191    def dataReceived(self, data):
     1192        pass
     1193
     1194    def connectionLost(self, reason):
     1195        if reason.type == ResponseDone:
     1196            self.finishedDeferred.callback(None)
     1197        else:
     1198            self.finishedDeferred.errback(reason)
     1199
     1200
    11551201class HTTP11ClientProtocol(Protocol):
    11561202    """
    11571203    L{HTTP11ClientProtocol} is an implementation of the HTTP 1.1 client
     
    12361282            return fail(RequestNotSent())
    12371283
    12381284        self._state = 'TRANSMITTING'
    1239         _requestDeferred = maybeDeferred(request.writeTo, self.transport)
    1240         self._finishedRequest = Deferred()
    12411285
    12421286        # Keep track of the Request object in case we need to call stopWriting
    12431287        # on it.
    12441288        self._currentRequest = request
    12451289
    1246         self._transportProxy = TransportProxyProducer(self.transport)
    1247         self._parser = HTTPClientParser(request, self._finishResponse)
    1248         self._parser.makeConnection(self._transportProxy)
    1249         self._responseDeferred = self._parser._responseDeferred
     1290        self._finishedRequest = Deferred()
    12501291
     1292        if request.headers.hasHeader('expect'):
     1293            expectations = request.headers.getRawHeaders('expect')
     1294            _expects100Continue = '100-continue' in [x.lower() for x in expectations]
     1295        else:
     1296            _expects100Continue = False
     1297
     1298        if _expects100Continue:
     1299            # This is synchronous, so jump right into WAITING
     1300            request.writeHeadersTo(self.transport)
     1301
     1302            self._state = 'WAITING'
     1303
     1304            self._setupParser(request)
     1305
     1306            _100ContinueResponseDeferred = self._parser._responseDeferred
     1307
     1308            self._responseDeferred = Deferred()
     1309
     1310            _requestDeferred = Deferred()
     1311
     1312            def ebFirstResponse(err):
     1313                # Tell the producer we don't need its body and forward the
     1314                # error
     1315                request.stopWriting()
     1316                self._finishedRequest.errback(err)
     1317
     1318            def cbFirstResponse(response):
     1319                if response.code == 100:
     1320                    # Read and discard the response body
     1321
     1322                    discarder = DiscardWithDeferred()
     1323
     1324                    response.deliverBody(discarder)
     1325
     1326
     1327                    def cb100ContinueBody(ignored):
     1328                        # Start sending the request body
     1329
     1330                        self._state = 'TRANSMITTING'
     1331
     1332                        self._disconnectParser(None)
     1333
     1334                        self._setupParser(request)
     1335
     1336                        _requestBodyDeferred = maybeDeferred(
     1337                                request.writeBodyTo, self.transport)
     1338
     1339                        _requestBodyDeferred.chainDeferred(_requestDeferred)
     1340
     1341                        self._parser._responseDeferred.chainDeferred(
     1342                                self._responseDeferred)
     1343
     1344                    discarder.finishedDeferred.addCallbacks(cb100ContinueBody,
     1345                            ebFirstResponse)
     1346
     1347                else:
     1348                    request.stopWriting()
     1349                    self._finishedRequest.callback(response)
     1350
     1351            _100ContinueResponseDeferred.addCallbacks(
     1352                    cbFirstResponse, ebFirstResponse)
     1353
     1354        else:
     1355            _requestDeferred = maybeDeferred(request.writeTo, self.transport)
     1356
     1357            self._setupParser(request)
     1358
     1359            self._responseDeferred = self._parser._responseDeferred
     1360
    12511361        def cbRequestWrotten(ignored):
    12521362            if self._state == 'TRANSMITTING':
    12531363                self._state = 'WAITING'
     
    12721382        return self._finishedRequest
    12731383
    12741384
     1385    def _setupParser(self, request):
     1386        """
     1387        Setup a L{HTTPClientParser} for a L{Response} to a L{Request}. If this
     1388        is not the first parser associated with this protocol, call
     1389        L{HTTP11ClientProtocol._disconnectParser} first.
     1390
     1391        @param request: L{Request} waiting for a L{Response}
     1392        """
     1393        self._transportProxy = TransportProxyProducer(self.transport)
     1394        self._parser = HTTPClientParser(request, self._finishResponse)
     1395        self._parser.makeConnection(self._transportProxy)
     1396
     1397
    12751398    def _finishResponse(self, rest):
    12761399        """
    12771400        Called by an L{HTTPClientParser} to indicate that it has parsed a