Ticket #3420: webclient.diff
| File webclient.diff, 12.5 KB (added by yasusii, 3 years ago) |
|---|
-
twisted/web/_newclient.py
diff -ru Twisted-10.1.0.orig/twisted/web/_newclient.py Twisted-10.1.0/twisted/web/_newclient.py
old new 524 524 @ivar bodyProducer: C{None} or an L{IBodyProducer} provider which 525 525 produces the content body to send to the remote HTTP server. 526 526 """ 527 def __init__(self, method, uri, headers, bodyProducer ):527 def __init__(self, method, uri, headers, bodyProducer, persistent=False): 528 528 self.method = method 529 529 self.uri = uri 530 530 self.headers = headers 531 531 self.bodyProducer = bodyProducer 532 self.persistent = persistent 532 533 533 534 534 535 def _writeHeaders(self, transport, TEorCL): … … 542 543 requestLines = [] 543 544 requestLines.append( 544 545 '%s %s HTTP/1.1\r\n' % (self.method, self.uri)) 545 requestLines.append('Connection: close\r\n') 546 if not self.persistent: 547 requestLines.append('Connection: close\r\n') 546 548 if TEorCL is not None: 547 549 requestLines.append(TEorCL) 548 550 for name, values in self.headers.getAllRawHeaders(): … … 1239 1241 """ 1240 1242 _state = 'QUIESCENT' 1241 1243 _parser = None 1244 persistent = False 1245 1246 @property 1247 def state(self): 1248 return self._state 1242 1249 1243 1250 def request(self, request): 1244 1251 """ … … 1259 1266 may errback with L{RequestNotSent} if it is not possible to send 1260 1267 any more requests using this L{HTTP11ClientProtocol}. 1261 1268 """ 1269 self.persistent = request.persistent 1262 1270 if self._state != 'QUIESCENT': 1263 1271 return fail(RequestNotSent()) 1264 1272 … … 1278 1286 def cbRequestWrotten(ignored): 1279 1287 if self._state == 'TRANSMITTING': 1280 1288 self._state = 'WAITING' 1281 # XXX We're stuck in WAITING until we lose the connection now.1282 # This will be wrong when persistent connections are supported.1283 # See #3420 for persistent connections.1284 1285 1289 self._responseDeferred.chainDeferred(self._finishedRequest) 1286 1290 1287 1291 def ebRequestWriting(err): … … 1307 1311 the L{HTTPClientParser} which were not part of the response it 1308 1312 was parsing. 1309 1313 """ 1310 # XXX this is because Connection: close is hard-coded above, probably 1311 # will want to change that at some point. Either the client or the 1312 # server can control this. 1313 1314 # XXX If the connection isn't being closed at this point, it's 1315 # important to make sure the transport isn't paused (after _giveUp, 1316 # or inside it, or something - after the parser can no longer touch 1317 # the transport) 1314 assert self._state in ('WAITING', 'TRANSMITTING') 1318 1315 1319 # For both of the above, see #3420 for persistent connections.1320 1321 if self._state == 'TRANSMITTING':1316 if self._state == 'WAITING': 1317 self._state = 'QUIESCENT' 1318 else: 1322 1319 # The server sent the entire response before we could send the 1323 1320 # whole request. That sucks. Oh well. Fire the request() 1324 1321 # Deferred with the response. But first, make sure that if the … … 1327 1324 self._state = 'TRANSMITTING_AFTER_RECEIVING_RESPONSE' 1328 1325 self._responseDeferred.chainDeferred(self._finishedRequest) 1329 1326 1330 self._giveUp(Failure(ConnectionDone("synthetic!"))) 1327 reason = ConnectionDone("synthetic!") 1328 self._giveUp(Failure(reason)) 1331 1329 1332 1330 1333 1331 def _disconnectParser(self, reason): -
twisted/web/client.py
diff -ru Twisted-10.1.0.orig/twisted/web/client.py Twisted-10.1.0/twisted/web/client.py
old new 628 628 @since: 9.0 629 629 """ 630 630 _protocol = HTTP11ClientProtocol 631 maxConnections = 2 # RFC 2616: A single-user client SHOULD NOT 632 # maintain more than 2 connections with any 633 # server or proxy. 631 634 632 def __init__(self, reactor, contextFactory=WebClientContextFactory()): 635 def __init__(self, reactor, contextFactory=WebClientContextFactory(), 636 persistent=False): 633 637 self._reactor = reactor 634 638 self._contextFactory = contextFactory 639 self.persistent = persistent 640 self._semaphores = {} 641 self._protocolCache = {} 635 642 636 643 637 644 def _wrapContextFactory(self, host, port): … … 704 711 @rtype: L{Deferred} 705 712 """ 706 713 scheme, host, port, path = _parse(uri) 707 d = self._connect(scheme, host, port)708 714 if headers is None: 709 715 headers = Headers() 710 716 if not headers.hasHeader('host'): … … 713 719 headers = Headers(dict(headers.getAllRawHeaders())) 714 720 headers.addRawHeader( 715 721 'host', self._computeHostValue(scheme, host, port)) 722 if self.persistent: 723 sem = self._semaphores.get((scheme, host, port)) 724 if sem is None: 725 sem = defer.DeferredSemaphore(self.maxConnections) 726 self._semaphores[scheme, host, port] = sem 727 return sem.run(self._request, method, scheme, host, port, path, 728 headers, bodyProducer) 729 else: 730 return self._request( 731 method, scheme, host, port, path, headers, bodyProducer) 732 733 734 def _request(self, method, scheme, host, port, path, headers, bodyProducer): 735 """ 736 Issue a new request. 737 738 @param method: The request method to send. 739 @type method: C{str} 740 741 @param uri: The request URI send. 742 @type uri: C{str} 743 744 @param headers: The request headers to send. If no I{Host} header is 745 included, one will be added based on the request URI. 746 @type headers: L{Headers} 747 748 @param bodyProducer: An object which will produce the request body or, 749 if the request body is to be empty, L{None}. 750 @type bodyProducer: L{IBodyProducer} provider 751 752 @return: A L{Deferred} which fires with the result of the request (a 753 L{Response} instance), or fails if there is a problem setting up a 754 connection over which to issue the request. It may also fail with 755 L{SchemeNotSupported} if the scheme of the given URI is not 756 supported. 757 @rtype: L{Deferred} 758 """ 759 protos = self._protocolCache.setdefault((scheme, host, port), []) 760 while protos: 761 # connection exists 762 p = protos.pop(0) 763 if p.state == 'QUIESCENT': 764 d = defer.succeed(p) 765 break 766 else: 767 # new connection 768 d = self._connect(scheme, host, port) 716 769 def cbConnected(proto): 717 return proto.request(Request(method, path, headers, bodyProducer)) 770 def cbRequest(response): 771 if self.persistent: 772 protos.append(proto) 773 return response 774 req = Request(method, path, headers, bodyProducer, 775 persistent=self.persistent) 776 rd = proto.request(req) 777 rd.addCallback(cbRequest) 778 return rd 718 779 d.addCallback(cbConnected) 719 780 return d 720 781 -
twisted/web/test/test_newclient.py
diff -ru Twisted-10.1.0.orig/twisted/web/test/test_newclient.py Twisted-10.1.0/twisted/web/test/test_newclient.py
old new 775 775 """ 776 776 method = 'GET' 777 777 stopped = False 778 persistent = False 778 779 779 780 def writeTo(self, transport): 780 781 self.finished = Deferred() … … 793 794 returns a succeeded L{Deferred}. This vaguely emulates the behavior of a 794 795 L{Request} with no body producer. 795 796 """ 797 persistent = False 798 796 799 def writeTo(self, transport): 797 800 transport.write('SOME BYTES') 798 801 return succeed(None) … … 861 864 L{RequestGenerationFailed} wrapping the underlying failure. 862 865 """ 863 866 class BrokenRequest: 867 persistent = False 864 868 def writeTo(self, transport): 865 869 return fail(ArbitraryException()) 866 870 … … 883 887 a L{Failure} of L{RequestGenerationFailed} wrapping that exception. 884 888 """ 885 889 class BrokenRequest: 890 persistent = False 886 891 def writeTo(self, transport): 887 892 raise ArbitraryException() 888 893 … … 952 957 self.assertEqual(response.code, 200) 953 958 self.assertEqual(response.headers, Headers()) 954 959 self.assertTrue(self.transport.disconnecting) 960 self.assertEqual(self.protocol.state, 'QUIESCENT') 955 961 d.addCallback(cbRequest) 956 962 self.protocol.dataReceived( 957 963 "HTTP/1.1 200 OK\r\n" … … 997 1003 p = AccumulatingProtocol() 998 1004 whenFinished = p.closedDeferred = Deferred() 999 1005 response.deliverBody(p) 1006 self.assertEqual( 1007 self.protocol.state, 'TRANSMITTING_AFTER_RECEIVING_RESPONSE') 1000 1008 return whenFinished.addCallback( 1001 1009 lambda ign: (response, p.data)) 1002 1010 d.addCallback(cbResponse) … … 1293 1301 "\r\n") 1294 1302 1295 1303 1304 def test_sendSimplestPersistentRequest(self): 1305 """ 1306 A pesistent request does not send 'Connection: close' header. 1307 """ 1308 req = Request('GET', '/', _boringHeaders, None, persistent=True) 1309 req.writeTo(self.transport) 1310 self.assertEqual( 1311 self.transport.value(), 1312 "GET / HTTP/1.1\r\n" 1313 "Host: example.com\r\n" 1314 "\r\n") 1315 1316 1296 1317 def test_sendRequestHeaders(self): 1297 1318 """ 1298 1319 L{Request.writeTo} formats header data and writes it to the given -
twisted/web/test/test_webclient.py
diff -ru Twisted-10.1.0.orig/twisted/web/test/test_webclient.py Twisted-10.1.0/twisted/web/test/test_webclient.py
old new 857 857 """ 858 858 def __init__(self): 859 859 self.requests = [] 860 self.state = 'QUIESCENT' 860 861 861 862 862 863 def request(self, request): … … 916 917 the TCP connection attempt fails. 917 918 """ 918 919 result = self.agent.request('GET', 'http://foo/') 919 920 920 # Cause the connection to be refused 921 921 host, port, factory = self.reactor.tcpClients.pop()[:3] 922 922 factory.clientConnectionFailed(None, Failure(ConnectionRefusedError())) 923 923 self.completeConnection() 924 925 924 return self.assertFailure(result, ConnectionRefusedError) 926 925 927 926 … … 1051 1050 self.assertIdentical(req.bodyProducer, body) 1052 1051 1053 1052 1053 def test_persistentRequest(self): 1054 """ 1055 Test L{Agent.request} with persistent=True option. 1056 """ 1057 self.agent.persistent = True 1058 self.agent._connect = self._dummyConnect 1059 1060 # first request 1061 d1 = self.agent.request( 1062 'GET', 'http://example.com:1234/foo', None, None) 1063 p1 = self.protocol 1064 self.assertEquals(len(p1.requests), 1) 1065 req, res = p1.requests.pop() 1066 self.assertEquals(req.uri, '/foo') 1067 # second request 1068 d2 = self.agent.request( 1069 'GET', 'http://example.com:1234/bar', None, None) 1070 self.assertTrue(self.protocol is not p1) 1071 p2 = self.protocol 1072 self.assertEquals(len(p2.requests), 1) 1073 req, res = p2.requests.pop() 1074 self.assertEquals(req.uri, '/bar') 1075 # third request 1076 d3 = self.agent.request( 1077 'GET', 'http://example.com:1234/baz', None, None) 1078 # third request does not make a new protocol instance 1079 self.assertTrue(self.protocol is p2) 1080 sem = self.agent._semaphores['http', 'example.com', 1234] 1081 self.assertIsInstance(sem, defer.DeferredSemaphore) 1082 # third request is in waiting 1083 self.assertEquals(len(sem.waiting), 1) 1084 d1.result.result.callback('First request done') 1085 # third request starts 1086 self.assertEquals(len(sem.waiting), 0) 1087 cache = self.agent._protocolCache['http', 'example.com', 1234] 1088 self.assertEquals(len(cache), 0) 1089 d2.result.result.callback('Second request done') 1090 self.assertEquals(len(cache), 1) 1091 1092 1054 1093 def test_hostProvided(self): 1055 1094 """ 1056 1095 If C{None} is passed to L{Agent.request} for the C{headers}
