test_agent.py 106 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053
  1. # Copyright (c) Twisted Matrix Laboratories.
  2. # See LICENSE for details.
  3. """
  4. Tests for L{twisted.web.client.Agent} and related new client APIs.
  5. """
  6. import zlib
  7. from io import BytesIO
  8. from zope.interface.verify import verifyObject
  9. from twisted.trial.unittest import TestCase
  10. from twisted.web import client, error, http_headers
  11. from twisted.web._newclient import RequestNotSent, RequestTransmissionFailed
  12. from twisted.web._newclient import ResponseNeverReceived, ResponseFailed
  13. from twisted.web._newclient import PotentialDataLoss
  14. from twisted.internet import defer, task
  15. from twisted.python.failure import Failure
  16. from twisted.python.compat import cookielib, intToBytes
  17. from twisted.python.components import proxyForInterface
  18. from twisted.test.proto_helpers import StringTransport, MemoryReactorClock
  19. from twisted.internet.task import Clock
  20. from twisted.internet.error import ConnectionRefusedError, ConnectionDone
  21. from twisted.internet.error import ConnectionLost
  22. from twisted.internet.protocol import Protocol, Factory
  23. from twisted.internet.defer import Deferred, succeed, CancelledError
  24. from twisted.internet.endpoints import TCP4ClientEndpoint
  25. from twisted.internet.address import IPv4Address, IPv6Address
  26. from twisted.web.client import (FileBodyProducer, Request, HTTPConnectionPool,
  27. ResponseDone, _HTTP11ClientFactory, URI)
  28. from twisted.web.iweb import (
  29. UNKNOWN_LENGTH, IAgent, IBodyProducer, IResponse, IAgentEndpointFactory,
  30. )
  31. from twisted.web.http_headers import Headers
  32. from twisted.web._newclient import HTTP11ClientProtocol, Response
  33. from twisted.internet.interfaces import IOpenSSLClientConnectionCreator
  34. from zope.interface.declarations import implementer
  35. from twisted.web.iweb import IPolicyForHTTPS
  36. from twisted.python.deprecate import getDeprecationWarningString
  37. from incremental import Version
  38. from twisted.web.client import BrowserLikePolicyForHTTPS
  39. from twisted.internet.test.test_endpoints import deterministicResolvingReactor
  40. from twisted.internet.endpoints import HostnameEndpoint
  41. from twisted.test.proto_helpers import AccumulatingProtocol
  42. from twisted.test.iosim import IOPump, FakeTransport
  43. from twisted.test.test_sslverify import certificatesForAuthorityAndServer
  44. from twisted.web.error import SchemeNotSupported
  45. try:
  46. from twisted.internet import ssl
  47. except ImportError:
  48. ssl = None
  49. skipWhenNoSSL = "SSL not present, cannot run SSL tests."
  50. skipWhenSSLPresent = None
  51. else:
  52. skipWhenSSLPresent = "SSL present."
  53. skipWhenNoSSL = None
  54. from twisted.internet._sslverify import ClientTLSOptions, IOpenSSLTrustRoot
  55. from twisted.internet.ssl import optionsForClientTLS
  56. from twisted.protocols.tls import TLSMemoryBIOProtocol, TLSMemoryBIOFactory
  57. class StubHTTPProtocol(Protocol):
  58. """
  59. A protocol like L{HTTP11ClientProtocol} but which does not actually know
  60. HTTP/1.1 and only collects requests in a list.
  61. @ivar requests: A C{list} of two-tuples. Each time a request is made, a
  62. tuple consisting of the request and the L{Deferred} returned from the
  63. request method is appended to this list.
  64. """
  65. def __init__(self):
  66. self.requests = []
  67. self.state = 'QUIESCENT'
  68. def request(self, request):
  69. """
  70. Capture the given request for later inspection.
  71. @return: A L{Deferred} which this code will never fire.
  72. """
  73. result = Deferred()
  74. self.requests.append((request, result))
  75. return result
  76. class FileConsumer(object):
  77. def __init__(self, outputFile):
  78. self.outputFile = outputFile
  79. def write(self, bytes):
  80. self.outputFile.write(bytes)
  81. class FileBodyProducerTests(TestCase):
  82. """
  83. Tests for the L{FileBodyProducer} which reads bytes from a file and writes
  84. them to an L{IConsumer}.
  85. """
  86. def _termination(self):
  87. """
  88. This method can be used as the C{terminationPredicateFactory} for a
  89. L{Cooperator}. It returns a predicate which immediately returns
  90. C{False}, indicating that no more work should be done this iteration.
  91. This has the result of only allowing one iteration of a cooperative
  92. task to be run per L{Cooperator} iteration.
  93. """
  94. return lambda: True
  95. def setUp(self):
  96. """
  97. Create a L{Cooperator} hooked up to an easily controlled, deterministic
  98. scheduler to use with L{FileBodyProducer}.
  99. """
  100. self._scheduled = []
  101. self.cooperator = task.Cooperator(
  102. self._termination, self._scheduled.append)
  103. def test_interface(self):
  104. """
  105. L{FileBodyProducer} instances provide L{IBodyProducer}.
  106. """
  107. self.assertTrue(verifyObject(
  108. IBodyProducer, FileBodyProducer(BytesIO(b""))))
  109. def test_unknownLength(self):
  110. """
  111. If the L{FileBodyProducer} is constructed with a file-like object
  112. without either a C{seek} or C{tell} method, its C{length} attribute is
  113. set to C{UNKNOWN_LENGTH}.
  114. """
  115. class HasSeek(object):
  116. def seek(self, offset, whence):
  117. pass
  118. class HasTell(object):
  119. def tell(self):
  120. pass
  121. producer = FileBodyProducer(HasSeek())
  122. self.assertEqual(UNKNOWN_LENGTH, producer.length)
  123. producer = FileBodyProducer(HasTell())
  124. self.assertEqual(UNKNOWN_LENGTH, producer.length)
  125. def test_knownLength(self):
  126. """
  127. If the L{FileBodyProducer} is constructed with a file-like object with
  128. both C{seek} and C{tell} methods, its C{length} attribute is set to the
  129. size of the file as determined by those methods.
  130. """
  131. inputBytes = b"here are some bytes"
  132. inputFile = BytesIO(inputBytes)
  133. inputFile.seek(5)
  134. producer = FileBodyProducer(inputFile)
  135. self.assertEqual(len(inputBytes) - 5, producer.length)
  136. self.assertEqual(inputFile.tell(), 5)
  137. def test_defaultCooperator(self):
  138. """
  139. If no L{Cooperator} instance is passed to L{FileBodyProducer}, the
  140. global cooperator is used.
  141. """
  142. producer = FileBodyProducer(BytesIO(b""))
  143. self.assertEqual(task.cooperate, producer._cooperate)
  144. def test_startProducing(self):
  145. """
  146. L{FileBodyProducer.startProducing} starts writing bytes from the input
  147. file to the given L{IConsumer} and returns a L{Deferred} which fires
  148. when they have all been written.
  149. """
  150. expectedResult = b"hello, world"
  151. readSize = 3
  152. output = BytesIO()
  153. consumer = FileConsumer(output)
  154. producer = FileBodyProducer(
  155. BytesIO(expectedResult), self.cooperator, readSize)
  156. complete = producer.startProducing(consumer)
  157. for i in range(len(expectedResult) // readSize + 1):
  158. self._scheduled.pop(0)()
  159. self.assertEqual([], self._scheduled)
  160. self.assertEqual(expectedResult, output.getvalue())
  161. self.assertEqual(None, self.successResultOf(complete))
  162. def test_inputClosedAtEOF(self):
  163. """
  164. When L{FileBodyProducer} reaches end-of-file on the input file given to
  165. it, the input file is closed.
  166. """
  167. readSize = 4
  168. inputBytes = b"some friendly bytes"
  169. inputFile = BytesIO(inputBytes)
  170. producer = FileBodyProducer(inputFile, self.cooperator, readSize)
  171. consumer = FileConsumer(BytesIO())
  172. producer.startProducing(consumer)
  173. for i in range(len(inputBytes) // readSize + 2):
  174. self._scheduled.pop(0)()
  175. self.assertTrue(inputFile.closed)
  176. def test_failedReadWhileProducing(self):
  177. """
  178. If a read from the input file fails while producing bytes to the
  179. consumer, the L{Deferred} returned by
  180. L{FileBodyProducer.startProducing} fires with a L{Failure} wrapping
  181. that exception.
  182. """
  183. class BrokenFile(object):
  184. def read(self, count):
  185. raise IOError("Simulated bad thing")
  186. producer = FileBodyProducer(BrokenFile(), self.cooperator)
  187. complete = producer.startProducing(FileConsumer(BytesIO()))
  188. self._scheduled.pop(0)()
  189. self.failureResultOf(complete).trap(IOError)
  190. def test_stopProducing(self):
  191. """
  192. L{FileBodyProducer.stopProducing} stops the underlying L{IPullProducer}
  193. and the cooperative task responsible for calling C{resumeProducing} and
  194. closes the input file but does not cause the L{Deferred} returned by
  195. C{startProducing} to fire.
  196. """
  197. expectedResult = b"hello, world"
  198. readSize = 3
  199. output = BytesIO()
  200. consumer = FileConsumer(output)
  201. inputFile = BytesIO(expectedResult)
  202. producer = FileBodyProducer(
  203. inputFile, self.cooperator, readSize)
  204. complete = producer.startProducing(consumer)
  205. producer.stopProducing()
  206. self.assertTrue(inputFile.closed)
  207. self._scheduled.pop(0)()
  208. self.assertEqual(b"", output.getvalue())
  209. self.assertNoResult(complete)
  210. def test_pauseProducing(self):
  211. """
  212. L{FileBodyProducer.pauseProducing} temporarily suspends writing bytes
  213. from the input file to the given L{IConsumer}.
  214. """
  215. expectedResult = b"hello, world"
  216. readSize = 5
  217. output = BytesIO()
  218. consumer = FileConsumer(output)
  219. producer = FileBodyProducer(
  220. BytesIO(expectedResult), self.cooperator, readSize)
  221. complete = producer.startProducing(consumer)
  222. self._scheduled.pop(0)()
  223. self.assertEqual(output.getvalue(), expectedResult[:5])
  224. producer.pauseProducing()
  225. # Sort of depends on an implementation detail of Cooperator: even
  226. # though the only task is paused, there's still a scheduled call. If
  227. # this were to go away because Cooperator became smart enough to cancel
  228. # this call in this case, that would be fine.
  229. self._scheduled.pop(0)()
  230. # Since the producer is paused, no new data should be here.
  231. self.assertEqual(output.getvalue(), expectedResult[:5])
  232. self.assertEqual([], self._scheduled)
  233. self.assertNoResult(complete)
  234. def test_resumeProducing(self):
  235. """
  236. L{FileBodyProducer.resumeProducing} re-commences writing bytes from the
  237. input file to the given L{IConsumer} after it was previously paused
  238. with L{FileBodyProducer.pauseProducing}.
  239. """
  240. expectedResult = b"hello, world"
  241. readSize = 5
  242. output = BytesIO()
  243. consumer = FileConsumer(output)
  244. producer = FileBodyProducer(
  245. BytesIO(expectedResult), self.cooperator, readSize)
  246. producer.startProducing(consumer)
  247. self._scheduled.pop(0)()
  248. self.assertEqual(expectedResult[:readSize], output.getvalue())
  249. producer.pauseProducing()
  250. producer.resumeProducing()
  251. self._scheduled.pop(0)()
  252. self.assertEqual(expectedResult[:readSize * 2], output.getvalue())
  253. EXAMPLE_COM_IP = '127.0.0.7'
  254. EXAMPLE_COM_V6_IP = '::7'
  255. EXAMPLE_NET_IP = '127.0.0.8'
  256. EXAMPLE_ORG_IP = '127.0.0.9'
  257. FOO_LOCAL_IP = '127.0.0.10'
  258. FOO_COM_IP = '127.0.0.11'
  259. class FakeReactorAndConnectMixin:
  260. """
  261. A test mixin providing a testable C{Reactor} class and a dummy C{connect}
  262. method which allows instances to pretend to be endpoints.
  263. """
  264. def createReactor(self):
  265. """
  266. Create a L{MemoryReactorClock} and give it some hostnames it can
  267. resolve.
  268. @return: a L{MemoryReactorClock}-like object with a slightly limited
  269. interface (only C{advance} and C{tcpClients} in addition to its
  270. formally-declared reactor interfaces), which can resolve a fixed
  271. set of domains.
  272. """
  273. mrc = MemoryReactorClock()
  274. drr = deterministicResolvingReactor(mrc, hostMap={
  275. u'example.com': [EXAMPLE_COM_IP],
  276. u'ipv6.example.com': [EXAMPLE_COM_V6_IP],
  277. u'example.net': [EXAMPLE_NET_IP],
  278. u'example.org': [EXAMPLE_ORG_IP],
  279. u'foo': [FOO_LOCAL_IP],
  280. u'foo.com': [FOO_COM_IP],
  281. })
  282. # Lots of tests were written expecting MemoryReactorClock and the
  283. # reactor seen by the SUT to be the same object.
  284. drr.tcpClients = mrc.tcpClients
  285. drr.advance = mrc.advance
  286. return drr
  287. class StubEndpoint(object):
  288. """
  289. Endpoint that wraps existing endpoint, substitutes StubHTTPProtocol, and
  290. resulting protocol instances are attached to the given test case.
  291. """
  292. def __init__(self, endpoint, testCase):
  293. self.endpoint = endpoint
  294. self.testCase = testCase
  295. self.factory = _HTTP11ClientFactory(lambda p: None)
  296. self.protocol = StubHTTPProtocol()
  297. self.factory.buildProtocol = lambda addr: self.protocol
  298. def connect(self, ignoredFactory):
  299. self.testCase.protocol = self.protocol
  300. self.endpoint.connect(self.factory)
  301. return succeed(self.protocol)
  302. def buildAgentForWrapperTest(self, reactor):
  303. """
  304. Return an Agent suitable for use in tests that wrap the Agent and want
  305. both a fake reactor and StubHTTPProtocol.
  306. """
  307. agent = client.Agent(reactor)
  308. _oldGetEndpoint = agent._getEndpoint
  309. agent._getEndpoint = lambda *args: (
  310. self.StubEndpoint(_oldGetEndpoint(*args), self))
  311. return agent
  312. def connect(self, factory):
  313. """
  314. Fake implementation of an endpoint which synchronously
  315. succeeds with an instance of L{StubHTTPProtocol} for ease of
  316. testing.
  317. """
  318. protocol = StubHTTPProtocol()
  319. protocol.makeConnection(None)
  320. self.protocol = protocol
  321. return succeed(protocol)
  322. class DummyEndpoint(object):
  323. """
  324. An endpoint that uses a fake transport.
  325. """
  326. def connect(self, factory):
  327. protocol = factory.buildProtocol(None)
  328. protocol.makeConnection(StringTransport())
  329. return succeed(protocol)
  330. class BadEndpoint(object):
  331. """
  332. An endpoint that shouldn't be called.
  333. """
  334. def connect(self, factory):
  335. raise RuntimeError("This endpoint should not have been used.")
  336. class DummyFactory(Factory):
  337. """
  338. Create C{StubHTTPProtocol} instances.
  339. """
  340. def __init__(self, quiescentCallback):
  341. pass
  342. protocol = StubHTTPProtocol
  343. class HTTPConnectionPoolTests(TestCase, FakeReactorAndConnectMixin):
  344. """
  345. Tests for the L{HTTPConnectionPool} class.
  346. """
  347. def setUp(self):
  348. self.fakeReactor = self.createReactor()
  349. self.pool = HTTPConnectionPool(self.fakeReactor)
  350. self.pool._factory = DummyFactory
  351. # The retry code path is tested in HTTPConnectionPoolRetryTests:
  352. self.pool.retryAutomatically = False
  353. def test_getReturnsNewIfCacheEmpty(self):
  354. """
  355. If there are no cached connections,
  356. L{HTTPConnectionPool.getConnection} returns a new connection.
  357. """
  358. self.assertEqual(self.pool._connections, {})
  359. def gotConnection(conn):
  360. self.assertIsInstance(conn, StubHTTPProtocol)
  361. # The new connection is not stored in the pool:
  362. self.assertNotIn(conn, self.pool._connections.values())
  363. unknownKey = 12245
  364. d = self.pool.getConnection(unknownKey, DummyEndpoint())
  365. return d.addCallback(gotConnection)
  366. def test_putStartsTimeout(self):
  367. """
  368. If a connection is put back to the pool, a 240-sec timeout is started.
  369. When the timeout hits, the connection is closed and removed from the
  370. pool.
  371. """
  372. # We start out with one cached connection:
  373. protocol = StubHTTPProtocol()
  374. protocol.makeConnection(StringTransport())
  375. self.pool._putConnection(("http", b"example.com", 80), protocol)
  376. # Connection is in pool, still not closed:
  377. self.assertEqual(protocol.transport.disconnecting, False)
  378. self.assertIn(protocol,
  379. self.pool._connections[("http", b"example.com", 80)])
  380. # Advance 239 seconds, still not closed:
  381. self.fakeReactor.advance(239)
  382. self.assertEqual(protocol.transport.disconnecting, False)
  383. self.assertIn(protocol,
  384. self.pool._connections[("http", b"example.com", 80)])
  385. self.assertIn(protocol, self.pool._timeouts)
  386. # Advance past 240 seconds, connection will be closed:
  387. self.fakeReactor.advance(1.1)
  388. self.assertEqual(protocol.transport.disconnecting, True)
  389. self.assertNotIn(protocol,
  390. self.pool._connections[("http", b"example.com", 80)])
  391. self.assertNotIn(protocol, self.pool._timeouts)
  392. def test_putExceedsMaxPersistent(self):
  393. """
  394. If an idle connection is put back in the cache and the max number of
  395. persistent connections has been exceeded, one of the connections is
  396. closed and removed from the cache.
  397. """
  398. pool = self.pool
  399. # We start out with two cached connection, the max:
  400. origCached = [StubHTTPProtocol(), StubHTTPProtocol()]
  401. for p in origCached:
  402. p.makeConnection(StringTransport())
  403. pool._putConnection(("http", b"example.com", 80), p)
  404. self.assertEqual(pool._connections[("http", b"example.com", 80)],
  405. origCached)
  406. timeouts = pool._timeouts.copy()
  407. # Now we add another one:
  408. newProtocol = StubHTTPProtocol()
  409. newProtocol.makeConnection(StringTransport())
  410. pool._putConnection(("http", b"example.com", 80), newProtocol)
  411. # The oldest cached connections will be removed and disconnected:
  412. newCached = pool._connections[("http", b"example.com", 80)]
  413. self.assertEqual(len(newCached), 2)
  414. self.assertEqual(newCached, [origCached[1], newProtocol])
  415. self.assertEqual([p.transport.disconnecting for p in newCached],
  416. [False, False])
  417. self.assertEqual(origCached[0].transport.disconnecting, True)
  418. self.assertTrue(timeouts[origCached[0]].cancelled)
  419. self.assertNotIn(origCached[0], pool._timeouts)
  420. def test_maxPersistentPerHost(self):
  421. """
  422. C{maxPersistentPerHost} is enforced per C{(scheme, host, port)}:
  423. different keys have different max connections.
  424. """
  425. def addProtocol(scheme, host, port):
  426. p = StubHTTPProtocol()
  427. p.makeConnection(StringTransport())
  428. self.pool._putConnection((scheme, host, port), p)
  429. return p
  430. persistent = []
  431. persistent.append(addProtocol("http", b"example.com", 80))
  432. persistent.append(addProtocol("http", b"example.com", 80))
  433. addProtocol("https", b"example.com", 443)
  434. addProtocol("http", b"www2.example.com", 80)
  435. self.assertEqual(
  436. self.pool._connections[("http", b"example.com", 80)], persistent)
  437. self.assertEqual(
  438. len(self.pool._connections[("https", b"example.com", 443)]), 1)
  439. self.assertEqual(
  440. len(self.pool._connections[("http", b"www2.example.com", 80)]), 1)
  441. def test_getCachedConnection(self):
  442. """
  443. Getting an address which has a cached connection returns the cached
  444. connection, removes it from the cache and cancels its timeout.
  445. """
  446. # We start out with one cached connection:
  447. protocol = StubHTTPProtocol()
  448. protocol.makeConnection(StringTransport())
  449. self.pool._putConnection(("http", b"example.com", 80), protocol)
  450. def gotConnection(conn):
  451. # We got the cached connection:
  452. self.assertIdentical(protocol, conn)
  453. self.assertNotIn(
  454. conn, self.pool._connections[("http", b"example.com", 80)])
  455. # And the timeout was cancelled:
  456. self.fakeReactor.advance(241)
  457. self.assertEqual(conn.transport.disconnecting, False)
  458. self.assertNotIn(conn, self.pool._timeouts)
  459. return self.pool.getConnection(("http", b"example.com", 80),
  460. BadEndpoint(),
  461. ).addCallback(gotConnection)
  462. def test_newConnection(self):
  463. """
  464. The pool's C{_newConnection} method constructs a new connection.
  465. """
  466. # We start out with one cached connection:
  467. protocol = StubHTTPProtocol()
  468. protocol.makeConnection(StringTransport())
  469. key = 12245
  470. self.pool._putConnection(key, protocol)
  471. def gotConnection(newConnection):
  472. # We got a new connection:
  473. self.assertNotIdentical(protocol, newConnection)
  474. # And the old connection is still there:
  475. self.assertIn(protocol, self.pool._connections[key])
  476. # While the new connection is not:
  477. self.assertNotIn(newConnection, self.pool._connections.values())
  478. d = self.pool._newConnection(key, DummyEndpoint())
  479. return d.addCallback(gotConnection)
  480. def test_getSkipsDisconnected(self):
  481. """
  482. When getting connections out of the cache, disconnected connections
  483. are removed and not returned.
  484. """
  485. pool = self.pool
  486. key = ("http", b"example.com", 80)
  487. # We start out with two cached connection, the max:
  488. origCached = [StubHTTPProtocol(), StubHTTPProtocol()]
  489. for p in origCached:
  490. p.makeConnection(StringTransport())
  491. pool._putConnection(key, p)
  492. self.assertEqual(pool._connections[key], origCached)
  493. # We close the first one:
  494. origCached[0].state = "DISCONNECTED"
  495. # Now, when we retrive connections we should get the *second* one:
  496. result = []
  497. self.pool.getConnection(key,
  498. BadEndpoint()).addCallback(result.append)
  499. self.assertIdentical(result[0], origCached[1])
  500. # And both the disconnected and removed connections should be out of
  501. # the cache:
  502. self.assertEqual(pool._connections[key], [])
  503. self.assertEqual(pool._timeouts, {})
  504. def test_putNotQuiescent(self):
  505. """
  506. If a non-quiescent connection is put back in the cache, an error is
  507. logged.
  508. """
  509. protocol = StubHTTPProtocol()
  510. # By default state is QUIESCENT
  511. self.assertEqual(protocol.state, "QUIESCENT")
  512. protocol.state = "NOTQUIESCENT"
  513. self.pool._putConnection(("http", b"example.com", 80), protocol)
  514. exc, = self.flushLoggedErrors(RuntimeError)
  515. self.assertEqual(
  516. exc.value.args[0],
  517. "BUG: Non-quiescent protocol added to connection pool.")
  518. self.assertIdentical(None, self.pool._connections.get(
  519. ("http", b"example.com", 80)))
  520. def test_getUsesQuiescentCallback(self):
  521. """
  522. When L{HTTPConnectionPool.getConnection} connects, it returns a
  523. C{Deferred} that fires with an instance of L{HTTP11ClientProtocol}
  524. that has the correct quiescent callback attached. When this callback
  525. is called the protocol is returned to the cache correctly, using the
  526. right key.
  527. """
  528. class StringEndpoint(object):
  529. def connect(self, factory):
  530. p = factory.buildProtocol(None)
  531. p.makeConnection(StringTransport())
  532. return succeed(p)
  533. pool = HTTPConnectionPool(self.fakeReactor, True)
  534. pool.retryAutomatically = False
  535. result = []
  536. key = "a key"
  537. pool.getConnection(
  538. key, StringEndpoint()).addCallback(
  539. result.append)
  540. protocol = result[0]
  541. self.assertIsInstance(protocol, HTTP11ClientProtocol)
  542. # Now that we have protocol instance, lets try to put it back in the
  543. # pool:
  544. protocol._state = "QUIESCENT"
  545. protocol._quiescentCallback(protocol)
  546. # If we try to retrive a connection to same destination again, we
  547. # should get the same protocol, because it should've been added back
  548. # to the pool:
  549. result2 = []
  550. pool.getConnection(
  551. key, StringEndpoint()).addCallback(
  552. result2.append)
  553. self.assertIdentical(result2[0], protocol)
  554. def test_closeCachedConnections(self):
  555. """
  556. L{HTTPConnectionPool.closeCachedConnections} closes all cached
  557. connections and removes them from the cache. It returns a Deferred
  558. that fires when they have all lost their connections.
  559. """
  560. persistent = []
  561. def addProtocol(scheme, host, port):
  562. p = HTTP11ClientProtocol()
  563. p.makeConnection(StringTransport())
  564. self.pool._putConnection((scheme, host, port), p)
  565. persistent.append(p)
  566. addProtocol("http", b"example.com", 80)
  567. addProtocol("http", b"www2.example.com", 80)
  568. doneDeferred = self.pool.closeCachedConnections()
  569. # Connections have begun disconnecting:
  570. for p in persistent:
  571. self.assertEqual(p.transport.disconnecting, True)
  572. self.assertEqual(self.pool._connections, {})
  573. # All timeouts were cancelled and removed:
  574. for dc in self.fakeReactor.getDelayedCalls():
  575. self.assertEqual(dc.cancelled, True)
  576. self.assertEqual(self.pool._timeouts, {})
  577. # Returned Deferred fires when all connections have been closed:
  578. result = []
  579. doneDeferred.addCallback(result.append)
  580. self.assertEqual(result, [])
  581. persistent[0].connectionLost(Failure(ConnectionDone()))
  582. self.assertEqual(result, [])
  583. persistent[1].connectionLost(Failure(ConnectionDone()))
  584. self.assertEqual(result, [None])
  585. def test_cancelGetConnectionCancelsEndpointConnect(self):
  586. """
  587. Cancelling the C{Deferred} returned from
  588. L{HTTPConnectionPool.getConnection} cancels the C{Deferred} returned
  589. by opening a new connection with the given endpoint.
  590. """
  591. self.assertEqual(self.pool._connections, {})
  592. connectionResult = Deferred()
  593. class Endpoint:
  594. def connect(self, factory):
  595. return connectionResult
  596. d = self.pool.getConnection(12345, Endpoint())
  597. d.cancel()
  598. self.assertEqual(self.failureResultOf(connectionResult).type,
  599. CancelledError)
  600. class AgentTestsMixin(object):
  601. """
  602. Tests for any L{IAgent} implementation.
  603. """
  604. def test_interface(self):
  605. """
  606. The agent object provides L{IAgent}.
  607. """
  608. self.assertTrue(verifyObject(IAgent, self.makeAgent()))
  609. class IntegrationTestingMixin(object):
  610. """
  611. Transport-to-Agent integration tests for both HTTP and HTTPS.
  612. """
  613. def test_integrationTestIPv4(self):
  614. """
  615. L{Agent} works over IPv4.
  616. """
  617. self.integrationTest(b'example.com', EXAMPLE_COM_IP, IPv4Address)
  618. def test_integrationTestIPv6(self):
  619. """
  620. L{Agent} works over IPv6.
  621. """
  622. self.integrationTest(b'ipv6.example.com', EXAMPLE_COM_V6_IP,
  623. IPv6Address)
  624. def integrationTest(self, hostName, expectedAddress, addressType,
  625. serverWrapper=lambda server: server,
  626. createAgent=client.Agent,
  627. scheme=b'http'):
  628. """
  629. L{Agent} will make a TCP connection, send an HTTP request, and return a
  630. L{Deferred} that fires when the response has been received.
  631. @param hostName: The hostname to interpolate into the URL to be
  632. requested.
  633. @type hostName: L{bytes}
  634. @param expectedAddress: The expected address string.
  635. @type expectedAddress: L{bytes}
  636. @param addressType: The class to construct an address out of.
  637. @type addressType: L{type}
  638. @param serverWrapper: A callable that takes a protocol factory and
  639. returns a protocol factory; used to wrap the server / responder
  640. side in a TLS server.
  641. @type serverWrapper:
  642. serverWrapper(L{twisted.internet.interfaces.IProtocolFactory}) ->
  643. L{twisted.internet.interfaces.IProtocolFactory}
  644. @param createAgent: A callable that takes a reactor and produces an
  645. L{IAgent}; used to construct an agent with an appropriate trust
  646. root for TLS.
  647. @type createAgent: createAgent(reactor) -> L{IAgent}
  648. @param scheme: The scheme to test, C{http} or C{https}
  649. @type scheme: L{bytes}
  650. """
  651. reactor = self.createReactor()
  652. agent = createAgent(reactor)
  653. deferred = agent.request(b"GET", scheme + b"://" + hostName + b"/")
  654. host, port, factory, timeout, bind = reactor.tcpClients[0]
  655. self.assertEqual(host, expectedAddress)
  656. peerAddress = addressType('TCP', host, port)
  657. clientProtocol = factory.buildProtocol(peerAddress)
  658. clientTransport = FakeTransport(clientProtocol, False,
  659. peerAddress=peerAddress)
  660. clientProtocol.makeConnection(clientTransport)
  661. @Factory.forProtocol
  662. def accumulator():
  663. ap = AccumulatingProtocol()
  664. accumulator.currentProtocol = ap
  665. return ap
  666. accumulator.currentProtocol = None
  667. accumulator.protocolConnectionMade = None
  668. wrapper = serverWrapper(accumulator).buildProtocol(None)
  669. serverTransport = FakeTransport(wrapper, True)
  670. wrapper.makeConnection(serverTransport)
  671. pump = IOPump(clientProtocol, wrapper,
  672. clientTransport, serverTransport, False)
  673. pump.flush()
  674. lines = accumulator.currentProtocol.data.split(b"\r\n")
  675. self.assertTrue(lines[0].startswith(b"GET / HTTP"))
  676. headers = dict([line.split(b": ", 1) for line in lines[1:] if line])
  677. self.assertEqual(headers[b'Host'], hostName)
  678. self.assertNoResult(deferred)
  679. accumulator.currentProtocol.transport.write(
  680. b"HTTP/1.1 200 OK"
  681. b"\r\nX-An-Header: an-value\r\n"
  682. b"\r\nContent-length: 12\r\n\r\n"
  683. b"hello world!"
  684. )
  685. pump.flush()
  686. response = self.successResultOf(deferred)
  687. self.assertEquals(response.headers.getRawHeaders(b'x-an-header')[0],
  688. b"an-value")
  689. @implementer(IAgentEndpointFactory)
  690. class StubEndpointFactory(object):
  691. """
  692. A stub L{IAgentEndpointFactory} for use in testing.
  693. """
  694. def endpointForURI(self, uri):
  695. """
  696. Testing implementation.
  697. @param uri: A L{URI}.
  698. @return: C{(scheme, host, port)} of passed in URI; violation of
  699. interface but useful for testing.
  700. @rtype: L{tuple}
  701. """
  702. return (uri.scheme, uri.host, uri.port)
  703. class AgentTests(TestCase, FakeReactorAndConnectMixin, AgentTestsMixin,
  704. IntegrationTestingMixin):
  705. """
  706. Tests for the new HTTP client API provided by L{Agent}.
  707. """
  708. def makeAgent(self):
  709. """
  710. @return: a new L{twisted.web.client.Agent} instance
  711. """
  712. return client.Agent(self.reactor)
  713. def setUp(self):
  714. """
  715. Create an L{Agent} wrapped around a fake reactor.
  716. """
  717. self.reactor = self.createReactor()
  718. self.agent = self.makeAgent()
  719. def test_defaultPool(self):
  720. """
  721. If no pool is passed in, the L{Agent} creates a non-persistent pool.
  722. """
  723. agent = client.Agent(self.reactor)
  724. self.assertIsInstance(agent._pool, HTTPConnectionPool)
  725. self.assertEqual(agent._pool.persistent, False)
  726. self.assertIdentical(agent._reactor, agent._pool._reactor)
  727. def test_persistent(self):
  728. """
  729. If C{persistent} is set to C{True} on the L{HTTPConnectionPool} (the
  730. default), C{Request}s are created with their C{persistent} flag set to
  731. C{True}.
  732. """
  733. pool = HTTPConnectionPool(self.reactor)
  734. agent = client.Agent(self.reactor, pool=pool)
  735. agent._getEndpoint = lambda *args: self
  736. agent.request(b"GET", b"http://127.0.0.1")
  737. self.assertEqual(self.protocol.requests[0][0].persistent, True)
  738. def test_nonPersistent(self):
  739. """
  740. If C{persistent} is set to C{False} when creating the
  741. L{HTTPConnectionPool}, C{Request}s are created with their
  742. C{persistent} flag set to C{False}.
  743. Elsewhere in the tests for the underlying HTTP code we ensure that
  744. this will result in the disconnection of the HTTP protocol once the
  745. request is done, so that the connection will not be returned to the
  746. pool.
  747. """
  748. pool = HTTPConnectionPool(self.reactor, persistent=False)
  749. agent = client.Agent(self.reactor, pool=pool)
  750. agent._getEndpoint = lambda *args: self
  751. agent.request(b"GET", b"http://127.0.0.1")
  752. self.assertEqual(self.protocol.requests[0][0].persistent, False)
  753. def test_connectUsesConnectionPool(self):
  754. """
  755. When a connection is made by the Agent, it uses its pool's
  756. C{getConnection} method to do so, with the endpoint returned by
  757. C{self._getEndpoint}. The key used is C{(scheme, host, port)}.
  758. """
  759. endpoint = DummyEndpoint()
  760. class MyAgent(client.Agent):
  761. def _getEndpoint(this, uri):
  762. self.assertEqual((uri.scheme, uri.host, uri.port),
  763. (b"http", b"foo", 80))
  764. return endpoint
  765. class DummyPool(object):
  766. connected = False
  767. persistent = False
  768. def getConnection(this, key, ep):
  769. this.connected = True
  770. self.assertEqual(ep, endpoint)
  771. # This is the key the default Agent uses, others will have
  772. # different keys:
  773. self.assertEqual(key, (b"http", b"foo", 80))
  774. return defer.succeed(StubHTTPProtocol())
  775. pool = DummyPool()
  776. agent = MyAgent(self.reactor, pool=pool)
  777. self.assertIdentical(pool, agent._pool)
  778. headers = http_headers.Headers()
  779. headers.addRawHeader(b"host", b"foo")
  780. bodyProducer = object()
  781. agent.request(b'GET', b'http://foo/',
  782. bodyProducer=bodyProducer, headers=headers)
  783. self.assertEqual(agent._pool.connected, True)
  784. def test_unsupportedScheme(self):
  785. """
  786. L{Agent.request} returns a L{Deferred} which fails with
  787. L{SchemeNotSupported} if the scheme of the URI passed to it is not
  788. C{'http'}.
  789. """
  790. return self.assertFailure(
  791. self.agent.request(b'GET', b'mailto:alice@example.com'),
  792. SchemeNotSupported)
  793. def test_connectionFailed(self):
  794. """
  795. The L{Deferred} returned by L{Agent.request} fires with a L{Failure} if
  796. the TCP connection attempt fails.
  797. """
  798. result = self.agent.request(b'GET', b'http://foo/')
  799. # Cause the connection to be refused
  800. host, port, factory = self.reactor.tcpClients.pop()[:3]
  801. factory.clientConnectionFailed(None, Failure(ConnectionRefusedError()))
  802. self.reactor.advance(10)
  803. # ^ https://twistedmatrix.com/trac/ticket/8202
  804. self.failureResultOf(result, ConnectionRefusedError)
  805. def test_connectHTTP(self):
  806. """
  807. L{Agent._getEndpoint} return a C{HostnameEndpoint} when passed a scheme
  808. of C{'http'}.
  809. """
  810. expectedHost = b'example.com'
  811. expectedPort = 1234
  812. endpoint = self.agent._getEndpoint(URI.fromBytes(
  813. b'http://' + expectedHost + b":" + intToBytes(expectedPort)))
  814. self.assertEqual(endpoint._hostStr, "example.com")
  815. self.assertEqual(endpoint._port, expectedPort)
  816. self.assertIsInstance(endpoint, HostnameEndpoint)
  817. def test_nonDecodableURI(self):
  818. """
  819. L{Agent._getEndpoint} when given a non-ASCII decodable URI will raise a
  820. L{ValueError} saying such.
  821. """
  822. uri = URI.fromBytes(b"http://example.com:80")
  823. uri.host = u'\u2603.com'.encode('utf8')
  824. with self.assertRaises(ValueError) as e:
  825. self.agent._getEndpoint(uri)
  826. self.assertEqual(e.exception.args[0],
  827. ("The host of the provided URI ({reprout}) contains "
  828. "non-ASCII octets, it should be ASCII "
  829. "decodable.").format(reprout=repr(uri.host)))
  830. def test_hostProvided(self):
  831. """
  832. If L{None} is passed to L{Agent.request} for the C{headers} parameter,
  833. a L{Headers} instance is created for the request and a I{Host} header
  834. added to it.
  835. """
  836. self.agent._getEndpoint = lambda *args: self
  837. self.agent.request(
  838. b'GET', b'http://example.com/foo?bar')
  839. req, res = self.protocol.requests.pop()
  840. self.assertEqual(req.headers.getRawHeaders(b'host'), [b'example.com'])
  841. def test_hostIPv6Bracketed(self):
  842. """
  843. If an IPv6 address is used in the C{uri} passed to L{Agent.request},
  844. the computed I{Host} header needs to be bracketed.
  845. """
  846. self.agent._getEndpoint = lambda *args: self
  847. self.agent.request(b'GET', b'http://[::1]/')
  848. req, res = self.protocol.requests.pop()
  849. self.assertEqual(req.headers.getRawHeaders(b'host'), [b'[::1]'])
  850. def test_hostOverride(self):
  851. """
  852. If the headers passed to L{Agent.request} includes a value for the
  853. I{Host} header, that value takes precedence over the one which would
  854. otherwise be automatically provided.
  855. """
  856. headers = http_headers.Headers({b'foo': [b'bar'], b'host': [b'quux']})
  857. self.agent._getEndpoint = lambda *args: self
  858. self.agent.request(
  859. b'GET', b'http://example.com/foo?bar', headers)
  860. req, res = self.protocol.requests.pop()
  861. self.assertEqual(req.headers.getRawHeaders(b'host'), [b'quux'])
  862. def test_headersUnmodified(self):
  863. """
  864. If a I{Host} header must be added to the request, the L{Headers}
  865. instance passed to L{Agent.request} is not modified.
  866. """
  867. headers = http_headers.Headers()
  868. self.agent._getEndpoint = lambda *args: self
  869. self.agent.request(
  870. b'GET', b'http://example.com/foo', headers)
  871. protocol = self.protocol
  872. # The request should have been issued.
  873. self.assertEqual(len(protocol.requests), 1)
  874. # And the headers object passed in should not have changed.
  875. self.assertEqual(headers, http_headers.Headers())
  876. def test_hostValueStandardHTTP(self):
  877. """
  878. When passed a scheme of C{'http'} and a port of C{80},
  879. L{Agent._computeHostValue} returns a string giving just
  880. the host name passed to it.
  881. """
  882. self.assertEqual(
  883. self.agent._computeHostValue(b'http', b'example.com', 80),
  884. b'example.com')
  885. def test_hostValueNonStandardHTTP(self):
  886. """
  887. When passed a scheme of C{'http'} and a port other than C{80},
  888. L{Agent._computeHostValue} returns a string giving the
  889. host passed to it joined together with the port number by C{":"}.
  890. """
  891. self.assertEqual(
  892. self.agent._computeHostValue(b'http', b'example.com', 54321),
  893. b'example.com:54321')
  894. def test_hostValueStandardHTTPS(self):
  895. """
  896. When passed a scheme of C{'https'} and a port of C{443},
  897. L{Agent._computeHostValue} returns a string giving just
  898. the host name passed to it.
  899. """
  900. self.assertEqual(
  901. self.agent._computeHostValue(b'https', b'example.com', 443),
  902. b'example.com')
  903. def test_hostValueNonStandardHTTPS(self):
  904. """
  905. When passed a scheme of C{'https'} and a port other than C{443},
  906. L{Agent._computeHostValue} returns a string giving the
  907. host passed to it joined together with the port number by C{":"}.
  908. """
  909. self.assertEqual(
  910. self.agent._computeHostValue(b'https', b'example.com', 54321),
  911. b'example.com:54321')
  912. def test_request(self):
  913. """
  914. L{Agent.request} establishes a new connection to the host indicated by
  915. the host part of the URI passed to it and issues a request using the
  916. method, the path portion of the URI, the headers, and the body producer
  917. passed to it. It returns a L{Deferred} which fires with an
  918. L{IResponse} from the server.
  919. """
  920. self.agent._getEndpoint = lambda *args: self
  921. headers = http_headers.Headers({b'foo': [b'bar']})
  922. # Just going to check the body for identity, so it doesn't need to be
  923. # real.
  924. body = object()
  925. self.agent.request(
  926. b'GET', b'http://example.com:1234/foo?bar', headers, body)
  927. protocol = self.protocol
  928. # The request should be issued.
  929. self.assertEqual(len(protocol.requests), 1)
  930. req, res = protocol.requests.pop()
  931. self.assertIsInstance(req, Request)
  932. self.assertEqual(req.method, b'GET')
  933. self.assertEqual(req.uri, b'/foo?bar')
  934. self.assertEqual(
  935. req.headers,
  936. http_headers.Headers({b'foo': [b'bar'],
  937. b'host': [b'example.com:1234']}))
  938. self.assertIdentical(req.bodyProducer, body)
  939. def test_connectTimeout(self):
  940. """
  941. L{Agent} takes a C{connectTimeout} argument which is forwarded to the
  942. following C{connectTCP} agent.
  943. """
  944. agent = client.Agent(self.reactor, connectTimeout=5)
  945. agent.request(b'GET', b'http://foo/')
  946. timeout = self.reactor.tcpClients.pop()[3]
  947. self.assertEqual(5, timeout)
  948. def test_connectTimeoutHTTPS(self):
  949. """
  950. L{Agent} takes a C{connectTimeout} argument which is forwarded to the
  951. following C{connectTCP} call.
  952. """
  953. agent = client.Agent(self.reactor, connectTimeout=5)
  954. agent.request(b'GET', b'https://foo/')
  955. timeout = self.reactor.tcpClients.pop()[3]
  956. self.assertEqual(5, timeout)
  957. test_connectTimeoutHTTPS.skip = skipWhenNoSSL
  958. def test_bindAddress(self):
  959. """
  960. L{Agent} takes a C{bindAddress} argument which is forwarded to the
  961. following C{connectTCP} call.
  962. """
  963. agent = client.Agent(self.reactor, bindAddress='192.168.0.1')
  964. agent.request(b'GET', b'http://foo/')
  965. address = self.reactor.tcpClients.pop()[4]
  966. self.assertEqual('192.168.0.1', address)
  967. def test_bindAddressSSL(self):
  968. """
  969. L{Agent} takes a C{bindAddress} argument which is forwarded to the
  970. following C{connectSSL} call.
  971. """
  972. agent = client.Agent(self.reactor, bindAddress='192.168.0.1')
  973. agent.request(b'GET', b'https://foo/')
  974. address = self.reactor.tcpClients.pop()[4]
  975. self.assertEqual('192.168.0.1', address)
  976. test_bindAddressSSL.skip = skipWhenNoSSL
  977. def test_responseIncludesRequest(self):
  978. """
  979. L{Response}s returned by L{Agent.request} have a reference to the
  980. L{Request} that was originally issued.
  981. """
  982. uri = b'http://example.com/'
  983. agent = self.buildAgentForWrapperTest(self.reactor)
  984. d = agent.request(b'GET', uri)
  985. # The request should be issued.
  986. self.assertEqual(len(self.protocol.requests), 1)
  987. req, res = self.protocol.requests.pop()
  988. self.assertIsInstance(req, Request)
  989. resp = client.Response._construct(
  990. (b'HTTP', 1, 1),
  991. 200,
  992. b'OK',
  993. client.Headers({}),
  994. None,
  995. req)
  996. res.callback(resp)
  997. response = self.successResultOf(d)
  998. self.assertEqual(
  999. (response.request.method, response.request.absoluteURI,
  1000. response.request.headers),
  1001. (req.method, req.absoluteURI, req.headers))
  1002. def test_requestAbsoluteURI(self):
  1003. """
  1004. L{Request.absoluteURI} is the absolute URI of the request.
  1005. """
  1006. uri = b'http://example.com/foo;1234?bar#frag'
  1007. agent = self.buildAgentForWrapperTest(self.reactor)
  1008. agent.request(b'GET', uri)
  1009. # The request should be issued.
  1010. self.assertEqual(len(self.protocol.requests), 1)
  1011. req, res = self.protocol.requests.pop()
  1012. self.assertIsInstance(req, Request)
  1013. self.assertEqual(req.absoluteURI, uri)
  1014. def test_requestMissingAbsoluteURI(self):
  1015. """
  1016. L{Request.absoluteURI} is L{None} if L{Request._parsedURI} is L{None}.
  1017. """
  1018. request = client.Request(b'FOO', b'/', client.Headers(), None)
  1019. self.assertIdentical(request.absoluteURI, None)
  1020. def test_endpointFactory(self):
  1021. """
  1022. L{Agent.usingEndpointFactory} creates an L{Agent} that uses the given
  1023. factory to create endpoints.
  1024. """
  1025. factory = StubEndpointFactory()
  1026. agent = client.Agent.usingEndpointFactory(
  1027. None, endpointFactory=factory)
  1028. uri = URI.fromBytes(b'http://example.com/')
  1029. returnedEndpoint = agent._getEndpoint(uri)
  1030. self.assertEqual(returnedEndpoint, (b"http", b"example.com", 80))
  1031. def test_endpointFactoryDefaultPool(self):
  1032. """
  1033. If no pool is passed in to L{Agent.usingEndpointFactory}, a default
  1034. pool is constructed with no persistent connections.
  1035. """
  1036. agent = client.Agent.usingEndpointFactory(
  1037. self.reactor, StubEndpointFactory())
  1038. pool = agent._pool
  1039. self.assertEqual((pool.__class__, pool.persistent, pool._reactor),
  1040. (HTTPConnectionPool, False, agent._reactor))
  1041. def test_endpointFactoryPool(self):
  1042. """
  1043. If a pool is passed in to L{Agent.usingEndpointFactory} it is used as
  1044. the L{Agent} pool.
  1045. """
  1046. pool = object()
  1047. agent = client.Agent.usingEndpointFactory(
  1048. self.reactor, StubEndpointFactory(), pool)
  1049. self.assertIs(pool, agent._pool)
  1050. class AgentHTTPSTests(TestCase, FakeReactorAndConnectMixin,
  1051. IntegrationTestingMixin):
  1052. """
  1053. Tests for the new HTTP client API that depends on SSL.
  1054. """
  1055. skip = skipWhenNoSSL
  1056. def makeEndpoint(self, host=b'example.com', port=443):
  1057. """
  1058. Create an L{Agent} with an https scheme and return its endpoint
  1059. created according to the arguments.
  1060. @param host: The host for the endpoint.
  1061. @type host: L{bytes}
  1062. @param port: The port for the endpoint.
  1063. @type port: L{int}
  1064. @return: An endpoint of an L{Agent} constructed according to args.
  1065. @rtype: L{SSL4ClientEndpoint}
  1066. """
  1067. return client.Agent(self.createReactor())._getEndpoint(
  1068. URI.fromBytes(b'https://' + host + b":" + intToBytes(port) + b"/"))
  1069. def test_endpointType(self):
  1070. """
  1071. L{Agent._getEndpoint} return a L{SSL4ClientEndpoint} when passed a
  1072. scheme of C{'https'}.
  1073. """
  1074. from twisted.internet.endpoints import _WrapperEndpoint
  1075. endpoint = self.makeEndpoint()
  1076. self.assertIsInstance(endpoint, _WrapperEndpoint)
  1077. self.assertIsInstance(endpoint._wrappedEndpoint, HostnameEndpoint)
  1078. def test_hostArgumentIsRespected(self):
  1079. """
  1080. If a host is passed, the endpoint respects it.
  1081. """
  1082. endpoint = self.makeEndpoint(host=b"example.com")
  1083. self.assertEqual(endpoint._wrappedEndpoint._hostStr, "example.com")
  1084. def test_portArgumentIsRespected(self):
  1085. """
  1086. If a port is passed, the endpoint respects it.
  1087. """
  1088. expectedPort = 4321
  1089. endpoint = self.makeEndpoint(port=expectedPort)
  1090. self.assertEqual(endpoint._wrappedEndpoint._port, expectedPort)
  1091. def test_contextFactoryType(self):
  1092. """
  1093. L{Agent} wraps its connection creator creator and uses modern TLS APIs.
  1094. """
  1095. endpoint = self.makeEndpoint()
  1096. contextFactory = endpoint._wrapperFactory(None)._connectionCreator
  1097. self.assertIsInstance(contextFactory, ClientTLSOptions)
  1098. self.assertEqual(contextFactory._hostname, u"example.com")
  1099. def test_connectHTTPSCustomConnectionCreator(self):
  1100. """
  1101. If a custom L{WebClientConnectionCreator}-like object is passed to
  1102. L{Agent.__init__} it will be used to determine the SSL parameters for
  1103. HTTPS requests. When an HTTPS request is made, the hostname and port
  1104. number of the request URL will be passed to the connection creator's
  1105. C{creatorForNetloc} method. The resulting context object will be used
  1106. to establish the SSL connection.
  1107. """
  1108. expectedHost = b'example.org'
  1109. expectedPort = 20443
  1110. class JustEnoughConnection(object):
  1111. handshakeStarted = False
  1112. connectState = False
  1113. def do_handshake(self):
  1114. """
  1115. The handshake started. Record that fact.
  1116. """
  1117. self.handshakeStarted = True
  1118. def set_connect_state(self):
  1119. """
  1120. The connection started. Record that fact.
  1121. """
  1122. self.connectState = True
  1123. contextArgs = []
  1124. @implementer(IOpenSSLClientConnectionCreator)
  1125. class JustEnoughCreator(object):
  1126. def __init__(self, hostname, port):
  1127. self.hostname = hostname
  1128. self.port = port
  1129. def clientConnectionForTLS(self, tlsProtocol):
  1130. """
  1131. Implement L{IOpenSSLClientConnectionCreator}.
  1132. @param tlsProtocol: The TLS protocol.
  1133. @type tlsProtocol: L{TLSMemoryBIOProtocol}
  1134. @return: C{expectedConnection}
  1135. """
  1136. contextArgs.append((tlsProtocol, self.hostname, self.port))
  1137. return expectedConnection
  1138. expectedConnection = JustEnoughConnection()
  1139. @implementer(IPolicyForHTTPS)
  1140. class StubBrowserLikePolicyForHTTPS(object):
  1141. def creatorForNetloc(self, hostname, port):
  1142. """
  1143. Emulate L{BrowserLikePolicyForHTTPS}.
  1144. @param hostname: The hostname to verify.
  1145. @type hostname: L{bytes}
  1146. @param port: The port number.
  1147. @type port: L{int}
  1148. @return: a stub L{IOpenSSLClientConnectionCreator}
  1149. @rtype: L{JustEnoughCreator}
  1150. """
  1151. return JustEnoughCreator(hostname, port)
  1152. expectedCreatorCreator = StubBrowserLikePolicyForHTTPS()
  1153. reactor = self.createReactor()
  1154. agent = client.Agent(reactor, expectedCreatorCreator)
  1155. endpoint = agent._getEndpoint(URI.fromBytes(
  1156. b'https://' + expectedHost + b":" + intToBytes(expectedPort)))
  1157. endpoint.connect(Factory.forProtocol(Protocol))
  1158. tlsFactory = reactor.tcpClients[-1][2]
  1159. tlsProtocol = tlsFactory.buildProtocol(None)
  1160. tlsProtocol.makeConnection(StringTransport())
  1161. tls = contextArgs[0][0]
  1162. self.assertIsInstance(tls, TLSMemoryBIOProtocol)
  1163. self.assertEqual(contextArgs[0][1:], (expectedHost, expectedPort))
  1164. self.assertTrue(expectedConnection.handshakeStarted)
  1165. self.assertTrue(expectedConnection.connectState)
  1166. def test_deprecatedDuckPolicy(self):
  1167. """
  1168. Passing something that duck-types I{like} a L{web client context
  1169. factory <twisted.web.client.WebClientContextFactory>} - something that
  1170. does not provide L{IPolicyForHTTPS} - to L{Agent} emits a
  1171. L{DeprecationWarning} even if you don't actually C{import
  1172. WebClientContextFactory} to do it.
  1173. """
  1174. def warnMe():
  1175. client.Agent(deterministicResolvingReactor(MemoryReactorClock()),
  1176. "does-not-provide-IPolicyForHTTPS")
  1177. warnMe()
  1178. warnings = self.flushWarnings([warnMe])
  1179. self.assertEqual(len(warnings), 1)
  1180. [warning] = warnings
  1181. self.assertEqual(warning['category'], DeprecationWarning)
  1182. self.assertEqual(
  1183. warning['message'],
  1184. "'does-not-provide-IPolicyForHTTPS' was passed as the HTTPS "
  1185. "policy for an Agent, but it does not provide IPolicyForHTTPS. "
  1186. "Since Twisted 14.0, you must pass a provider of IPolicyForHTTPS."
  1187. )
  1188. def test_alternateTrustRoot(self):
  1189. """
  1190. L{BrowserLikePolicyForHTTPS.creatorForNetloc} returns an
  1191. L{IOpenSSLClientConnectionCreator} provider which will add certificates
  1192. from the given trust root.
  1193. """
  1194. @implementer(IOpenSSLTrustRoot)
  1195. class CustomOpenSSLTrustRoot(object):
  1196. called = False
  1197. context = None
  1198. def _addCACertsToContext(self, context):
  1199. self.called = True
  1200. self.context = context
  1201. trustRoot = CustomOpenSSLTrustRoot()
  1202. policy = BrowserLikePolicyForHTTPS(trustRoot=trustRoot)
  1203. creator = policy.creatorForNetloc(b"thingy", 4321)
  1204. self.assertTrue(trustRoot.called)
  1205. connection = creator.clientConnectionForTLS(None)
  1206. self.assertIs(trustRoot.context, connection.get_context())
  1207. def integrationTest(self, hostName, expectedAddress, addressType):
  1208. """
  1209. Wrap L{AgentTestsMixin.integrationTest} with TLS.
  1210. """
  1211. authority, server = certificatesForAuthorityAndServer(hostName
  1212. .decode('ascii'))
  1213. def tlsify(serverFactory):
  1214. return TLSMemoryBIOFactory(server.options(), False, serverFactory)
  1215. def tlsagent(reactor):
  1216. from twisted.web.iweb import IPolicyForHTTPS
  1217. from zope.interface import implementer
  1218. @implementer(IPolicyForHTTPS)
  1219. class Policy(object):
  1220. def creatorForNetloc(self, hostname, port):
  1221. return optionsForClientTLS(hostname.decode("ascii"),
  1222. trustRoot=authority)
  1223. return client.Agent(reactor, contextFactory=Policy())
  1224. (super(AgentHTTPSTests, self)
  1225. .integrationTest(hostName, expectedAddress, addressType,
  1226. serverWrapper=tlsify,
  1227. createAgent=tlsagent,
  1228. scheme=b'https'))
  1229. class WebClientContextFactoryTests(TestCase):
  1230. """
  1231. Tests for the context factory wrapper for web clients
  1232. L{twisted.web.client.WebClientContextFactory}.
  1233. """
  1234. def setUp(self):
  1235. """
  1236. Get WebClientContextFactory while quashing its deprecation warning.
  1237. """
  1238. from twisted.web.client import WebClientContextFactory
  1239. self.warned = self.flushWarnings([WebClientContextFactoryTests.setUp])
  1240. self.webClientContextFactory = WebClientContextFactory
  1241. def test_deprecated(self):
  1242. """
  1243. L{twisted.web.client.WebClientContextFactory} is deprecated. Importing
  1244. it displays a warning.
  1245. """
  1246. self.assertEqual(len(self.warned), 1)
  1247. [warning] = self.warned
  1248. self.assertEqual(warning['category'], DeprecationWarning)
  1249. self.assertEqual(
  1250. warning['message'],
  1251. getDeprecationWarningString(
  1252. self.webClientContextFactory, Version("Twisted", 14, 0, 0),
  1253. replacement=BrowserLikePolicyForHTTPS,
  1254. )
  1255. # See https://twistedmatrix.com/trac/ticket/7242
  1256. .replace(";", ":")
  1257. )
  1258. def test_missingSSL(self):
  1259. """
  1260. If C{getContext} is called and SSL is not available, raise
  1261. L{NotImplementedError}.
  1262. """
  1263. self.assertRaises(
  1264. NotImplementedError,
  1265. self.webClientContextFactory().getContext,
  1266. b'example.com', 443,
  1267. )
  1268. def test_returnsContext(self):
  1269. """
  1270. If SSL is present, C{getContext} returns a L{OpenSSL.SSL.Context}.
  1271. """
  1272. ctx = self.webClientContextFactory().getContext('example.com', 443)
  1273. self.assertIsInstance(ctx, ssl.SSL.Context)
  1274. def test_setsTrustRootOnContextToDefaultTrustRoot(self):
  1275. """
  1276. The L{CertificateOptions} has C{trustRoot} set to the default trust
  1277. roots.
  1278. """
  1279. ctx = self.webClientContextFactory()
  1280. certificateOptions = ctx._getCertificateOptions('example.com', 443)
  1281. self.assertIsInstance(
  1282. certificateOptions.trustRoot, ssl.OpenSSLDefaultPaths)
  1283. test_returnsContext.skip \
  1284. = test_setsTrustRootOnContextToDefaultTrustRoot.skip \
  1285. = skipWhenNoSSL
  1286. test_missingSSL.skip = skipWhenSSLPresent
  1287. class HTTPConnectionPoolRetryTests(TestCase, FakeReactorAndConnectMixin):
  1288. """
  1289. L{client.HTTPConnectionPool}, by using
  1290. L{client._RetryingHTTP11ClientProtocol}, supports retrying requests done
  1291. against previously cached connections.
  1292. """
  1293. def test_onlyRetryIdempotentMethods(self):
  1294. """
  1295. Only GET, HEAD, OPTIONS, TRACE, DELETE methods cause a retry.
  1296. """
  1297. pool = client.HTTPConnectionPool(None)
  1298. connection = client._RetryingHTTP11ClientProtocol(None, pool)
  1299. self.assertTrue(connection._shouldRetry(
  1300. b"GET", RequestNotSent(), None))
  1301. self.assertTrue(connection._shouldRetry(
  1302. b"HEAD", RequestNotSent(), None))
  1303. self.assertTrue(connection._shouldRetry(
  1304. b"OPTIONS", RequestNotSent(), None))
  1305. self.assertTrue(connection._shouldRetry(
  1306. b"TRACE", RequestNotSent(), None))
  1307. self.assertTrue(connection._shouldRetry(
  1308. b"DELETE", RequestNotSent(), None))
  1309. self.assertFalse(connection._shouldRetry(
  1310. b"POST", RequestNotSent(), None))
  1311. self.assertFalse(connection._shouldRetry(
  1312. b"MYMETHOD", RequestNotSent(), None))
  1313. # This will be covered by a different ticket, since we need support
  1314. #for resettable body producers:
  1315. # self.assertTrue(connection._doRetry("PUT", RequestNotSent(), None))
  1316. def test_onlyRetryIfNoResponseReceived(self):
  1317. """
  1318. Only L{RequestNotSent}, L{RequestTransmissionFailed} and
  1319. L{ResponseNeverReceived} exceptions cause a retry.
  1320. """
  1321. pool = client.HTTPConnectionPool(None)
  1322. connection = client._RetryingHTTP11ClientProtocol(None, pool)
  1323. self.assertTrue(connection._shouldRetry(
  1324. b"GET", RequestNotSent(), None))
  1325. self.assertTrue(connection._shouldRetry(
  1326. b"GET", RequestTransmissionFailed([]), None))
  1327. self.assertTrue(connection._shouldRetry(
  1328. b"GET", ResponseNeverReceived([]),None))
  1329. self.assertFalse(connection._shouldRetry(
  1330. b"GET", ResponseFailed([]), None))
  1331. self.assertFalse(connection._shouldRetry(
  1332. b"GET", ConnectionRefusedError(), None))
  1333. def test_dontRetryIfFailedDueToCancel(self):
  1334. """
  1335. If a request failed due to the operation being cancelled,
  1336. C{_shouldRetry} returns C{False} to indicate the request should not be
  1337. retried.
  1338. """
  1339. pool = client.HTTPConnectionPool(None)
  1340. connection = client._RetryingHTTP11ClientProtocol(None, pool)
  1341. exception = ResponseNeverReceived([Failure(defer.CancelledError())])
  1342. self.assertFalse(connection._shouldRetry(b"GET", exception, None))
  1343. def test_retryIfFailedDueToNonCancelException(self):
  1344. """
  1345. If a request failed with L{ResponseNeverReceived} due to some
  1346. arbitrary exception, C{_shouldRetry} returns C{True} to indicate the
  1347. request should be retried.
  1348. """
  1349. pool = client.HTTPConnectionPool(None)
  1350. connection = client._RetryingHTTP11ClientProtocol(None, pool)
  1351. self.assertTrue(connection._shouldRetry(
  1352. b"GET", ResponseNeverReceived([Failure(Exception())]), None))
  1353. def test_wrappedOnPersistentReturned(self):
  1354. """
  1355. If L{client.HTTPConnectionPool.getConnection} returns a previously
  1356. cached connection, it will get wrapped in a
  1357. L{client._RetryingHTTP11ClientProtocol}.
  1358. """
  1359. pool = client.HTTPConnectionPool(Clock())
  1360. # Add a connection to the cache:
  1361. protocol = StubHTTPProtocol()
  1362. protocol.makeConnection(StringTransport())
  1363. pool._putConnection(123, protocol)
  1364. # Retrieve it, it should come back wrapped in a
  1365. # _RetryingHTTP11ClientProtocol:
  1366. d = pool.getConnection(123, DummyEndpoint())
  1367. def gotConnection(connection):
  1368. self.assertIsInstance(connection,
  1369. client._RetryingHTTP11ClientProtocol)
  1370. self.assertIdentical(connection._clientProtocol, protocol)
  1371. return d.addCallback(gotConnection)
  1372. def test_notWrappedOnNewReturned(self):
  1373. """
  1374. If L{client.HTTPConnectionPool.getConnection} returns a new
  1375. connection, it will be returned as is.
  1376. """
  1377. pool = client.HTTPConnectionPool(None)
  1378. d = pool.getConnection(123, DummyEndpoint())
  1379. def gotConnection(connection):
  1380. # Don't want to use isinstance since potentially the wrapper might
  1381. # subclass it at some point:
  1382. self.assertIdentical(connection.__class__, HTTP11ClientProtocol)
  1383. return d.addCallback(gotConnection)
  1384. def retryAttempt(self, willWeRetry):
  1385. """
  1386. Fail a first request, possibly retrying depending on argument.
  1387. """
  1388. protocols = []
  1389. def newProtocol():
  1390. protocol = StubHTTPProtocol()
  1391. protocols.append(protocol)
  1392. return defer.succeed(protocol)
  1393. bodyProducer = object()
  1394. request = client.Request(b"FOO", b"/", client.Headers(), bodyProducer,
  1395. persistent=True)
  1396. newProtocol()
  1397. protocol = protocols[0]
  1398. retrier = client._RetryingHTTP11ClientProtocol(protocol, newProtocol)
  1399. def _shouldRetry(m, e, bp):
  1400. self.assertEqual(m, b"FOO")
  1401. self.assertIdentical(bp, bodyProducer)
  1402. self.assertIsInstance(e, (RequestNotSent, ResponseNeverReceived))
  1403. return willWeRetry
  1404. retrier._shouldRetry = _shouldRetry
  1405. d = retrier.request(request)
  1406. # So far, one request made:
  1407. self.assertEqual(len(protocols), 1)
  1408. self.assertEqual(len(protocols[0].requests), 1)
  1409. # Fail the first request:
  1410. protocol.requests[0][1].errback(RequestNotSent())
  1411. return d, protocols
  1412. def test_retryIfShouldRetryReturnsTrue(self):
  1413. """
  1414. L{client._RetryingHTTP11ClientProtocol} retries when
  1415. L{client._RetryingHTTP11ClientProtocol._shouldRetry} returns C{True}.
  1416. """
  1417. d, protocols = self.retryAttempt(True)
  1418. # We retried!
  1419. self.assertEqual(len(protocols), 2)
  1420. response = object()
  1421. protocols[1].requests[0][1].callback(response)
  1422. return d.addCallback(self.assertIdentical, response)
  1423. def test_dontRetryIfShouldRetryReturnsFalse(self):
  1424. """
  1425. L{client._RetryingHTTP11ClientProtocol} does not retry when
  1426. L{client._RetryingHTTP11ClientProtocol._shouldRetry} returns C{False}.
  1427. """
  1428. d, protocols = self.retryAttempt(False)
  1429. # We did not retry:
  1430. self.assertEqual(len(protocols), 1)
  1431. return self.assertFailure(d, RequestNotSent)
  1432. def test_onlyRetryWithoutBody(self):
  1433. """
  1434. L{_RetryingHTTP11ClientProtocol} only retries queries that don't have
  1435. a body.
  1436. This is an implementation restriction; if the restriction is fixed,
  1437. this test should be removed and PUT added to list of methods that
  1438. support retries.
  1439. """
  1440. pool = client.HTTPConnectionPool(None)
  1441. connection = client._RetryingHTTP11ClientProtocol(None, pool)
  1442. self.assertTrue(connection._shouldRetry(b"GET", RequestNotSent(), None))
  1443. self.assertFalse(connection._shouldRetry(b"GET", RequestNotSent(), object()))
  1444. def test_onlyRetryOnce(self):
  1445. """
  1446. If a L{client._RetryingHTTP11ClientProtocol} fails more than once on
  1447. an idempotent query before a response is received, it will not retry.
  1448. """
  1449. d, protocols = self.retryAttempt(True)
  1450. self.assertEqual(len(protocols), 2)
  1451. # Fail the second request too:
  1452. protocols[1].requests[0][1].errback(ResponseNeverReceived([]))
  1453. # We didn't retry again:
  1454. self.assertEqual(len(protocols), 2)
  1455. return self.assertFailure(d, ResponseNeverReceived)
  1456. def test_dontRetryIfRetryAutomaticallyFalse(self):
  1457. """
  1458. If L{HTTPConnectionPool.retryAutomatically} is set to C{False}, don't
  1459. wrap connections with retrying logic.
  1460. """
  1461. pool = client.HTTPConnectionPool(Clock())
  1462. pool.retryAutomatically = False
  1463. # Add a connection to the cache:
  1464. protocol = StubHTTPProtocol()
  1465. protocol.makeConnection(StringTransport())
  1466. pool._putConnection(123, protocol)
  1467. # Retrieve it, it should come back unwrapped:
  1468. d = pool.getConnection(123, DummyEndpoint())
  1469. def gotConnection(connection):
  1470. self.assertIdentical(connection, protocol)
  1471. return d.addCallback(gotConnection)
  1472. def test_retryWithNewConnection(self):
  1473. """
  1474. L{client.HTTPConnectionPool} creates
  1475. {client._RetryingHTTP11ClientProtocol} with a new connection factory
  1476. method that creates a new connection using the same key and endpoint
  1477. as the wrapped connection.
  1478. """
  1479. pool = client.HTTPConnectionPool(Clock())
  1480. key = 123
  1481. endpoint = DummyEndpoint()
  1482. newConnections = []
  1483. # Override the pool's _newConnection:
  1484. def newConnection(k, e):
  1485. newConnections.append((k, e))
  1486. pool._newConnection = newConnection
  1487. # Add a connection to the cache:
  1488. protocol = StubHTTPProtocol()
  1489. protocol.makeConnection(StringTransport())
  1490. pool._putConnection(key, protocol)
  1491. # Retrieve it, it should come back wrapped in a
  1492. # _RetryingHTTP11ClientProtocol:
  1493. d = pool.getConnection(key, endpoint)
  1494. def gotConnection(connection):
  1495. self.assertIsInstance(connection,
  1496. client._RetryingHTTP11ClientProtocol)
  1497. self.assertIdentical(connection._clientProtocol, protocol)
  1498. # Verify that the _newConnection method on retrying connection
  1499. # calls _newConnection on the pool:
  1500. self.assertEqual(newConnections, [])
  1501. connection._newConnection()
  1502. self.assertEqual(len(newConnections), 1)
  1503. self.assertEqual(newConnections[0][0], key)
  1504. self.assertIdentical(newConnections[0][1], endpoint)
  1505. return d.addCallback(gotConnection)
  1506. class CookieTestsMixin(object):
  1507. """
  1508. Mixin for unit tests dealing with cookies.
  1509. """
  1510. def addCookies(self, cookieJar, uri, cookies):
  1511. """
  1512. Add a cookie to a cookie jar.
  1513. """
  1514. response = client._FakeUrllib2Response(
  1515. client.Response(
  1516. (b'HTTP', 1, 1),
  1517. 200,
  1518. b'OK',
  1519. client.Headers({b'Set-Cookie': cookies}),
  1520. None))
  1521. request = client._FakeUrllib2Request(uri)
  1522. cookieJar.extract_cookies(response, request)
  1523. return request, response
  1524. class CookieJarTests(TestCase, CookieTestsMixin):
  1525. """
  1526. Tests for L{twisted.web.client._FakeUrllib2Response} and
  1527. L{twisted.web.client._FakeUrllib2Request}'s interactions with
  1528. C{cookielib.CookieJar} instances.
  1529. """
  1530. def makeCookieJar(self):
  1531. """
  1532. @return: a C{cookielib.CookieJar} with some sample cookies
  1533. """
  1534. cookieJar = cookielib.CookieJar()
  1535. reqres = self.addCookies(
  1536. cookieJar,
  1537. b'http://example.com:1234/foo?bar',
  1538. [b'foo=1; cow=moo; Path=/foo; Comment=hello',
  1539. b'bar=2; Comment=goodbye'])
  1540. return cookieJar, reqres
  1541. def test_extractCookies(self):
  1542. """
  1543. L{cookielib.CookieJar.extract_cookies} extracts cookie information from
  1544. fake urllib2 response instances.
  1545. """
  1546. jar = self.makeCookieJar()[0]
  1547. cookies = dict([(c.name, c) for c in jar])
  1548. cookie = cookies['foo']
  1549. self.assertEqual(cookie.version, 0)
  1550. self.assertEqual(cookie.name, 'foo')
  1551. self.assertEqual(cookie.value, '1')
  1552. self.assertEqual(cookie.path, '/foo')
  1553. self.assertEqual(cookie.comment, 'hello')
  1554. self.assertEqual(cookie.get_nonstandard_attr('cow'), 'moo')
  1555. cookie = cookies['bar']
  1556. self.assertEqual(cookie.version, 0)
  1557. self.assertEqual(cookie.name, 'bar')
  1558. self.assertEqual(cookie.value, '2')
  1559. self.assertEqual(cookie.path, '/')
  1560. self.assertEqual(cookie.comment, 'goodbye')
  1561. self.assertIdentical(cookie.get_nonstandard_attr('cow'), None)
  1562. def test_sendCookie(self):
  1563. """
  1564. L{cookielib.CookieJar.add_cookie_header} adds a cookie header to a fake
  1565. urllib2 request instance.
  1566. """
  1567. jar, (request, response) = self.makeCookieJar()
  1568. self.assertIdentical(
  1569. request.get_header('Cookie', None),
  1570. None)
  1571. jar.add_cookie_header(request)
  1572. self.assertEqual(
  1573. request.get_header('Cookie', None),
  1574. 'foo=1; bar=2')
  1575. class CookieAgentTests(TestCase, CookieTestsMixin, FakeReactorAndConnectMixin,
  1576. AgentTestsMixin):
  1577. """
  1578. Tests for L{twisted.web.client.CookieAgent}.
  1579. """
  1580. def makeAgent(self):
  1581. """
  1582. @return: a new L{twisted.web.client.CookieAgent}
  1583. """
  1584. return client.CookieAgent(
  1585. self.buildAgentForWrapperTest(self.reactor),
  1586. cookielib.CookieJar())
  1587. def setUp(self):
  1588. self.reactor = self.createReactor()
  1589. def test_emptyCookieJarRequest(self):
  1590. """
  1591. L{CookieAgent.request} does not insert any C{'Cookie'} header into the
  1592. L{Request} object if there is no cookie in the cookie jar for the URI
  1593. being requested. Cookies are extracted from the response and stored in
  1594. the cookie jar.
  1595. """
  1596. cookieJar = cookielib.CookieJar()
  1597. self.assertEqual(list(cookieJar), [])
  1598. agent = self.buildAgentForWrapperTest(self.reactor)
  1599. cookieAgent = client.CookieAgent(agent, cookieJar)
  1600. d = cookieAgent.request(
  1601. b'GET', b'http://example.com:1234/foo?bar')
  1602. def _checkCookie(ignored):
  1603. cookies = list(cookieJar)
  1604. self.assertEqual(len(cookies), 1)
  1605. self.assertEqual(cookies[0].name, 'foo')
  1606. self.assertEqual(cookies[0].value, '1')
  1607. d.addCallback(_checkCookie)
  1608. req, res = self.protocol.requests.pop()
  1609. self.assertIdentical(req.headers.getRawHeaders(b'cookie'), None)
  1610. resp = client.Response(
  1611. (b'HTTP', 1, 1),
  1612. 200,
  1613. b'OK',
  1614. client.Headers({b'Set-Cookie': [b'foo=1',]}),
  1615. None)
  1616. res.callback(resp)
  1617. return d
  1618. def test_requestWithCookie(self):
  1619. """
  1620. L{CookieAgent.request} inserts a C{'Cookie'} header into the L{Request}
  1621. object when there is a cookie matching the request URI in the cookie
  1622. jar.
  1623. """
  1624. uri = b'http://example.com:1234/foo?bar'
  1625. cookie = b'foo=1'
  1626. cookieJar = cookielib.CookieJar()
  1627. self.addCookies(cookieJar, uri, [cookie])
  1628. self.assertEqual(len(list(cookieJar)), 1)
  1629. agent = self.buildAgentForWrapperTest(self.reactor)
  1630. cookieAgent = client.CookieAgent(agent, cookieJar)
  1631. cookieAgent.request(b'GET', uri)
  1632. req, res = self.protocol.requests.pop()
  1633. self.assertEqual(req.headers.getRawHeaders(b'cookie'), [cookie])
  1634. def test_secureCookie(self):
  1635. """
  1636. L{CookieAgent} is able to handle secure cookies, ie cookies which
  1637. should only be handled over https.
  1638. """
  1639. uri = b'https://example.com:1234/foo?bar'
  1640. cookie = b'foo=1;secure'
  1641. cookieJar = cookielib.CookieJar()
  1642. self.addCookies(cookieJar, uri, [cookie])
  1643. self.assertEqual(len(list(cookieJar)), 1)
  1644. agent = self.buildAgentForWrapperTest(self.reactor)
  1645. cookieAgent = client.CookieAgent(agent, cookieJar)
  1646. cookieAgent.request(b'GET', uri)
  1647. req, res = self.protocol.requests.pop()
  1648. self.assertEqual(req.headers.getRawHeaders(b'cookie'), [b'foo=1'])
  1649. test_secureCookie.skip = skipWhenNoSSL
  1650. def test_secureCookieOnInsecureConnection(self):
  1651. """
  1652. If a cookie is setup as secure, it won't be sent with the request if
  1653. it's not over HTTPS.
  1654. """
  1655. uri = b'http://example.com/foo?bar'
  1656. cookie = b'foo=1;secure'
  1657. cookieJar = cookielib.CookieJar()
  1658. self.addCookies(cookieJar, uri, [cookie])
  1659. self.assertEqual(len(list(cookieJar)), 1)
  1660. agent = self.buildAgentForWrapperTest(self.reactor)
  1661. cookieAgent = client.CookieAgent(agent, cookieJar)
  1662. cookieAgent.request(b'GET', uri)
  1663. req, res = self.protocol.requests.pop()
  1664. self.assertIdentical(None, req.headers.getRawHeaders(b'cookie'))
  1665. def test_portCookie(self):
  1666. """
  1667. L{CookieAgent} supports cookies which enforces the port number they
  1668. need to be transferred upon.
  1669. """
  1670. uri = b'http://example.com:1234/foo?bar'
  1671. cookie = b'foo=1;port=1234'
  1672. cookieJar = cookielib.CookieJar()
  1673. self.addCookies(cookieJar, uri, [cookie])
  1674. self.assertEqual(len(list(cookieJar)), 1)
  1675. agent = self.buildAgentForWrapperTest(self.reactor)
  1676. cookieAgent = client.CookieAgent(agent, cookieJar)
  1677. cookieAgent.request(b'GET', uri)
  1678. req, res = self.protocol.requests.pop()
  1679. self.assertEqual(req.headers.getRawHeaders(b'cookie'), [b'foo=1'])
  1680. def test_portCookieOnWrongPort(self):
  1681. """
  1682. When creating a cookie with a port directive, it won't be added to the
  1683. L{cookie.CookieJar} if the URI is on a different port.
  1684. """
  1685. uri = b'http://example.com:4567/foo?bar'
  1686. cookie = b'foo=1;port=1234'
  1687. cookieJar = cookielib.CookieJar()
  1688. self.addCookies(cookieJar, uri, [cookie])
  1689. self.assertEqual(len(list(cookieJar)), 0)
  1690. class Decoder1(proxyForInterface(IResponse)):
  1691. """
  1692. A test decoder to be used by L{client.ContentDecoderAgent} tests.
  1693. """
  1694. class Decoder2(Decoder1):
  1695. """
  1696. A test decoder to be used by L{client.ContentDecoderAgent} tests.
  1697. """
  1698. class ContentDecoderAgentTests(TestCase, FakeReactorAndConnectMixin,
  1699. AgentTestsMixin):
  1700. """
  1701. Tests for L{client.ContentDecoderAgent}.
  1702. """
  1703. def makeAgent(self):
  1704. """
  1705. @return: a new L{twisted.web.client.ContentDecoderAgent}
  1706. """
  1707. return client.ContentDecoderAgent(self.agent, [])
  1708. def setUp(self):
  1709. """
  1710. Create an L{Agent} wrapped around a fake reactor.
  1711. """
  1712. self.reactor = self.createReactor()
  1713. self.agent = self.buildAgentForWrapperTest(self.reactor)
  1714. def test_acceptHeaders(self):
  1715. """
  1716. L{client.ContentDecoderAgent} sets the I{Accept-Encoding} header to the
  1717. names of the available decoder objects.
  1718. """
  1719. agent = client.ContentDecoderAgent(
  1720. self.agent, [(b'decoder1', Decoder1), (b'decoder2', Decoder2)])
  1721. agent.request(b'GET', b'http://example.com/foo')
  1722. protocol = self.protocol
  1723. self.assertEqual(len(protocol.requests), 1)
  1724. req, res = protocol.requests.pop()
  1725. self.assertEqual(req.headers.getRawHeaders(b'accept-encoding'),
  1726. [b'decoder1,decoder2'])
  1727. def test_existingHeaders(self):
  1728. """
  1729. If there are existing I{Accept-Encoding} fields,
  1730. L{client.ContentDecoderAgent} creates a new field for the decoders it
  1731. knows about.
  1732. """
  1733. headers = http_headers.Headers({b'foo': [b'bar'],
  1734. b'accept-encoding': [b'fizz']})
  1735. agent = client.ContentDecoderAgent(
  1736. self.agent, [(b'decoder1', Decoder1), (b'decoder2', Decoder2)])
  1737. agent.request(b'GET', b'http://example.com/foo', headers=headers)
  1738. protocol = self.protocol
  1739. self.assertEqual(len(protocol.requests), 1)
  1740. req, res = protocol.requests.pop()
  1741. self.assertEqual(
  1742. list(sorted(req.headers.getAllRawHeaders())),
  1743. [(b'Accept-Encoding', [b'fizz', b'decoder1,decoder2']),
  1744. (b'Foo', [b'bar']),
  1745. (b'Host', [b'example.com'])])
  1746. def test_plainEncodingResponse(self):
  1747. """
  1748. If the response is not encoded despited the request I{Accept-Encoding}
  1749. headers, L{client.ContentDecoderAgent} simply forwards the response.
  1750. """
  1751. agent = client.ContentDecoderAgent(
  1752. self.agent, [(b'decoder1', Decoder1), (b'decoder2', Decoder2)])
  1753. deferred = agent.request(b'GET', b'http://example.com/foo')
  1754. req, res = self.protocol.requests.pop()
  1755. response = Response((b'HTTP', 1, 1), 200, b'OK', http_headers.Headers(),
  1756. None)
  1757. res.callback(response)
  1758. return deferred.addCallback(self.assertIdentical, response)
  1759. def test_unsupportedEncoding(self):
  1760. """
  1761. If an encoding unknown to the L{client.ContentDecoderAgent} is found,
  1762. the response is unchanged.
  1763. """
  1764. agent = client.ContentDecoderAgent(
  1765. self.agent, [(b'decoder1', Decoder1), (b'decoder2', Decoder2)])
  1766. deferred = agent.request(b'GET', b'http://example.com/foo')
  1767. req, res = self.protocol.requests.pop()
  1768. headers = http_headers.Headers({b'foo': [b'bar'],
  1769. b'content-encoding': [b'fizz']})
  1770. response = Response((b'HTTP', 1, 1), 200, b'OK', headers, None)
  1771. res.callback(response)
  1772. return deferred.addCallback(self.assertIdentical, response)
  1773. def test_unknownEncoding(self):
  1774. """
  1775. When L{client.ContentDecoderAgent} encounters a decoder it doesn't know
  1776. about, it stops decoding even if another encoding is known afterwards.
  1777. """
  1778. agent = client.ContentDecoderAgent(
  1779. self.agent, [(b'decoder1', Decoder1), (b'decoder2', Decoder2)])
  1780. deferred = agent.request(b'GET', b'http://example.com/foo')
  1781. req, res = self.protocol.requests.pop()
  1782. headers = http_headers.Headers({b'foo': [b'bar'],
  1783. b'content-encoding':
  1784. [b'decoder1,fizz,decoder2']})
  1785. response = Response((b'HTTP', 1, 1), 200, b'OK', headers, None)
  1786. res.callback(response)
  1787. def check(result):
  1788. self.assertNotIdentical(response, result)
  1789. self.assertIsInstance(result, Decoder2)
  1790. self.assertEqual([b'decoder1,fizz'],
  1791. result.headers.getRawHeaders(b'content-encoding'))
  1792. return deferred.addCallback(check)
  1793. class SimpleAgentProtocol(Protocol):
  1794. """
  1795. A L{Protocol} to be used with an L{client.Agent} to receive data.
  1796. @ivar finished: L{Deferred} firing when C{connectionLost} is called.
  1797. @ivar made: L{Deferred} firing when C{connectionMade} is called.
  1798. @ivar received: C{list} of received data.
  1799. """
  1800. def __init__(self):
  1801. self.made = Deferred()
  1802. self.finished = Deferred()
  1803. self.received = []
  1804. def connectionMade(self):
  1805. self.made.callback(None)
  1806. def connectionLost(self, reason):
  1807. self.finished.callback(None)
  1808. def dataReceived(self, data):
  1809. self.received.append(data)
  1810. class ContentDecoderAgentWithGzipTests(TestCase,
  1811. FakeReactorAndConnectMixin):
  1812. def setUp(self):
  1813. """
  1814. Create an L{Agent} wrapped around a fake reactor.
  1815. """
  1816. self.reactor = self.createReactor()
  1817. agent = self.buildAgentForWrapperTest(self.reactor)
  1818. self.agent = client.ContentDecoderAgent(
  1819. agent, [(b"gzip", client.GzipDecoder)])
  1820. def test_gzipEncodingResponse(self):
  1821. """
  1822. If the response has a C{gzip} I{Content-Encoding} header,
  1823. L{GzipDecoder} wraps the response to return uncompressed data to the
  1824. user.
  1825. """
  1826. deferred = self.agent.request(b'GET', b'http://example.com/foo')
  1827. req, res = self.protocol.requests.pop()
  1828. headers = http_headers.Headers({b'foo': [b'bar'],
  1829. b'content-encoding': [b'gzip']})
  1830. transport = StringTransport()
  1831. response = Response((b'HTTP', 1, 1), 200, b'OK', headers, transport)
  1832. response.length = 12
  1833. res.callback(response)
  1834. compressor = zlib.compressobj(2, zlib.DEFLATED, 16 + zlib.MAX_WBITS)
  1835. data = (compressor.compress(b'x' * 6) + compressor.compress(b'y' * 4) +
  1836. compressor.flush())
  1837. def checkResponse(result):
  1838. self.assertNotIdentical(result, response)
  1839. self.assertEqual(result.version, (b'HTTP', 1, 1))
  1840. self.assertEqual(result.code, 200)
  1841. self.assertEqual(result.phrase, b'OK')
  1842. self.assertEqual(list(result.headers.getAllRawHeaders()),
  1843. [(b'Foo', [b'bar'])])
  1844. self.assertEqual(result.length, UNKNOWN_LENGTH)
  1845. self.assertRaises(AttributeError, getattr, result, 'unknown')
  1846. response._bodyDataReceived(data[:5])
  1847. response._bodyDataReceived(data[5:])
  1848. response._bodyDataFinished()
  1849. protocol = SimpleAgentProtocol()
  1850. result.deliverBody(protocol)
  1851. self.assertEqual(protocol.received, [b'x' * 6 + b'y' * 4])
  1852. return defer.gatherResults([protocol.made, protocol.finished])
  1853. deferred.addCallback(checkResponse)
  1854. return deferred
  1855. def test_brokenContent(self):
  1856. """
  1857. If the data received by the L{GzipDecoder} isn't valid gzip-compressed
  1858. data, the call to C{deliverBody} fails with a C{zlib.error}.
  1859. """
  1860. deferred = self.agent.request(b'GET', b'http://example.com/foo')
  1861. req, res = self.protocol.requests.pop()
  1862. headers = http_headers.Headers({b'foo': [b'bar'],
  1863. b'content-encoding': [b'gzip']})
  1864. transport = StringTransport()
  1865. response = Response((b'HTTP', 1, 1), 200, b'OK', headers, transport)
  1866. response.length = 12
  1867. res.callback(response)
  1868. data = b"not gzipped content"
  1869. def checkResponse(result):
  1870. response._bodyDataReceived(data)
  1871. result.deliverBody(Protocol())
  1872. deferred.addCallback(checkResponse)
  1873. self.assertFailure(deferred, client.ResponseFailed)
  1874. def checkFailure(error):
  1875. error.reasons[0].trap(zlib.error)
  1876. self.assertIsInstance(error.response, Response)
  1877. return deferred.addCallback(checkFailure)
  1878. def test_flushData(self):
  1879. """
  1880. When the connection with the server is lost, the gzip protocol calls
  1881. C{flush} on the zlib decompressor object to get uncompressed data which
  1882. may have been buffered.
  1883. """
  1884. class decompressobj(object):
  1885. def __init__(self, wbits):
  1886. pass
  1887. def decompress(self, data):
  1888. return b'x'
  1889. def flush(self):
  1890. return b'y'
  1891. oldDecompressObj = zlib.decompressobj
  1892. zlib.decompressobj = decompressobj
  1893. self.addCleanup(setattr, zlib, 'decompressobj', oldDecompressObj)
  1894. deferred = self.agent.request(b'GET', b'http://example.com/foo')
  1895. req, res = self.protocol.requests.pop()
  1896. headers = http_headers.Headers({b'content-encoding': [b'gzip']})
  1897. transport = StringTransport()
  1898. response = Response((b'HTTP', 1, 1), 200, b'OK', headers, transport)
  1899. res.callback(response)
  1900. def checkResponse(result):
  1901. response._bodyDataReceived(b'data')
  1902. response._bodyDataFinished()
  1903. protocol = SimpleAgentProtocol()
  1904. result.deliverBody(protocol)
  1905. self.assertEqual(protocol.received, [b'x', b'y'])
  1906. return defer.gatherResults([protocol.made, protocol.finished])
  1907. deferred.addCallback(checkResponse)
  1908. return deferred
  1909. def test_flushError(self):
  1910. """
  1911. If the C{flush} call in C{connectionLost} fails, the C{zlib.error}
  1912. exception is caught and turned into a L{ResponseFailed}.
  1913. """
  1914. class decompressobj(object):
  1915. def __init__(self, wbits):
  1916. pass
  1917. def decompress(self, data):
  1918. return b'x'
  1919. def flush(self):
  1920. raise zlib.error()
  1921. oldDecompressObj = zlib.decompressobj
  1922. zlib.decompressobj = decompressobj
  1923. self.addCleanup(setattr, zlib, 'decompressobj', oldDecompressObj)
  1924. deferred = self.agent.request(b'GET', b'http://example.com/foo')
  1925. req, res = self.protocol.requests.pop()
  1926. headers = http_headers.Headers({b'content-encoding': [b'gzip']})
  1927. transport = StringTransport()
  1928. response = Response((b'HTTP', 1, 1), 200, b'OK', headers, transport)
  1929. res.callback(response)
  1930. def checkResponse(result):
  1931. response._bodyDataReceived(b'data')
  1932. response._bodyDataFinished()
  1933. protocol = SimpleAgentProtocol()
  1934. result.deliverBody(protocol)
  1935. self.assertEqual(protocol.received, [b'x', b'y'])
  1936. return defer.gatherResults([protocol.made, protocol.finished])
  1937. deferred.addCallback(checkResponse)
  1938. self.assertFailure(deferred, client.ResponseFailed)
  1939. def checkFailure(error):
  1940. error.reasons[1].trap(zlib.error)
  1941. self.assertIsInstance(error.response, Response)
  1942. return deferred.addCallback(checkFailure)
  1943. class ProxyAgentTests(TestCase, FakeReactorAndConnectMixin, AgentTestsMixin):
  1944. """
  1945. Tests for L{client.ProxyAgent}.
  1946. """
  1947. def makeAgent(self):
  1948. """
  1949. @return: a new L{twisted.web.client.ProxyAgent}
  1950. """
  1951. return client.ProxyAgent(
  1952. TCP4ClientEndpoint(self.reactor, "127.0.0.1", 1234),
  1953. self.reactor)
  1954. def setUp(self):
  1955. self.reactor = self.createReactor()
  1956. self.agent = client.ProxyAgent(
  1957. TCP4ClientEndpoint(self.reactor, "bar", 5678), self.reactor)
  1958. oldEndpoint = self.agent._proxyEndpoint
  1959. self.agent._proxyEndpoint = self.StubEndpoint(oldEndpoint, self)
  1960. def test_proxyRequest(self):
  1961. """
  1962. L{client.ProxyAgent} issues an HTTP request against the proxy, with the
  1963. full URI as path, when C{request} is called.
  1964. """
  1965. headers = http_headers.Headers({b'foo': [b'bar']})
  1966. # Just going to check the body for identity, so it doesn't need to be
  1967. # real.
  1968. body = object()
  1969. self.agent.request(
  1970. b'GET', b'http://example.com:1234/foo?bar', headers, body)
  1971. host, port, factory = self.reactor.tcpClients.pop()[:3]
  1972. self.assertEqual(host, "bar")
  1973. self.assertEqual(port, 5678)
  1974. self.assertIsInstance(factory._wrappedFactory,
  1975. client._HTTP11ClientFactory)
  1976. protocol = self.protocol
  1977. # The request should be issued.
  1978. self.assertEqual(len(protocol.requests), 1)
  1979. req, res = protocol.requests.pop()
  1980. self.assertIsInstance(req, Request)
  1981. self.assertEqual(req.method, b'GET')
  1982. self.assertEqual(req.uri, b'http://example.com:1234/foo?bar')
  1983. self.assertEqual(
  1984. req.headers,
  1985. http_headers.Headers({b'foo': [b'bar'],
  1986. b'host': [b'example.com:1234']}))
  1987. self.assertIdentical(req.bodyProducer, body)
  1988. def test_nonPersistent(self):
  1989. """
  1990. C{ProxyAgent} connections are not persistent by default.
  1991. """
  1992. self.assertEqual(self.agent._pool.persistent, False)
  1993. def test_connectUsesConnectionPool(self):
  1994. """
  1995. When a connection is made by the C{ProxyAgent}, it uses its pool's
  1996. C{getConnection} method to do so, with the endpoint it was constructed
  1997. with and a key of C{("http-proxy", endpoint)}.
  1998. """
  1999. endpoint = DummyEndpoint()
  2000. class DummyPool(object):
  2001. connected = False
  2002. persistent = False
  2003. def getConnection(this, key, ep):
  2004. this.connected = True
  2005. self.assertIdentical(ep, endpoint)
  2006. # The key is *not* tied to the final destination, but only to
  2007. # the address of the proxy, since that's where *we* are
  2008. # connecting:
  2009. self.assertEqual(key, ("http-proxy", endpoint))
  2010. return defer.succeed(StubHTTPProtocol())
  2011. pool = DummyPool()
  2012. agent = client.ProxyAgent(endpoint, self.reactor, pool=pool)
  2013. self.assertIdentical(pool, agent._pool)
  2014. agent.request(b'GET', b'http://foo/')
  2015. self.assertEqual(agent._pool.connected, True)
  2016. class _RedirectAgentTestsMixin(object):
  2017. """
  2018. Test cases mixin for L{RedirectAgentTests} and
  2019. L{BrowserLikeRedirectAgentTests}.
  2020. """
  2021. def test_noRedirect(self):
  2022. """
  2023. L{client.RedirectAgent} behaves like L{client.Agent} if the response
  2024. doesn't contain a redirect.
  2025. """
  2026. deferred = self.agent.request(b'GET', b'http://example.com/foo')
  2027. req, res = self.protocol.requests.pop()
  2028. headers = http_headers.Headers()
  2029. response = Response((b'HTTP', 1, 1), 200, b'OK', headers, None)
  2030. res.callback(response)
  2031. self.assertEqual(0, len(self.protocol.requests))
  2032. result = self.successResultOf(deferred)
  2033. self.assertIdentical(response, result)
  2034. self.assertIdentical(result.previousResponse, None)
  2035. def _testRedirectDefault(self, code):
  2036. """
  2037. When getting a redirect, L{client.RedirectAgent} follows the URL
  2038. specified in the L{Location} header field and make a new request.
  2039. @param code: HTTP status code.
  2040. """
  2041. self.agent.request(b'GET', b'http://example.com/foo')
  2042. host, port = self.reactor.tcpClients.pop()[:2]
  2043. self.assertEqual(EXAMPLE_COM_IP, host)
  2044. self.assertEqual(80, port)
  2045. req, res = self.protocol.requests.pop()
  2046. # If possible (i.e.: SSL support is present), run the test with a
  2047. # cross-scheme redirect to verify that the scheme is honored; if not,
  2048. # let's just make sure it works at all.
  2049. if ssl is None:
  2050. scheme = b'http'
  2051. expectedPort = 80
  2052. else:
  2053. scheme = b'https'
  2054. expectedPort = 443
  2055. headers = http_headers.Headers(
  2056. {b'location': [scheme + b'://example.com/bar']})
  2057. response = Response((b'HTTP', 1, 1), code, b'OK', headers, None)
  2058. res.callback(response)
  2059. req2, res2 = self.protocol.requests.pop()
  2060. self.assertEqual(b'GET', req2.method)
  2061. self.assertEqual(b'/bar', req2.uri)
  2062. host, port = self.reactor.tcpClients.pop()[:2]
  2063. self.assertEqual(EXAMPLE_COM_IP, host)
  2064. self.assertEqual(expectedPort, port)
  2065. def test_redirect301(self):
  2066. """
  2067. L{client.RedirectAgent} follows redirects on status code 301.
  2068. """
  2069. self._testRedirectDefault(301)
  2070. def test_redirect302(self):
  2071. """
  2072. L{client.RedirectAgent} follows redirects on status code 302.
  2073. """
  2074. self._testRedirectDefault(302)
  2075. def test_redirect307(self):
  2076. """
  2077. L{client.RedirectAgent} follows redirects on status code 307.
  2078. """
  2079. self._testRedirectDefault(307)
  2080. def _testRedirectToGet(self, code, method):
  2081. """
  2082. L{client.RedirectAgent} changes the method to I{GET} when getting
  2083. a redirect on a non-I{GET} request.
  2084. @param code: HTTP status code.
  2085. @param method: HTTP request method.
  2086. """
  2087. self.agent.request(method, b'http://example.com/foo')
  2088. req, res = self.protocol.requests.pop()
  2089. headers = http_headers.Headers(
  2090. {b'location': [b'http://example.com/bar']})
  2091. response = Response((b'HTTP', 1, 1), code, b'OK', headers, None)
  2092. res.callback(response)
  2093. req2, res2 = self.protocol.requests.pop()
  2094. self.assertEqual(b'GET', req2.method)
  2095. self.assertEqual(b'/bar', req2.uri)
  2096. def test_redirect303(self):
  2097. """
  2098. L{client.RedirectAgent} changes the method to I{GET} when getting a 303
  2099. redirect on a I{POST} request.
  2100. """
  2101. self._testRedirectToGet(303, b'POST')
  2102. def test_noLocationField(self):
  2103. """
  2104. If no L{Location} header field is found when getting a redirect,
  2105. L{client.RedirectAgent} fails with a L{ResponseFailed} error wrapping a
  2106. L{error.RedirectWithNoLocation} exception.
  2107. """
  2108. deferred = self.agent.request(b'GET', b'http://example.com/foo')
  2109. req, res = self.protocol.requests.pop()
  2110. headers = http_headers.Headers()
  2111. response = Response((b'HTTP', 1, 1), 301, b'OK', headers, None)
  2112. res.callback(response)
  2113. fail = self.failureResultOf(deferred, client.ResponseFailed)
  2114. fail.value.reasons[0].trap(error.RedirectWithNoLocation)
  2115. self.assertEqual(b'http://example.com/foo',
  2116. fail.value.reasons[0].value.uri)
  2117. self.assertEqual(301, fail.value.response.code)
  2118. def _testPageRedirectFailure(self, code, method):
  2119. """
  2120. When getting a redirect on an unsupported request method,
  2121. L{client.RedirectAgent} fails with a L{ResponseFailed} error wrapping
  2122. a L{error.PageRedirect} exception.
  2123. @param code: HTTP status code.
  2124. @param method: HTTP request method.
  2125. """
  2126. deferred = self.agent.request(method, b'http://example.com/foo')
  2127. req, res = self.protocol.requests.pop()
  2128. headers = http_headers.Headers()
  2129. response = Response((b'HTTP', 1, 1), code, b'OK', headers, None)
  2130. res.callback(response)
  2131. fail = self.failureResultOf(deferred, client.ResponseFailed)
  2132. fail.value.reasons[0].trap(error.PageRedirect)
  2133. self.assertEqual(b'http://example.com/foo',
  2134. fail.value.reasons[0].value.location)
  2135. self.assertEqual(code, fail.value.response.code)
  2136. def test_307OnPost(self):
  2137. """
  2138. When getting a 307 redirect on a I{POST} request,
  2139. L{client.RedirectAgent} fails with a L{ResponseFailed} error wrapping
  2140. a L{error.PageRedirect} exception.
  2141. """
  2142. self._testPageRedirectFailure(307, b'POST')
  2143. def test_redirectLimit(self):
  2144. """
  2145. If the limit of redirects specified to L{client.RedirectAgent} is
  2146. reached, the deferred fires with L{ResponseFailed} error wrapping
  2147. a L{InfiniteRedirection} exception.
  2148. """
  2149. agent = self.buildAgentForWrapperTest(self.reactor)
  2150. redirectAgent = client.RedirectAgent(agent, 1)
  2151. deferred = redirectAgent.request(b'GET', b'http://example.com/foo')
  2152. req, res = self.protocol.requests.pop()
  2153. headers = http_headers.Headers(
  2154. {b'location': [b'http://example.com/bar']})
  2155. response = Response((b'HTTP', 1, 1), 302, b'OK', headers, None)
  2156. res.callback(response)
  2157. req2, res2 = self.protocol.requests.pop()
  2158. response2 = Response((b'HTTP', 1, 1), 302, b'OK', headers, None)
  2159. res2.callback(response2)
  2160. fail = self.failureResultOf(deferred, client.ResponseFailed)
  2161. fail.value.reasons[0].trap(error.InfiniteRedirection)
  2162. self.assertEqual(b'http://example.com/foo',
  2163. fail.value.reasons[0].value.location)
  2164. self.assertEqual(302, fail.value.response.code)
  2165. def _testRedirectURI(self, uri, location, finalURI):
  2166. """
  2167. When L{client.RedirectAgent} encounters a relative redirect I{URI}, it
  2168. is resolved against the request I{URI} before following the redirect.
  2169. @param uri: Request URI.
  2170. @param location: I{Location} header redirect URI.
  2171. @param finalURI: Expected final URI.
  2172. """
  2173. self.agent.request(b'GET', uri)
  2174. req, res = self.protocol.requests.pop()
  2175. headers = http_headers.Headers(
  2176. {b'location': [location]})
  2177. response = Response((b'HTTP', 1, 1), 302, b'OK', headers, None)
  2178. res.callback(response)
  2179. req2, res2 = self.protocol.requests.pop()
  2180. self.assertEqual(b'GET', req2.method)
  2181. self.assertEqual(finalURI, req2.absoluteURI)
  2182. def test_relativeURI(self):
  2183. """
  2184. L{client.RedirectAgent} resolves and follows relative I{URI}s in
  2185. redirects, preserving query strings.
  2186. """
  2187. self._testRedirectURI(
  2188. b'http://example.com/foo/bar', b'baz',
  2189. b'http://example.com/foo/baz')
  2190. self._testRedirectURI(
  2191. b'http://example.com/foo/bar', b'/baz',
  2192. b'http://example.com/baz')
  2193. self._testRedirectURI(
  2194. b'http://example.com/foo/bar', b'/baz?a',
  2195. b'http://example.com/baz?a')
  2196. def test_relativeURIPreserveFragments(self):
  2197. """
  2198. L{client.RedirectAgent} resolves and follows relative I{URI}s in
  2199. redirects, preserving fragments in way that complies with the HTTP 1.1
  2200. bis draft.
  2201. @see: U{https://tools.ietf.org/html/draft-ietf-httpbis-p2-semantics-22#section-7.1.2}
  2202. """
  2203. self._testRedirectURI(
  2204. b'http://example.com/foo/bar#frag', b'/baz?a',
  2205. b'http://example.com/baz?a#frag')
  2206. self._testRedirectURI(
  2207. b'http://example.com/foo/bar', b'/baz?a#frag2',
  2208. b'http://example.com/baz?a#frag2')
  2209. def test_relativeURISchemeRelative(self):
  2210. """
  2211. L{client.RedirectAgent} resolves and follows scheme relative I{URI}s in
  2212. redirects, replacing the hostname and port when required.
  2213. """
  2214. self._testRedirectURI(
  2215. b'http://example.com/foo/bar', b'//foo.com/baz',
  2216. b'http://foo.com/baz')
  2217. self._testRedirectURI(
  2218. b'http://example.com/foo/bar', b'//foo.com:81/baz',
  2219. b'http://foo.com:81/baz')
  2220. def test_responseHistory(self):
  2221. """
  2222. L{Response.response} references the previous L{Response} from
  2223. a redirect, or L{None} if there was no previous response.
  2224. """
  2225. agent = self.buildAgentForWrapperTest(self.reactor)
  2226. redirectAgent = client.RedirectAgent(agent)
  2227. deferred = redirectAgent.request(b'GET', b'http://example.com/foo')
  2228. redirectReq, redirectRes = self.protocol.requests.pop()
  2229. headers = http_headers.Headers(
  2230. {b'location': [b'http://example.com/bar']})
  2231. redirectResponse = Response((b'HTTP', 1, 1), 302, b'OK', headers, None)
  2232. redirectRes.callback(redirectResponse)
  2233. req, res = self.protocol.requests.pop()
  2234. response = Response((b'HTTP', 1, 1), 200, b'OK', headers, None)
  2235. res.callback(response)
  2236. finalResponse = self.successResultOf(deferred)
  2237. self.assertIdentical(finalResponse.previousResponse, redirectResponse)
  2238. self.assertIdentical(redirectResponse.previousResponse, None)
  2239. class RedirectAgentTests(TestCase, FakeReactorAndConnectMixin,
  2240. _RedirectAgentTestsMixin, AgentTestsMixin):
  2241. """
  2242. Tests for L{client.RedirectAgent}.
  2243. """
  2244. def makeAgent(self):
  2245. """
  2246. @return: a new L{twisted.web.client.RedirectAgent}
  2247. """
  2248. return client.RedirectAgent(
  2249. self.buildAgentForWrapperTest(self.reactor))
  2250. def setUp(self):
  2251. self.reactor = self.createReactor()
  2252. self.agent = self.makeAgent()
  2253. def test_301OnPost(self):
  2254. """
  2255. When getting a 301 redirect on a I{POST} request,
  2256. L{client.RedirectAgent} fails with a L{ResponseFailed} error wrapping
  2257. a L{error.PageRedirect} exception.
  2258. """
  2259. self._testPageRedirectFailure(301, b'POST')
  2260. def test_302OnPost(self):
  2261. """
  2262. When getting a 302 redirect on a I{POST} request,
  2263. L{client.RedirectAgent} fails with a L{ResponseFailed} error wrapping
  2264. a L{error.PageRedirect} exception.
  2265. """
  2266. self._testPageRedirectFailure(302, b'POST')
  2267. class BrowserLikeRedirectAgentTests(TestCase,
  2268. FakeReactorAndConnectMixin,
  2269. _RedirectAgentTestsMixin,
  2270. AgentTestsMixin):
  2271. """
  2272. Tests for L{client.BrowserLikeRedirectAgent}.
  2273. """
  2274. def makeAgent(self):
  2275. """
  2276. @return: a new L{twisted.web.client.BrowserLikeRedirectAgent}
  2277. """
  2278. return client.BrowserLikeRedirectAgent(
  2279. self.buildAgentForWrapperTest(self.reactor))
  2280. def setUp(self):
  2281. self.reactor = self.createReactor()
  2282. self.agent = self.makeAgent()
  2283. def test_redirectToGet301(self):
  2284. """
  2285. L{client.BrowserLikeRedirectAgent} changes the method to I{GET} when
  2286. getting a 302 redirect on a I{POST} request.
  2287. """
  2288. self._testRedirectToGet(301, b'POST')
  2289. def test_redirectToGet302(self):
  2290. """
  2291. L{client.BrowserLikeRedirectAgent} changes the method to I{GET} when
  2292. getting a 302 redirect on a I{POST} request.
  2293. """
  2294. self._testRedirectToGet(302, b'POST')
  2295. class AbortableStringTransport(StringTransport):
  2296. """
  2297. A version of L{StringTransport} that supports C{abortConnection}.
  2298. """
  2299. # This should be replaced by a common version in #6530.
  2300. aborting = False
  2301. def abortConnection(self):
  2302. """
  2303. A testable version of the C{ITCPTransport.abortConnection} method.
  2304. Since this is a special case of closing the connection,
  2305. C{loseConnection} is also called.
  2306. """
  2307. self.aborting = True
  2308. self.loseConnection()
  2309. class DummyResponse(object):
  2310. """
  2311. Fake L{IResponse} for testing readBody that captures the protocol passed to
  2312. deliverBody and uses it to make a connection with a transport.
  2313. @ivar protocol: After C{deliverBody} is called, the protocol it was called
  2314. with.
  2315. @ivar transport: An instance created by calling C{transportFactory} which
  2316. is used by L{DummyResponse.protocol} to make a connection.
  2317. """
  2318. code = 200
  2319. phrase = b"OK"
  2320. def __init__(self, headers=None, transportFactory=AbortableStringTransport):
  2321. """
  2322. @param headers: The headers for this response. If L{None}, an empty
  2323. L{Headers} instance will be used.
  2324. @type headers: L{Headers}
  2325. @param transportFactory: A callable used to construct the transport.
  2326. """
  2327. if headers is None:
  2328. headers = Headers()
  2329. self.headers = headers
  2330. self.transport = transportFactory()
  2331. def deliverBody(self, protocol):
  2332. """
  2333. Record the given protocol and use it to make a connection with
  2334. L{DummyResponse.transport}.
  2335. """
  2336. self.protocol = protocol
  2337. self.protocol.makeConnection(self.transport)
  2338. class AlreadyCompletedDummyResponse(DummyResponse):
  2339. """
  2340. A dummy response that has already had its transport closed.
  2341. """
  2342. def deliverBody(self, protocol):
  2343. """
  2344. Make the connection, then remove the transport.
  2345. """
  2346. self.protocol = protocol
  2347. self.protocol.makeConnection(self.transport)
  2348. self.protocol.transport = None
  2349. class ReadBodyTests(TestCase):
  2350. """
  2351. Tests for L{client.readBody}
  2352. """
  2353. def test_success(self):
  2354. """
  2355. L{client.readBody} returns a L{Deferred} which fires with the complete
  2356. body of the L{IResponse} provider passed to it.
  2357. """
  2358. response = DummyResponse()
  2359. d = client.readBody(response)
  2360. response.protocol.dataReceived(b"first")
  2361. response.protocol.dataReceived(b"second")
  2362. response.protocol.connectionLost(Failure(ResponseDone()))
  2363. self.assertEqual(self.successResultOf(d), b"firstsecond")
  2364. def test_cancel(self):
  2365. """
  2366. When cancelling the L{Deferred} returned by L{client.readBody}, the
  2367. connection to the server will be aborted.
  2368. """
  2369. response = DummyResponse()
  2370. deferred = client.readBody(response)
  2371. deferred.cancel()
  2372. self.failureResultOf(deferred, defer.CancelledError)
  2373. self.assertTrue(response.transport.aborting)
  2374. def test_withPotentialDataLoss(self):
  2375. """
  2376. If the full body of the L{IResponse} passed to L{client.readBody} is
  2377. not definitely received, the L{Deferred} returned by L{client.readBody}
  2378. fires with a L{Failure} wrapping L{client.PartialDownloadError} with
  2379. the content that was received.
  2380. """
  2381. response = DummyResponse()
  2382. d = client.readBody(response)
  2383. response.protocol.dataReceived(b"first")
  2384. response.protocol.dataReceived(b"second")
  2385. response.protocol.connectionLost(Failure(PotentialDataLoss()))
  2386. failure = self.failureResultOf(d)
  2387. failure.trap(client.PartialDownloadError)
  2388. self.assertEqual({
  2389. "status": failure.value.status,
  2390. "message": failure.value.message,
  2391. "body": failure.value.response,
  2392. }, {
  2393. "status": b"200",
  2394. "message": b"OK",
  2395. "body": b"firstsecond",
  2396. })
  2397. def test_otherErrors(self):
  2398. """
  2399. If there is an exception other than L{client.PotentialDataLoss} while
  2400. L{client.readBody} is collecting the response body, the L{Deferred}
  2401. returned by {client.readBody} fires with that exception.
  2402. """
  2403. response = DummyResponse()
  2404. d = client.readBody(response)
  2405. response.protocol.dataReceived(b"first")
  2406. response.protocol.connectionLost(
  2407. Failure(ConnectionLost("mystery problem")))
  2408. reason = self.failureResultOf(d)
  2409. reason.trap(ConnectionLost)
  2410. self.assertEqual(reason.value.args, ("mystery problem",))
  2411. def test_deprecatedTransport(self):
  2412. """
  2413. Calling L{client.readBody} with a transport that does not implement
  2414. L{twisted.internet.interfaces.ITCPTransport} produces a deprecation
  2415. warning, but no exception when cancelling.
  2416. """
  2417. response = DummyResponse(transportFactory=StringTransport)
  2418. response.transport.abortConnection = None
  2419. d = self.assertWarns(
  2420. DeprecationWarning,
  2421. 'Using readBody with a transport that does not have an '
  2422. 'abortConnection method',
  2423. __file__,
  2424. lambda: client.readBody(response))
  2425. d.cancel()
  2426. self.failureResultOf(d, defer.CancelledError)
  2427. def test_deprecatedTransportNoWarning(self):
  2428. """
  2429. Calling L{client.readBody} with a response that has already had its
  2430. transport closed (eg. for a very small request) will not trigger a
  2431. deprecation warning.
  2432. """
  2433. response = AlreadyCompletedDummyResponse()
  2434. client.readBody(response)
  2435. warnings = self.flushWarnings()
  2436. self.assertEqual(len(warnings), 0)