1 # Copyright (c) Twisted Matrix Laboratories.
2 # See LICENSE for details.
5 Tests for L{twisted.web.client}.
10 from errno import ENOSPC
12 from StringIO import StringIO
14 from urlparse import urlparse, urljoin
16 from zope.interface.verify import verifyObject
18 from twisted.trial import unittest
19 from twisted.web import server, static, client, error, util, resource, http_headers
20 from twisted.web._newclient import RequestNotSent, RequestTransmissionFailed
21 from twisted.web._newclient import ResponseNeverReceived, ResponseFailed
22 from twisted.internet import reactor, defer, interfaces, task
23 from twisted.python.failure import Failure
24 from twisted.python.filepath import FilePath
25 from twisted.python.log import msg
26 from twisted.python.components import proxyForInterface
27 from twisted.protocols.policies import WrappingFactory
28 from twisted.test.proto_helpers import StringTransport
29 from twisted.test.proto_helpers import MemoryReactor
30 from twisted.internet.task import Clock
31 from twisted.internet.error import ConnectionRefusedError, ConnectionDone
32 from twisted.internet.protocol import Protocol, Factory
33 from twisted.internet.defer import Deferred, succeed
34 from twisted.internet.endpoints import TCP4ClientEndpoint, SSL4ClientEndpoint
35 from twisted.web.client import FileBodyProducer, Request, HTTPConnectionPool
36 from twisted.web.client import _WebToNormalContextFactory
37 from twisted.web.client import WebClientContextFactory, _HTTP11ClientFactory
38 from twisted.web.iweb import UNKNOWN_LENGTH, IBodyProducer, IResponse
39 from twisted.web._newclient import HTTP11ClientProtocol, Response
40 from twisted.web.error import SchemeNotSupported
43 from twisted.internet import ssl
49 class ExtendedRedirect(resource.Resource):
53 The HTTP status code is set according to the C{code} query parameter.
55 @type lastMethod: C{str}
56 @ivar lastMethod: Last handled HTTP request method
62 def __init__(self, url):
63 resource.Resource.__init__(self)
67 def render(self, request):
69 self.lastMethod = request.method
72 self.lastMethod = request.method
73 code = int(request.args['code'][0])
74 return self.redirectTo(self.url, request, code)
77 def getChild(self, name, request):
81 def redirectTo(self, url, request, code):
82 request.setResponseCode(code)
83 request.setHeader("location", url)
88 class ForeverTakingResource(resource.Resource):
90 L{ForeverTakingResource} is a resource which never finishes responding
93 def __init__(self, write=False):
94 resource.Resource.__init__(self)
97 def render(self, request):
99 request.write('some bytes')
100 return server.NOT_DONE_YET
103 class CookieMirrorResource(resource.Resource):
104 def render(self, request):
106 for k,v in request.received_cookies.items():
111 class RawCookieMirrorResource(resource.Resource):
112 def render(self, request):
113 return repr(request.getHeader('cookie'))
115 class ErrorResource(resource.Resource):
117 def render(self, request):
118 request.setResponseCode(401)
119 if request.args.get("showlength"):
120 request.setHeader("content-length", "0")
123 class NoLengthResource(resource.Resource):
125 def render(self, request):
130 class HostHeaderResource(resource.Resource):
132 A testing resource which renders itself as the value of the host header
135 def render(self, request):
136 return request.received_headers['host']
140 class PayloadResource(resource.Resource):
142 A testing resource which renders itself as the contents of the request body
143 as long as the request body is 100 bytes long, otherwise which renders
144 itself as C{"ERROR"}.
146 def render(self, request):
147 data = request.content.read()
148 contentLength = request.received_headers['content-length']
149 if len(data) != 100 or int(contentLength) != 100:
154 class DelayResource(resource.Resource):
156 def __init__(self, seconds):
157 self.seconds = seconds
159 def render(self, request):
161 request.write('some bytes')
163 reactor.callLater(self.seconds, response)
164 return server.NOT_DONE_YET
167 class BrokenDownloadResource(resource.Resource):
169 def render(self, request):
170 # only sends 3 bytes even though it claims to send 5
171 request.setHeader("content-length", "5")
175 class CountingRedirect(util.Redirect):
177 A L{util.Redirect} resource that keeps track of the number of times the
178 resource has been accessed.
180 def __init__(self, *a, **kw):
181 util.Redirect.__init__(self, *a, **kw)
184 def render(self, request):
186 return util.Redirect.render(self, request)
189 class CountingResource(resource.Resource):
191 A resource that keeps track of the number of times it has been accessed.
194 resource.Resource.__init__(self)
197 def render(self, request):
202 class ParseUrlTestCase(unittest.TestCase):
204 Test URL parsing facility and defaults values.
207 def test_parse(self):
209 L{client._parse} correctly parses a URL into its various components.
211 # The default port for HTTP is 80.
213 client._parse('http://127.0.0.1/'),
214 ('http', '127.0.0.1', 80, '/'))
216 # The default port for HTTPS is 443.
218 client._parse('https://127.0.0.1/'),
219 ('https', '127.0.0.1', 443, '/'))
223 client._parse('http://spam:12345/'),
224 ('http', 'spam', 12345, '/'))
226 # Weird (but commonly accepted) structure uses default port.
228 client._parse('http://spam:/'),
229 ('http', 'spam', 80, '/'))
231 # Spaces in the hostname are trimmed, the default path is /.
233 client._parse('http://foo '),
234 ('http', 'foo', 80, '/'))
237 def test_externalUnicodeInterference(self):
239 L{client._parse} should return C{str} for the scheme, host, and path
240 elements of its return tuple, even when passed an URL which has
241 previously been passed to L{urlparse} as a C{unicode} string.
243 badInput = u'http://example.com/path'
244 goodInput = badInput.encode('ascii')
246 scheme, host, port, path = client._parse(goodInput)
247 self.assertIsInstance(scheme, str)
248 self.assertIsInstance(host, str)
249 self.assertIsInstance(path, str)
253 class HTTPPageGetterTests(unittest.TestCase):
255 Tests for L{HTTPPagerGetter}, the HTTP client protocol implementation
256 used to implement L{getPage}.
258 def test_earlyHeaders(self):
260 When a connection is made, L{HTTPPagerGetter} sends the headers from
261 its factory's C{headers} dict. If I{Host} or I{Content-Length} is
262 present in this dict, the values are not sent, since they are sent with
263 special values before the C{headers} dict is processed. If
264 I{User-Agent} is present in the dict, it overrides the value of the
265 C{agent} attribute of the factory. If I{Cookie} is present in the
266 dict, its value is added to the values from the factory's C{cookies}
269 factory = client.HTTPClientFactory(
272 cookies={'baz': 'quux'},
273 postdata="some data",
275 'Host': 'example.net',
276 'User-Agent': 'fooble',
277 'Cookie': 'blah blah',
278 'Content-Length': '12981',
280 transport = StringTransport()
281 protocol = client.HTTPPageGetter()
282 protocol.factory = factory
283 protocol.makeConnection(transport)
286 "GET /bar HTTP/1.0\r\n"
287 "Host: example.net\r\n"
288 "User-Agent: foobar\r\n"
289 "Content-Length: 9\r\n"
291 "connection: close\r\n"
292 "Cookie: blah blah; baz=quux\r\n"
297 class GetBodyProtocol(Protocol):
299 def __init__(self, deferred):
300 self.deferred = deferred
303 def dataReceived(self, bytes):
306 def connectionLost(self, reason):
307 self.deferred.callback(self.buf)
310 def getBody(response):
312 response.deliverBody(GetBodyProtocol(d))
316 class WebClientTestCase(unittest.TestCase):
317 def _listen(self, site):
318 return reactor.listenTCP(0, site, interface="127.0.0.1")
321 self.agent = None # for twisted.web.client.Agent test
322 self.cleanupServerConnections = 0
325 FilePath(name).child("file").setContent("0123456789")
326 r = static.File(name)
327 r.putChild("redirect", util.Redirect("/file"))
328 self.infiniteRedirectResource = CountingRedirect("/infiniteRedirect")
329 r.putChild("infiniteRedirect", self.infiniteRedirectResource)
330 r.putChild("wait", ForeverTakingResource())
331 r.putChild("write-then-wait", ForeverTakingResource(write=True))
332 r.putChild("error", ErrorResource())
333 r.putChild("nolength", NoLengthResource())
334 r.putChild("host", HostHeaderResource())
335 r.putChild("payload", PayloadResource())
336 r.putChild("broken", BrokenDownloadResource())
337 r.putChild("cookiemirror", CookieMirrorResource())
338 r.putChild('delay1', DelayResource(1))
339 r.putChild('delay2', DelayResource(2))
341 self.afterFoundGetCounter = CountingResource()
342 r.putChild("afterFoundGetCounter", self.afterFoundGetCounter)
343 r.putChild("afterFoundGetRedirect", util.Redirect("/afterFoundGetCounter"))
345 miscasedHead = static.Data("miscased-head GET response content", "major/minor")
346 miscasedHead.render_Head = lambda request: "miscased-head content"
347 r.putChild("miscased-head", miscasedHead)
349 self.extendedRedirect = ExtendedRedirect('/extendedRedirect')
350 r.putChild("extendedRedirect", self.extendedRedirect)
351 self.site = server.Site(r, timeout=None)
352 self.wrapper = WrappingFactory(self.site)
353 self.port = self._listen(self.wrapper)
354 self.portno = self.port.getHost().port
358 # clean up connections for twisted.web.client.Agent test.
359 self.agent.closeCachedConnections()
362 # If the test indicated it might leave some server-side connections
363 # around, clean them up.
364 connections = self.wrapper.protocols.keys()
365 # If there are fewer server-side connections than requested,
366 # that's okay. Some might have noticed that the client closed
367 # the connection and cleaned up after themselves.
368 for n in range(min(len(connections), self.cleanupServerConnections)):
369 proto = connections.pop()
370 msg("Closing %r" % (proto,))
371 proto.transport.loseConnection()
373 msg("Some left-over connections; this test is probably buggy.")
374 return self.port.stopListening()
376 def getURL(self, path):
377 host = "http://127.0.0.1:%d/" % self.portno
378 return urljoin(host, path)
380 def testPayload(self):
381 s = "0123456789" * 10
382 return client.getPage(self.getURL("payload"), postdata=s
383 ).addCallback(self.assertEqual, s
387 def test_getPageBrokenDownload(self):
389 If the connection is closed before the number of bytes indicated by
390 I{Content-Length} have been received, the L{Deferred} returned by
391 L{getPage} fails with L{PartialDownloadError}.
393 d = client.getPage(self.getURL("broken"))
394 d = self.assertFailure(d, client.PartialDownloadError)
395 d.addCallback(lambda exc: self.assertEqual(exc.response, "abc"))
399 def test_downloadPageBrokenDownload(self):
401 If the connection is closed before the number of bytes indicated by
402 I{Content-Length} have been received, the L{Deferred} returned by
403 L{downloadPage} fails with L{PartialDownloadError}.
405 # test what happens when download gets disconnected in the middle
406 path = FilePath(self.mktemp())
407 d = client.downloadPage(self.getURL("broken"), path.path)
408 d = self.assertFailure(d, client.PartialDownloadError)
410 def checkResponse(response):
412 The HTTP status code from the server is propagated through the
413 C{PartialDownloadError}.
415 self.assertEqual(response.status, "200")
416 self.assertEqual(response.message, "OK")
418 d.addCallback(checkResponse)
420 def cbFailed(ignored):
421 self.assertEqual(path.getContent(), "abc")
422 d.addCallback(cbFailed)
426 def test_downloadPageLogsFileCloseError(self):
428 If there is an exception closing the file being written to after the
429 connection is prematurely closed, that exception is logged.
432 def write(self, bytes):
436 raise IOError(ENOSPC, "No file left on device")
438 d = client.downloadPage(self.getURL("broken"), BrokenFile())
439 d = self.assertFailure(d, client.PartialDownloadError)
440 def cbFailed(ignored):
441 self.assertEqual(len(self.flushLoggedErrors(IOError)), 1)
442 d.addCallback(cbFailed)
446 def testHostHeader(self):
447 # if we pass Host header explicitly, it should be used, otherwise
448 # it should extract from url
449 return defer.gatherResults([
450 client.getPage(self.getURL("host")).addCallback(self.assertEqual, "127.0.0.1:%s" % (self.portno,)),
451 client.getPage(self.getURL("host"), headers={"Host": "www.example.com"}).addCallback(self.assertEqual, "www.example.com")])
454 def test_getPage(self):
456 L{client.getPage} returns a L{Deferred} which is called back with
457 the body of the response if the default method B{GET} is used.
459 d = client.getPage(self.getURL("file"))
460 d.addCallback(self.assertEqual, "0123456789")
464 def test_getPageHEAD(self):
466 L{client.getPage} returns a L{Deferred} which is called back with
467 the empty string if the method is I{HEAD} and there is a successful
470 d = client.getPage(self.getURL("file"), method="HEAD")
471 d.addCallback(self.assertEqual, "")
475 def test_getPageNotQuiteHEAD(self):
477 If the request method is a different casing of I{HEAD} (ie, not all
478 capitalized) then it is not a I{HEAD} request and the response body
481 d = client.getPage(self.getURL("miscased-head"), method='Head')
482 d.addCallback(self.assertEqual, "miscased-head content")
486 def test_timeoutNotTriggering(self):
488 When a non-zero timeout is passed to L{getPage} and the page is
489 retrieved before the timeout period elapses, the L{Deferred} is
490 called back with the contents of the page.
492 d = client.getPage(self.getURL("host"), timeout=100)
493 d.addCallback(self.assertEqual, "127.0.0.1:%s" % (self.portno,))
497 def test_timeoutTriggering(self):
499 When a non-zero timeout is passed to L{getPage} and that many
500 seconds elapse before the server responds to the request. the
501 L{Deferred} is errbacked with a L{error.TimeoutError}.
503 # This will probably leave some connections around.
504 self.cleanupServerConnections = 1
505 return self.assertFailure(
506 client.getPage(self.getURL("wait"), timeout=0.000001),
510 def testDownloadPage(self):
512 downloadData = [("file", self.mktemp(), "0123456789"),
513 ("nolength", self.mktemp(), "nolength")]
515 for (url, name, data) in downloadData:
516 d = client.downloadPage(self.getURL(url), name)
517 d.addCallback(self._cbDownloadPageTest, data, name)
519 return defer.gatherResults(downloads)
521 def _cbDownloadPageTest(self, ignored, data, name):
522 bytes = file(name, "rb").read()
523 self.assertEqual(bytes, data)
525 def testDownloadPageError1(self):
527 def write(self, data):
528 raise IOError, "badness happened during write"
532 return self.assertFailure(
533 client.downloadPage(self.getURL("file"), ef),
536 def testDownloadPageError2(self):
538 def write(self, data):
541 raise IOError, "badness happened during close"
543 return self.assertFailure(
544 client.downloadPage(self.getURL("file"), ef),
547 def testDownloadPageError3(self):
548 # make sure failures in open() are caught too. This is tricky.
549 # Might only work on posix.
550 tmpfile = open("unwritable", "wb")
552 os.chmod("unwritable", 0) # make it unwritable (to us)
553 d = self.assertFailure(
554 client.downloadPage(self.getURL("file"), "unwritable"),
556 d.addBoth(self._cleanupDownloadPageError3)
559 def _cleanupDownloadPageError3(self, ignored):
560 os.chmod("unwritable", 0700)
561 os.unlink("unwritable")
564 def _downloadTest(self, method):
566 for (url, code) in [("nosuchfile", "404"), ("error", "401"),
567 ("error?showlength=1", "401")]:
569 d = self.assertFailure(d, error.Error)
570 d.addCallback(lambda exc, code=code: self.assertEqual(exc.args[0], code))
572 return defer.DeferredList(dl, fireOnOneErrback=True)
574 def testServerError(self):
575 return self._downloadTest(lambda url: client.getPage(self.getURL(url)))
577 def testDownloadServerError(self):
578 return self._downloadTest(lambda url: client.downloadPage(self.getURL(url), url.split('?')[0]))
580 def testFactoryInfo(self):
581 url = self.getURL('file')
582 scheme, host, port, path = client._parse(url)
583 factory = client.HTTPClientFactory(url)
584 reactor.connectTCP(host, port, factory)
585 return factory.deferred.addCallback(self._cbFactoryInfo, factory)
587 def _cbFactoryInfo(self, ignoredResult, factory):
588 self.assertEqual(factory.status, '200')
589 self.assert_(factory.version.startswith('HTTP/'))
590 self.assertEqual(factory.message, 'OK')
591 self.assertEqual(factory.response_headers['content-length'][0], '10')
594 def test_followRedirect(self):
596 By default, L{client.getPage} follows redirects and returns the content
597 of the target resource.
599 d = client.getPage(self.getURL("redirect"))
600 d.addCallback(self.assertEqual, "0123456789")
604 def test_noFollowRedirect(self):
606 If C{followRedirect} is passed a false value, L{client.getPage} does not
607 follow redirects and returns a L{Deferred} which fails with
608 L{error.PageRedirect} when it encounters one.
610 d = self.assertFailure(
611 client.getPage(self.getURL("redirect"), followRedirect=False),
613 d.addCallback(self._cbCheckLocation)
617 def _cbCheckLocation(self, exc):
618 self.assertEqual(exc.location, "/file")
621 def test_infiniteRedirection(self):
623 When more than C{redirectLimit} HTTP redirects are encountered, the
624 page request fails with L{InfiniteRedirection}.
626 def checkRedirectCount(*a):
627 self.assertEqual(f._redirectCount, 13)
628 self.assertEqual(self.infiniteRedirectResource.count, 13)
630 f = client._makeGetterFactory(
631 self.getURL('infiniteRedirect'),
632 client.HTTPClientFactory,
634 d = self.assertFailure(f.deferred, error.InfiniteRedirection)
635 d.addCallback(checkRedirectCount)
639 def test_isolatedFollowRedirect(self):
641 C{client.HTTPPagerGetter} instances each obey the C{followRedirect}
642 value passed to the L{client.getPage} call which created them.
644 d1 = client.getPage(self.getURL('redirect'), followRedirect=True)
645 d2 = client.getPage(self.getURL('redirect'), followRedirect=False)
647 d = self.assertFailure(d2, error.PageRedirect
648 ).addCallback(lambda dummy: d1)
652 def test_afterFoundGet(self):
654 Enabling unsafe redirection behaviour overwrites the method of
655 redirected C{POST} requests with C{GET}.
657 url = self.getURL('extendedRedirect?code=302')
658 f = client.HTTPClientFactory(url, followRedirect=True, method="POST")
661 "By default, afterFoundGet must be disabled")
665 self.extendedRedirect.lastMethod,
667 "With afterFoundGet, the HTTP method must change to GET")
670 url, followRedirect=True, afterFoundGet=True, method="POST")
671 d.addCallback(gotPage)
675 def test_downloadAfterFoundGet(self):
677 Passing C{True} for C{afterFoundGet} to L{client.downloadPage} invokes
678 the same kind of redirect handling as passing that argument to
679 L{client.getPage} invokes.
681 url = self.getURL('extendedRedirect?code=302')
685 self.extendedRedirect.lastMethod,
687 "With afterFoundGet, the HTTP method must change to GET")
689 d = client.downloadPage(url, "downloadTemp",
690 followRedirect=True, afterFoundGet=True, method="POST")
691 d.addCallback(gotPage)
695 def test_afterFoundGetMakesOneRequest(self):
697 When C{afterFoundGet} is C{True}, L{client.getPage} only issues one
698 request to the server when following the redirect. This is a regression
701 def checkRedirectCount(*a):
702 self.assertEqual(self.afterFoundGetCounter.count, 1)
704 url = self.getURL('afterFoundGetRedirect')
706 url, followRedirect=True, afterFoundGet=True, method="POST")
707 d.addCallback(checkRedirectCount)
711 def testPartial(self):
717 partialDownload = [(True, "abcd456789"),
718 (True, "abcd456789"),
719 (False, "0123456789")]
721 d = defer.succeed(None)
722 for (partial, expectedData) in partialDownload:
723 d.addCallback(self._cbRunPartial, name, partial)
724 d.addCallback(self._cbPartialTest, expectedData, name)
728 testPartial.skip = "Cannot test until webserver can serve partial data properly"
730 def _cbRunPartial(self, ignored, name, partial):
731 return client.downloadPage(self.getURL("file"), name, supportPartial=partial)
733 def _cbPartialTest(self, ignored, expectedData, filename):
734 bytes = file(filename, "rb").read()
735 self.assertEqual(bytes, expectedData)
738 def test_downloadTimeout(self):
740 If the timeout indicated by the C{timeout} parameter to
741 L{client.HTTPDownloader.__init__} elapses without the complete response
742 being received, the L{defer.Deferred} returned by
743 L{client.downloadPage} fires with a L{Failure} wrapping a
744 L{defer.TimeoutError}.
746 self.cleanupServerConnections = 2
747 # Verify the behavior if no bytes are ever written.
748 first = client.downloadPage(
750 self.mktemp(), timeout=0.01)
752 # Verify the behavior if some bytes are written but then the request
754 second = client.downloadPage(
755 self.getURL("write-then-wait"),
756 self.mktemp(), timeout=0.01)
758 return defer.gatherResults([
759 self.assertFailure(first, defer.TimeoutError),
760 self.assertFailure(second, defer.TimeoutError)])
763 def test_downloadHeaders(self):
765 After L{client.HTTPDownloader.deferred} fires, the
766 L{client.HTTPDownloader} instance's C{status} and C{response_headers}
767 attributes are populated with the values from the response.
769 def checkHeaders(factory):
770 self.assertEqual(factory.status, '200')
771 self.assertEqual(factory.response_headers['content-type'][0], 'text/html')
772 self.assertEqual(factory.response_headers['content-length'][0], '10')
773 os.unlink(factory.fileName)
774 factory = client._makeGetterFactory(
776 client.HTTPDownloader,
777 fileOrName=self.mktemp())
778 return factory.deferred.addCallback(lambda _: checkHeaders(factory))
781 def test_downloadCookies(self):
783 The C{cookies} dict passed to the L{client.HTTPDownloader}
784 initializer is used to populate the I{Cookie} header included in the
785 request sent to the server.
787 output = self.mktemp()
788 factory = client._makeGetterFactory(
789 self.getURL('cookiemirror'),
790 client.HTTPDownloader,
792 cookies={'foo': 'bar'})
793 def cbFinished(ignored):
795 FilePath(output).getContent(),
797 factory.deferred.addCallback(cbFinished)
798 return factory.deferred
801 def test_downloadRedirectLimit(self):
803 When more than C{redirectLimit} HTTP redirects are encountered, the
804 page request fails with L{InfiniteRedirection}.
806 def checkRedirectCount(*a):
807 self.assertEqual(f._redirectCount, 7)
808 self.assertEqual(self.infiniteRedirectResource.count, 7)
810 f = client._makeGetterFactory(
811 self.getURL('infiniteRedirect'),
812 client.HTTPDownloader,
813 fileOrName=self.mktemp(),
815 d = self.assertFailure(f.deferred, error.InfiniteRedirection)
816 d.addCallback(checkRedirectCount)
821 class WebClientSSLTestCase(WebClientTestCase):
822 def _listen(self, site):
823 from twisted import test
824 return reactor.listenSSL(0, site,
825 contextFactory=ssl.DefaultOpenSSLContextFactory(
826 FilePath(test.__file__).sibling('server.pem').path,
827 FilePath(test.__file__).sibling('server.pem').path,
829 interface="127.0.0.1")
831 def getURL(self, path):
832 return "https://127.0.0.1:%d/%s" % (self.portno, path)
834 def testFactoryInfo(self):
835 url = self.getURL('file')
836 scheme, host, port, path = client._parse(url)
837 factory = client.HTTPClientFactory(url)
838 reactor.connectSSL(host, port, factory, ssl.ClientContextFactory())
839 # The base class defines _cbFactoryInfo correctly for this
840 return factory.deferred.addCallback(self._cbFactoryInfo, factory)
844 class WebClientRedirectBetweenSSLandPlainText(unittest.TestCase):
845 def getHTTPS(self, path):
846 return "https://127.0.0.1:%d/%s" % (self.tlsPortno, path)
848 def getHTTP(self, path):
849 return "http://127.0.0.1:%d/%s" % (self.plainPortno, path)
852 plainRoot = static.Data('not me', 'text/plain')
853 tlsRoot = static.Data('me neither', 'text/plain')
855 plainSite = server.Site(plainRoot, timeout=None)
856 tlsSite = server.Site(tlsRoot, timeout=None)
858 from twisted import test
859 self.tlsPort = reactor.listenSSL(0, tlsSite,
860 contextFactory=ssl.DefaultOpenSSLContextFactory(
861 FilePath(test.__file__).sibling('server.pem').path,
862 FilePath(test.__file__).sibling('server.pem').path,
864 interface="127.0.0.1")
865 self.plainPort = reactor.listenTCP(0, plainSite, interface="127.0.0.1")
867 self.plainPortno = self.plainPort.getHost().port
868 self.tlsPortno = self.tlsPort.getHost().port
870 plainRoot.putChild('one', util.Redirect(self.getHTTPS('two')))
871 tlsRoot.putChild('two', util.Redirect(self.getHTTP('three')))
872 plainRoot.putChild('three', util.Redirect(self.getHTTPS('four')))
873 tlsRoot.putChild('four', static.Data('FOUND IT!', 'text/plain'))
876 ds = map(defer.maybeDeferred,
877 [self.plainPort.stopListening, self.tlsPort.stopListening])
878 return defer.gatherResults(ds)
880 def testHoppingAround(self):
881 return client.getPage(self.getHTTP("one")
882 ).addCallback(self.assertEqual, "FOUND IT!"
886 disconnecting = False
889 def write(self, stuff):
890 self.data.append(stuff)
892 class CookieTestCase(unittest.TestCase):
893 def _listen(self, site):
894 return reactor.listenTCP(0, site, interface="127.0.0.1")
897 root = static.Data('El toro!', 'text/plain')
898 root.putChild("cookiemirror", CookieMirrorResource())
899 root.putChild("rawcookiemirror", RawCookieMirrorResource())
900 site = server.Site(root, timeout=None)
901 self.port = self._listen(site)
902 self.portno = self.port.getHost().port
905 return self.port.stopListening()
907 def getHTTP(self, path):
908 return "http://127.0.0.1:%d/%s" % (self.portno, path)
910 def testNoCookies(self):
911 return client.getPage(self.getHTTP("cookiemirror")
912 ).addCallback(self.assertEqual, "[]"
915 def testSomeCookies(self):
916 cookies = {'foo': 'bar', 'baz': 'quux'}
917 return client.getPage(self.getHTTP("cookiemirror"), cookies=cookies
918 ).addCallback(self.assertEqual, "[('baz', 'quux'), ('foo', 'bar')]"
921 def testRawNoCookies(self):
922 return client.getPage(self.getHTTP("rawcookiemirror")
923 ).addCallback(self.assertEqual, "None"
926 def testRawSomeCookies(self):
927 cookies = {'foo': 'bar', 'baz': 'quux'}
928 return client.getPage(self.getHTTP("rawcookiemirror"), cookies=cookies
929 ).addCallback(self.assertEqual, "'foo=bar; baz=quux'"
932 def testCookieHeaderParsing(self):
933 factory = client.HTTPClientFactory('http://foo.example.com/')
934 proto = factory.buildProtocol('127.42.42.42')
935 proto.transport = FakeTransport()
936 proto.connectionMade()
941 'Set-Cookie: CUSTOMER=WILE_E_COYOTE; path=/; expires=Wednesday, 09-Nov-99 23:12:40 GMT',
942 'Set-Cookie: PART_NUMBER=ROCKET_LAUNCHER_0001; path=/',
943 'Set-Cookie: SHIPPING=FEDEX; path=/foo',
948 proto.dataReceived(line + '\r\n')
949 self.assertEqual(proto.transport.data,
950 ['GET / HTTP/1.0\r\n',
951 'Host: foo.example.com\r\n',
952 'User-Agent: Twisted PageGetter\r\n',
954 self.assertEqual(factory.cookies,
956 'CUSTOMER': 'WILE_E_COYOTE',
957 'PART_NUMBER': 'ROCKET_LAUNCHER_0001',
963 class TestHostHeader(unittest.TestCase):
965 Test that L{HTTPClientFactory} includes the port in the host header
969 def _getHost(self, bytes):
971 Retrieve the value of the I{Host} header from the serialized
972 request given by C{bytes}.
974 for line in bytes.splitlines():
976 name, value = line.split(':', 1)
977 if name.strip().lower() == 'host':
983 def test_HTTPDefaultPort(self):
985 No port should be included in the host header when connecting to the
988 factory = client.HTTPClientFactory('http://foo.example.com/')
989 proto = factory.buildProtocol('127.42.42.42')
990 proto.makeConnection(StringTransport())
991 self.assertEqual(self._getHost(proto.transport.value()),
995 def test_HTTPPort80(self):
997 No port should be included in the host header when connecting to the
998 default HTTP port even if it is in the URL.
1000 factory = client.HTTPClientFactory('http://foo.example.com:80/')
1001 proto = factory.buildProtocol('127.42.42.42')
1002 proto.makeConnection(StringTransport())
1003 self.assertEqual(self._getHost(proto.transport.value()),
1007 def test_HTTPNotPort80(self):
1009 The port should be included in the host header when connecting to the
1010 a non default HTTP port.
1012 factory = client.HTTPClientFactory('http://foo.example.com:8080/')
1013 proto = factory.buildProtocol('127.42.42.42')
1014 proto.makeConnection(StringTransport())
1015 self.assertEqual(self._getHost(proto.transport.value()),
1016 'foo.example.com:8080')
1019 def test_HTTPSDefaultPort(self):
1021 No port should be included in the host header when connecting to the
1024 factory = client.HTTPClientFactory('https://foo.example.com/')
1025 proto = factory.buildProtocol('127.42.42.42')
1026 proto.makeConnection(StringTransport())
1027 self.assertEqual(self._getHost(proto.transport.value()),
1031 def test_HTTPSPort443(self):
1033 No port should be included in the host header when connecting to the
1034 default HTTPS port even if it is in the URL.
1036 factory = client.HTTPClientFactory('https://foo.example.com:443/')
1037 proto = factory.buildProtocol('127.42.42.42')
1038 proto.makeConnection(StringTransport())
1039 self.assertEqual(self._getHost(proto.transport.value()),
1043 def test_HTTPSNotPort443(self):
1045 The port should be included in the host header when connecting to the
1046 a non default HTTPS port.
1048 factory = client.HTTPClientFactory('http://foo.example.com:8080/')
1049 proto = factory.buildProtocol('127.42.42.42')
1050 proto.makeConnection(StringTransport())
1051 self.assertEqual(self._getHost(proto.transport.value()),
1052 'foo.example.com:8080')
1056 class StubHTTPProtocol(Protocol):
1058 A protocol like L{HTTP11ClientProtocol} but which does not actually know
1059 HTTP/1.1 and only collects requests in a list.
1061 @ivar requests: A C{list} of two-tuples. Each time a request is made, a
1062 tuple consisting of the request and the L{Deferred} returned from the
1063 request method is appended to this list.
1067 self.state = 'QUIESCENT'
1070 def request(self, request):
1072 Capture the given request for later inspection.
1074 @return: A L{Deferred} which this code will never fire.
1077 self.requests.append((request, result))
1082 class FileConsumer(object):
1083 def __init__(self, outputFile):
1084 self.outputFile = outputFile
1087 def write(self, bytes):
1088 self.outputFile.write(bytes)
1092 class FileBodyProducerTests(unittest.TestCase):
1094 Tests for the L{FileBodyProducer} which reads bytes from a file and writes
1095 them to an L{IConsumer}.
1097 _NO_RESULT = object()
1099 def _resultNow(self, deferred):
1101 Return the current result of C{deferred} if it is not a failure. If it
1102 has no result, return C{self._NO_RESULT}. If it is a failure, raise an
1107 deferred.addCallbacks(result.append, failure.append)
1108 if len(result) == 1:
1110 elif len(failure) == 1:
1112 "Deferred had failure instead of success: %r" % (failure[0],))
1113 return self._NO_RESULT
1116 def _failureNow(self, deferred):
1118 Return the current result of C{deferred} if it is a failure. If it has
1119 no result, return C{self._NO_RESULT}. If it is not a failure, raise an
1124 deferred.addCallbacks(result.append, failure.append)
1125 if len(result) == 1:
1127 "Deferred had success instead of failure: %r" % (result[0],))
1128 elif len(failure) == 1:
1130 return self._NO_RESULT
1133 def _termination(self):
1135 This method can be used as the C{terminationPredicateFactory} for a
1136 L{Cooperator}. It returns a predicate which immediately returns
1137 C{False}, indicating that no more work should be done this iteration.
1138 This has the result of only allowing one iteration of a cooperative
1139 task to be run per L{Cooperator} iteration.
1146 Create a L{Cooperator} hooked up to an easily controlled, deterministic
1147 scheduler to use with L{FileBodyProducer}.
1149 self._scheduled = []
1150 self.cooperator = task.Cooperator(
1151 self._termination, self._scheduled.append)
1154 def test_interface(self):
1156 L{FileBodyProducer} instances provide L{IBodyProducer}.
1158 self.assertTrue(verifyObject(
1159 IBodyProducer, FileBodyProducer(StringIO(""))))
1162 def test_unknownLength(self):
1164 If the L{FileBodyProducer} is constructed with a file-like object
1165 without either a C{seek} or C{tell} method, its C{length} attribute is
1166 set to C{UNKNOWN_LENGTH}.
1168 class HasSeek(object):
1169 def seek(self, offset, whence):
1172 class HasTell(object):
1176 producer = FileBodyProducer(HasSeek())
1177 self.assertEqual(UNKNOWN_LENGTH, producer.length)
1178 producer = FileBodyProducer(HasTell())
1179 self.assertEqual(UNKNOWN_LENGTH, producer.length)
1182 def test_knownLength(self):
1184 If the L{FileBodyProducer} is constructed with a file-like object with
1185 both C{seek} and C{tell} methods, its C{length} attribute is set to the
1186 size of the file as determined by those methods.
1188 inputBytes = "here are some bytes"
1189 inputFile = StringIO(inputBytes)
1191 producer = FileBodyProducer(inputFile)
1192 self.assertEqual(len(inputBytes) - 5, producer.length)
1193 self.assertEqual(inputFile.tell(), 5)
1196 def test_defaultCooperator(self):
1198 If no L{Cooperator} instance is passed to L{FileBodyProducer}, the
1199 global cooperator is used.
1201 producer = FileBodyProducer(StringIO(""))
1202 self.assertEqual(task.cooperate, producer._cooperate)
1205 def test_startProducing(self):
1207 L{FileBodyProducer.startProducing} starts writing bytes from the input
1208 file to the given L{IConsumer} and returns a L{Deferred} which fires
1209 when they have all been written.
1211 expectedResult = "hello, world"
1214 consumer = FileConsumer(output)
1215 producer = FileBodyProducer(
1216 StringIO(expectedResult), self.cooperator, readSize)
1217 complete = producer.startProducing(consumer)
1218 for i in range(len(expectedResult) / readSize + 1):
1219 self._scheduled.pop(0)()
1220 self.assertEqual([], self._scheduled)
1221 self.assertEqual(expectedResult, output.getvalue())
1222 self.assertEqual(None, self._resultNow(complete))
1225 def test_inputClosedAtEOF(self):
1227 When L{FileBodyProducer} reaches end-of-file on the input file given to
1228 it, the input file is closed.
1231 inputBytes = "some friendly bytes"
1232 inputFile = StringIO(inputBytes)
1233 producer = FileBodyProducer(inputFile, self.cooperator, readSize)
1234 consumer = FileConsumer(StringIO())
1235 producer.startProducing(consumer)
1236 for i in range(len(inputBytes) / readSize + 2):
1237 self._scheduled.pop(0)()
1238 self.assertTrue(inputFile.closed)
1241 def test_failedReadWhileProducing(self):
1243 If a read from the input file fails while producing bytes to the
1244 consumer, the L{Deferred} returned by
1245 L{FileBodyProducer.startProducing} fires with a L{Failure} wrapping
1248 class BrokenFile(object):
1249 def read(self, count):
1250 raise IOError("Simulated bad thing")
1251 producer = FileBodyProducer(BrokenFile(), self.cooperator)
1252 complete = producer.startProducing(FileConsumer(StringIO()))
1253 self._scheduled.pop(0)()
1254 self._failureNow(complete).trap(IOError)
1257 def test_stopProducing(self):
1259 L{FileBodyProducer.stopProducing} stops the underlying L{IPullProducer}
1260 and the cooperative task responsible for calling C{resumeProducing} and
1261 closes the input file but does not cause the L{Deferred} returned by
1262 C{startProducing} to fire.
1264 expectedResult = "hello, world"
1267 consumer = FileConsumer(output)
1268 inputFile = StringIO(expectedResult)
1269 producer = FileBodyProducer(
1270 inputFile, self.cooperator, readSize)
1271 complete = producer.startProducing(consumer)
1272 producer.stopProducing()
1273 self.assertTrue(inputFile.closed)
1274 self._scheduled.pop(0)()
1275 self.assertEqual("", output.getvalue())
1276 self.assertIdentical(self._NO_RESULT, self._resultNow(complete))
1279 def test_pauseProducing(self):
1281 L{FileBodyProducer.pauseProducing} temporarily suspends writing bytes
1282 from the input file to the given L{IConsumer}.
1284 expectedResult = "hello, world"
1287 consumer = FileConsumer(output)
1288 producer = FileBodyProducer(
1289 StringIO(expectedResult), self.cooperator, readSize)
1290 complete = producer.startProducing(consumer)
1291 self._scheduled.pop(0)()
1292 self.assertEqual(output.getvalue(), expectedResult[:5])
1293 producer.pauseProducing()
1295 # Sort of depends on an implementation detail of Cooperator: even
1296 # though the only task is paused, there's still a scheduled call. If
1297 # this were to go away because Cooperator became smart enough to cancel
1298 # this call in this case, that would be fine.
1299 self._scheduled.pop(0)()
1301 # Since the producer is paused, no new data should be here.
1302 self.assertEqual(output.getvalue(), expectedResult[:5])
1303 self.assertEqual([], self._scheduled)
1304 self.assertIdentical(self._NO_RESULT, self._resultNow(complete))
1307 def test_resumeProducing(self):
1309 L{FileBodyProducer.resumeProducing} re-commences writing bytes from the
1310 input file to the given L{IConsumer} after it was previously paused
1311 with L{FileBodyProducer.pauseProducing}.
1313 expectedResult = "hello, world"
1316 consumer = FileConsumer(output)
1317 producer = FileBodyProducer(
1318 StringIO(expectedResult), self.cooperator, readSize)
1319 producer.startProducing(consumer)
1320 self._scheduled.pop(0)()
1321 self.assertEqual(expectedResult[:readSize], output.getvalue())
1322 producer.pauseProducing()
1323 producer.resumeProducing()
1324 self._scheduled.pop(0)()
1325 self.assertEqual(expectedResult[:readSize * 2], output.getvalue())
1329 class FakeReactorAndConnectMixin:
1331 A test mixin providing a testable C{Reactor} class and a dummy C{connect}
1332 method which allows instances to pretend to be endpoints.
1335 class Reactor(MemoryReactor, Clock):
1337 MemoryReactor.__init__(self)
1338 Clock.__init__(self)
1341 class StubEndpoint(object):
1343 Endpoint that wraps existing endpoint, substitutes StubHTTPProtocol, and
1344 resulting protocol instances are attached to the given test case.
1347 def __init__(self, endpoint, testCase):
1348 self.endpoint = endpoint
1349 self.testCase = testCase
1350 self.factory = _HTTP11ClientFactory(lambda p: None)
1351 self.protocol = StubHTTPProtocol()
1352 self.factory.buildProtocol = lambda addr: self.protocol
1354 def connect(self, ignoredFactory):
1355 self.testCase.protocol = self.protocol
1356 self.endpoint.connect(self.factory)
1357 return succeed(self.protocol)
1360 def buildAgentForWrapperTest(self, reactor):
1362 Return an Agent suitable for use in tests that wrap the Agent and want
1363 both a fake reactor and StubHTTPProtocol.
1365 agent = client.Agent(reactor)
1366 _oldGetEndpoint = agent._getEndpoint
1367 agent._getEndpoint = lambda *args: (
1368 self.StubEndpoint(_oldGetEndpoint(*args), self))
1372 def connect(self, factory):
1374 Fake implementation of an endpoint which synchronously
1375 succeeds with an instance of L{StubHTTPProtocol} for ease of
1378 protocol = StubHTTPProtocol()
1379 protocol.makeConnection(None)
1380 self.protocol = protocol
1381 return succeed(protocol)
1385 class DummyEndpoint(object):
1387 An endpoint that uses a fake transport.
1390 def connect(self, factory):
1391 protocol = factory.buildProtocol(None)
1392 protocol.makeConnection(StringTransport())
1393 return succeed(protocol)
1397 class BadEndpoint(object):
1399 An endpoint that shouldn't be called.
1402 def connect(self, factory):
1403 raise RuntimeError("This endpoint should not have been used.")
1406 class DummyFactory(Factory):
1408 Create C{StubHTTPProtocol} instances.
1410 def __init__(self, quiescentCallback):
1413 protocol = StubHTTPProtocol
1417 class HTTPConnectionPoolTests(unittest.TestCase, FakeReactorAndConnectMixin):
1419 Tests for the L{HTTPConnectionPool} class.
1423 self.fakeReactor = self.Reactor()
1424 self.pool = HTTPConnectionPool(self.fakeReactor)
1425 self.pool._factory = DummyFactory
1426 # The retry code path is tested in HTTPConnectionPoolRetryTests:
1427 self.pool.retryAutomatically = False
1430 def test_getReturnsNewIfCacheEmpty(self):
1432 If there are no cached connections,
1433 L{HTTPConnectionPool.getConnection} returns a new connection.
1435 self.assertEqual(self.pool._connections, {})
1437 def gotConnection(conn):
1438 self.assertIsInstance(conn, StubHTTPProtocol)
1439 # The new connection is not stored in the pool:
1440 self.assertNotIn(conn, self.pool._connections.values())
1443 d = self.pool.getConnection(unknownKey, DummyEndpoint())
1444 return d.addCallback(gotConnection)
1447 def test_putStartsTimeout(self):
1449 If a connection is put back to the pool, a 240-sec timeout is started.
1451 When the timeout hits, the connection is closed and removed from the
1454 # We start out with one cached connection:
1455 protocol = StubHTTPProtocol()
1456 protocol.makeConnection(StringTransport())
1457 self.pool._putConnection(("http", "example.com", 80), protocol)
1459 # Connection is in pool, still not closed:
1460 self.assertEqual(protocol.transport.disconnecting, False)
1461 self.assertIn(protocol,
1462 self.pool._connections[("http", "example.com", 80)])
1464 # Advance 239 seconds, still not closed:
1465 self.fakeReactor.advance(239)
1466 self.assertEqual(protocol.transport.disconnecting, False)
1467 self.assertIn(protocol,
1468 self.pool._connections[("http", "example.com", 80)])
1469 self.assertIn(protocol, self.pool._timeouts)
1471 # Advance past 240 seconds, connection will be closed:
1472 self.fakeReactor.advance(1.1)
1473 self.assertEqual(protocol.transport.disconnecting, True)
1474 self.assertNotIn(protocol,
1475 self.pool._connections[("http", "example.com", 80)])
1476 self.assertNotIn(protocol, self.pool._timeouts)
1479 def test_putExceedsMaxPersistent(self):
1481 If an idle connection is put back in the cache and the max number of
1482 persistent connections has been exceeded, one of the connections is
1483 closed and removed from the cache.
1487 # We start out with two cached connection, the max:
1488 origCached = [StubHTTPProtocol(), StubHTTPProtocol()]
1489 for p in origCached:
1490 p.makeConnection(StringTransport())
1491 pool._putConnection(("http", "example.com", 80), p)
1492 self.assertEqual(pool._connections[("http", "example.com", 80)],
1494 timeouts = pool._timeouts.copy()
1496 # Now we add another one:
1497 newProtocol = StubHTTPProtocol()
1498 newProtocol.makeConnection(StringTransport())
1499 pool._putConnection(("http", "example.com", 80), newProtocol)
1501 # The oldest cached connections will be removed and disconnected:
1502 newCached = pool._connections[("http", "example.com", 80)]
1503 self.assertEqual(len(newCached), 2)
1504 self.assertEqual(newCached, [origCached[1], newProtocol])
1505 self.assertEqual([p.transport.disconnecting for p in newCached],
1507 self.assertEqual(origCached[0].transport.disconnecting, True)
1508 self.assertTrue(timeouts[origCached[0]].cancelled)
1509 self.assertNotIn(origCached[0], pool._timeouts)
1512 def test_maxPersistentPerHost(self):
1514 C{maxPersistentPerHost} is enforced per C{(scheme, host, port)}:
1515 different keys have different max connections.
1517 def addProtocol(scheme, host, port):
1518 p = StubHTTPProtocol()
1519 p.makeConnection(StringTransport())
1520 self.pool._putConnection((scheme, host, port), p)
1523 persistent.append(addProtocol("http", "example.com", 80))
1524 persistent.append(addProtocol("http", "example.com", 80))
1525 addProtocol("https", "example.com", 443)
1526 addProtocol("http", "www2.example.com", 80)
1529 self.pool._connections[("http", "example.com", 80)], persistent)
1531 len(self.pool._connections[("https", "example.com", 443)]), 1)
1533 len(self.pool._connections[("http", "www2.example.com", 80)]), 1)
1536 def test_getCachedConnection(self):
1538 Getting an address which has a cached connection returns the cached
1539 connection, removes it from the cache and cancels its timeout.
1541 # We start out with one cached connection:
1542 protocol = StubHTTPProtocol()
1543 protocol.makeConnection(StringTransport())
1544 self.pool._putConnection(("http", "example.com", 80), protocol)
1546 def gotConnection(conn):
1547 # We got the cached connection:
1548 self.assertIdentical(protocol, conn)
1550 conn, self.pool._connections[("http", "example.com", 80)])
1551 # And the timeout was cancelled:
1552 self.fakeReactor.advance(241)
1553 self.assertEqual(conn.transport.disconnecting, False)
1554 self.assertNotIn(conn, self.pool._timeouts)
1556 return self.pool.getConnection(("http", "example.com", 80),
1558 ).addCallback(gotConnection)
1561 def test_newConnection(self):
1563 The pool's C{_newConnection} method constructs a new connection.
1565 # We start out with one cached connection:
1566 protocol = StubHTTPProtocol()
1567 protocol.makeConnection(StringTransport())
1569 self.pool._putConnection(key, protocol)
1571 def gotConnection(newConnection):
1572 # We got a new connection:
1573 self.assertNotIdentical(protocol, newConnection)
1574 # And the old connection is still there:
1575 self.assertIn(protocol, self.pool._connections[key])
1576 # While the new connection is not:
1577 self.assertNotIn(newConnection, self.pool._connections.values())
1579 d = self.pool._newConnection(key, DummyEndpoint())
1580 return d.addCallback(gotConnection)
1583 def test_getSkipsDisconnected(self):
1585 When getting connections out of the cache, disconnected connections
1586 are removed and not returned.
1589 key = ("http", "example.com", 80)
1591 # We start out with two cached connection, the max:
1592 origCached = [StubHTTPProtocol(), StubHTTPProtocol()]
1593 for p in origCached:
1594 p.makeConnection(StringTransport())
1595 pool._putConnection(key, p)
1596 self.assertEqual(pool._connections[key], origCached)
1598 # We close the first one:
1599 origCached[0].state = "DISCONNECTED"
1601 # Now, when we retrive connections we should get the *second* one:
1603 self.pool.getConnection(key,
1604 BadEndpoint()).addCallback(result.append)
1605 self.assertIdentical(result[0], origCached[1])
1607 # And both the disconnected and removed connections should be out of
1609 self.assertEqual(pool._connections[key], [])
1610 self.assertEqual(pool._timeouts, {})
1613 def test_putNotQuiescent(self):
1615 If a non-quiescent connection is put back in the cache, an error is
1618 protocol = StubHTTPProtocol()
1619 # By default state is QUIESCENT
1620 self.assertEqual(protocol.state, "QUIESCENT")
1622 protocol.state = "NOTQUIESCENT"
1623 self.pool._putConnection(("http", "example.com", 80), protocol)
1624 error, = self.flushLoggedErrors(RuntimeError)
1626 error.value.args[0],
1627 "BUG: Non-quiescent protocol added to connection pool.")
1628 self.assertIdentical(None, self.pool._connections.get(
1629 ("http", "example.com", 80)))
1632 def test_getUsesQuiescentCallback(self):
1634 When L{HTTPConnectionPool.getConnection} connects, it returns a
1635 C{Deferred} that fires with an instance of L{HTTP11ClientProtocol}
1636 that has the correct quiescent callback attached. When this callback
1637 is called the protocol is returned to the cache correctly, using the
1640 class StringEndpoint(object):
1641 def connect(self, factory):
1642 p = factory.buildProtocol(None)
1643 p.makeConnection(StringTransport())
1646 pool = HTTPConnectionPool(self.fakeReactor, True)
1647 pool.retryAutomatically = False
1651 key, StringEndpoint()).addCallback(
1653 protocol = result[0]
1654 self.assertIsInstance(protocol, HTTP11ClientProtocol)
1656 # Now that we have protocol instance, lets try to put it back in the
1658 protocol._state = "QUIESCENT"
1659 protocol._quiescentCallback(protocol)
1661 # If we try to retrive a connection to same destination again, we
1662 # should get the same protocol, because it should've been added back
1666 key, StringEndpoint()).addCallback(
1668 self.assertIdentical(result2[0], protocol)
1671 def test_closeCachedConnections(self):
1673 L{HTTPConnectionPool.closeCachedConnections} closes all cached
1674 connections and removes them from the cache. It returns a Deferred
1675 that fires when they have all lost their connections.
1678 def addProtocol(scheme, host, port):
1679 p = HTTP11ClientProtocol()
1680 p.makeConnection(StringTransport())
1681 self.pool._putConnection((scheme, host, port), p)
1682 persistent.append(p)
1683 addProtocol("http", "example.com", 80)
1684 addProtocol("http", "www2.example.com", 80)
1685 doneDeferred = self.pool.closeCachedConnections()
1687 # Connections have begun disconnecting:
1688 for p in persistent:
1689 self.assertEqual(p.transport.disconnecting, True)
1690 self.assertEqual(self.pool._connections, {})
1691 # All timeouts were cancelled and removed:
1692 for dc in self.fakeReactor.getDelayedCalls():
1693 self.assertEqual(dc.cancelled, True)
1694 self.assertEqual(self.pool._timeouts, {})
1696 # Returned Deferred fires when all connections have been closed:
1698 doneDeferred.addCallback(result.append)
1699 self.assertEqual(result, [])
1700 persistent[0].connectionLost(Failure(ConnectionDone()))
1701 self.assertEqual(result, [])
1702 persistent[1].connectionLost(Failure(ConnectionDone()))
1703 self.assertEqual(result, [None])
1707 class AgentTests(unittest.TestCase, FakeReactorAndConnectMixin):
1709 Tests for the new HTTP client API provided by L{Agent}.
1713 Create an L{Agent} wrapped around a fake reactor.
1715 self.reactor = self.Reactor()
1716 self.agent = client.Agent(self.reactor)
1719 def completeConnection(self):
1721 Do whitebox stuff to finish any outstanding connection attempts the
1722 agent may have initiated.
1724 This spins the fake reactor clock just enough to get L{ClientCreator},
1725 which agent is implemented in terms of, to fire its Deferreds.
1727 self.reactor.advance(0)
1730 def test_defaultPool(self):
1732 If no pool is passed in, the L{Agent} creates a non-persistent pool.
1734 agent = client.Agent(self.reactor)
1735 self.assertIsInstance(agent._pool, HTTPConnectionPool)
1736 self.assertEqual(agent._pool.persistent, False)
1737 self.assertIdentical(agent._reactor, agent._pool._reactor)
1740 def test_persistent(self):
1742 If C{persistent} is set to C{True} on the L{HTTPConnectionPool} (the
1743 default), C{Request}s are created with their C{persistent} flag set to
1746 pool = HTTPConnectionPool(self.reactor)
1747 agent = client.Agent(self.reactor, pool=pool)
1748 agent._getEndpoint = lambda *args: self
1749 agent.request("GET", "http://127.0.0.1")
1750 self.assertEqual(self.protocol.requests[0][0].persistent, True)
1753 def test_nonPersistent(self):
1755 If C{persistent} is set to C{False} when creating the
1756 L{HTTPConnectionPool}, C{Request}s are created with their
1757 C{persistent} flag set to C{False}.
1759 Elsewhere in the tests for the underlying HTTP code we ensure that
1760 this will result in the disconnection of the HTTP protocol once the
1761 request is done, so that the connection will not be returned to the
1764 pool = HTTPConnectionPool(self.reactor, persistent=False)
1765 agent = client.Agent(self.reactor, pool=pool)
1766 agent._getEndpoint = lambda *args: self
1767 agent.request("GET", "http://127.0.0.1")
1768 self.assertEqual(self.protocol.requests[0][0].persistent, False)
1771 def test_connectUsesConnectionPool(self):
1773 When a connection is made by the Agent, it uses its pool's
1774 C{getConnection} method to do so, with the endpoint returned by
1775 C{self._getEndpoint}. The key used is C{(scheme, host, port)}.
1777 endpoint = DummyEndpoint()
1778 class MyAgent(client.Agent):
1779 def _getEndpoint(this, scheme, host, port):
1780 self.assertEqual((scheme, host, port),
1781 ("http", "foo", 80))
1784 class DummyPool(object):
1787 def getConnection(this, key, ep):
1788 this.connected = True
1789 self.assertEqual(ep, endpoint)
1790 # This is the key the default Agent uses, others will have
1792 self.assertEqual(key, ("http", "foo", 80))
1793 return defer.succeed(StubHTTPProtocol())
1796 agent = MyAgent(self.reactor, pool=pool)
1797 self.assertIdentical(pool, agent._pool)
1799 headers = http_headers.Headers()
1800 headers.addRawHeader("host", "foo")
1801 bodyProducer = object()
1802 agent.request('GET', 'http://foo/',
1803 bodyProducer=bodyProducer, headers=headers)
1804 self.assertEqual(agent._pool.connected, True)
1807 def test_unsupportedScheme(self):
1809 L{Agent.request} returns a L{Deferred} which fails with
1810 L{SchemeNotSupported} if the scheme of the URI passed to it is not
1813 return self.assertFailure(
1814 self.agent.request('GET', 'mailto:alice@example.com'),
1818 def test_connectionFailed(self):
1820 The L{Deferred} returned by L{Agent.request} fires with a L{Failure} if
1821 the TCP connection attempt fails.
1823 result = self.agent.request('GET', 'http://foo/')
1824 # Cause the connection to be refused
1825 host, port, factory = self.reactor.tcpClients.pop()[:3]
1826 factory.clientConnectionFailed(None, Failure(ConnectionRefusedError()))
1827 self.completeConnection()
1828 return self.assertFailure(result, ConnectionRefusedError)
1831 def test_connectHTTP(self):
1833 L{Agent._getEndpoint} return a C{TCP4ClientEndpoint} when passed a
1834 scheme of C{'http'}.
1836 expectedHost = 'example.com'
1838 endpoint = self.agent._getEndpoint('http', expectedHost, expectedPort)
1839 self.assertEqual(endpoint._host, expectedHost)
1840 self.assertEqual(endpoint._port, expectedPort)
1841 self.assertIsInstance(endpoint, TCP4ClientEndpoint)
1844 def test_connectHTTPS(self):
1846 L{Agent._getEndpoint} return a C{SSL4ClientEndpoint} when passed a
1847 scheme of C{'https'}.
1849 expectedHost = 'example.com'
1851 endpoint = self.agent._getEndpoint('https', expectedHost, expectedPort)
1852 self.assertIsInstance(endpoint, SSL4ClientEndpoint)
1853 self.assertEqual(endpoint._host, expectedHost)
1854 self.assertEqual(endpoint._port, expectedPort)
1855 self.assertIsInstance(endpoint._sslContextFactory,
1856 _WebToNormalContextFactory)
1857 # Default context factory was used:
1858 self.assertIsInstance(endpoint._sslContextFactory._webContext,
1859 WebClientContextFactory)
1861 test_connectHTTPS.skip = "OpenSSL not present"
1864 def test_connectHTTPSCustomContextFactory(self):
1866 If a context factory is passed to L{Agent.__init__} it will be used to
1867 determine the SSL parameters for HTTPS requests. When an HTTPS request
1868 is made, the hostname and port number of the request URL will be passed
1869 to the context factory's C{getContext} method. The resulting context
1870 object will be used to establish the SSL connection.
1872 expectedHost = 'example.org'
1873 expectedPort = 20443
1874 expectedContext = object()
1877 class StubWebContextFactory(object):
1878 def getContext(self, hostname, port):
1879 contextArgs.append((hostname, port))
1880 return expectedContext
1882 agent = client.Agent(self.reactor, StubWebContextFactory())
1883 endpoint = agent._getEndpoint('https', expectedHost, expectedPort)
1884 contextFactory = endpoint._sslContextFactory
1885 context = contextFactory.getContext()
1886 self.assertEqual(context, expectedContext)
1887 self.assertEqual(contextArgs, [(expectedHost, expectedPort)])
1890 def test_hostProvided(self):
1892 If C{None} is passed to L{Agent.request} for the C{headers} parameter,
1893 a L{Headers} instance is created for the request and a I{Host} header
1896 self.agent._getEndpoint = lambda *args: self
1898 'GET', 'http://example.com/foo?bar')
1900 req, res = self.protocol.requests.pop()
1901 self.assertEqual(req.headers.getRawHeaders('host'), ['example.com'])
1904 def test_hostOverride(self):
1906 If the headers passed to L{Agent.request} includes a value for the
1907 I{Host} header, that value takes precedence over the one which would
1908 otherwise be automatically provided.
1910 headers = http_headers.Headers({'foo': ['bar'], 'host': ['quux']})
1911 self.agent._getEndpoint = lambda *args: self
1913 'GET', 'http://example.com/foo?bar', headers)
1915 req, res = self.protocol.requests.pop()
1916 self.assertEqual(req.headers.getRawHeaders('host'), ['quux'])
1919 def test_headersUnmodified(self):
1921 If a I{Host} header must be added to the request, the L{Headers}
1922 instance passed to L{Agent.request} is not modified.
1924 headers = http_headers.Headers()
1925 self.agent._getEndpoint = lambda *args: self
1927 'GET', 'http://example.com/foo', headers)
1929 protocol = self.protocol
1931 # The request should have been issued.
1932 self.assertEqual(len(protocol.requests), 1)
1933 # And the headers object passed in should not have changed.
1934 self.assertEqual(headers, http_headers.Headers())
1937 def test_hostValueStandardHTTP(self):
1939 When passed a scheme of C{'http'} and a port of C{80},
1940 L{Agent._computeHostValue} returns a string giving just
1941 the host name passed to it.
1944 self.agent._computeHostValue('http', 'example.com', 80),
1948 def test_hostValueNonStandardHTTP(self):
1950 When passed a scheme of C{'http'} and a port other than C{80},
1951 L{Agent._computeHostValue} returns a string giving the
1952 host passed to it joined together with the port number by C{":"}.
1955 self.agent._computeHostValue('http', 'example.com', 54321),
1956 'example.com:54321')
1959 def test_hostValueStandardHTTPS(self):
1961 When passed a scheme of C{'https'} and a port of C{443},
1962 L{Agent._computeHostValue} returns a string giving just
1963 the host name passed to it.
1966 self.agent._computeHostValue('https', 'example.com', 443),
1970 def test_hostValueNonStandardHTTPS(self):
1972 When passed a scheme of C{'https'} and a port other than C{443},
1973 L{Agent._computeHostValue} returns a string giving the
1974 host passed to it joined together with the port number by C{":"}.
1977 self.agent._computeHostValue('https', 'example.com', 54321),
1978 'example.com:54321')
1981 def test_request(self):
1983 L{Agent.request} establishes a new connection to the host indicated by
1984 the host part of the URI passed to it and issues a request using the
1985 method, the path portion of the URI, the headers, and the body producer
1986 passed to it. It returns a L{Deferred} which fires with an
1987 L{IResponse} from the server.
1989 self.agent._getEndpoint = lambda *args: self
1991 headers = http_headers.Headers({'foo': ['bar']})
1992 # Just going to check the body for identity, so it doesn't need to be
1996 'GET', 'http://example.com:1234/foo?bar', headers, body)
1998 protocol = self.protocol
2000 # The request should be issued.
2001 self.assertEqual(len(protocol.requests), 1)
2002 req, res = protocol.requests.pop()
2003 self.assertIsInstance(req, Request)
2004 self.assertEqual(req.method, 'GET')
2005 self.assertEqual(req.uri, '/foo?bar')
2008 http_headers.Headers({'foo': ['bar'],
2009 'host': ['example.com:1234']}))
2010 self.assertIdentical(req.bodyProducer, body)
2013 def test_connectTimeout(self):
2015 L{Agent} takes a C{connectTimeout} argument which is forwarded to the
2016 following C{connectTCP} agent.
2018 agent = client.Agent(self.reactor, connectTimeout=5)
2019 agent.request('GET', 'http://foo/')
2020 timeout = self.reactor.tcpClients.pop()[3]
2021 self.assertEqual(5, timeout)
2024 def test_connectSSLTimeout(self):
2026 L{Agent} takes a C{connectTimeout} argument which is forwarded to the
2027 following C{connectSSL} call.
2029 agent = client.Agent(self.reactor, connectTimeout=5)
2030 agent.request('GET', 'https://foo/')
2031 timeout = self.reactor.sslClients.pop()[4]
2032 self.assertEqual(5, timeout)
2035 def test_bindAddress(self):
2037 L{Agent} takes a C{bindAddress} argument which is forwarded to the
2038 following C{connectTCP} call.
2040 agent = client.Agent(self.reactor, bindAddress='192.168.0.1')
2041 agent.request('GET', 'http://foo/')
2042 address = self.reactor.tcpClients.pop()[4]
2043 self.assertEqual('192.168.0.1', address)
2046 def test_bindAddressSSL(self):
2048 L{Agent} takes a C{bindAddress} argument which is forwarded to the
2049 following C{connectSSL} call.
2051 agent = client.Agent(self.reactor, bindAddress='192.168.0.1')
2052 agent.request('GET', 'https://foo/')
2053 address = self.reactor.sslClients.pop()[5]
2054 self.assertEqual('192.168.0.1', address)
2058 class HTTPConnectionPoolRetryTests(unittest.TestCase, FakeReactorAndConnectMixin):
2060 L{client.HTTPConnectionPool}, by using
2061 L{client._RetryingHTTP11ClientProtocol}, supports retrying requests done
2062 against previously cached connections.
2065 def test_onlyRetryIdempotentMethods(self):
2067 Only GET, HEAD, OPTIONS, TRACE, DELETE methods should cause a retry.
2069 pool = client.HTTPConnectionPool(None)
2070 connection = client._RetryingHTTP11ClientProtocol(None, pool)
2071 self.assertTrue(connection._shouldRetry("GET", RequestNotSent(), None))
2072 self.assertTrue(connection._shouldRetry("HEAD", RequestNotSent(), None))
2073 self.assertTrue(connection._shouldRetry(
2074 "OPTIONS", RequestNotSent(), None))
2075 self.assertTrue(connection._shouldRetry(
2076 "TRACE", RequestNotSent(), None))
2077 self.assertTrue(connection._shouldRetry(
2078 "DELETE", RequestNotSent(), None))
2079 self.assertFalse(connection._shouldRetry(
2080 "POST", RequestNotSent(), None))
2081 self.assertFalse(connection._shouldRetry(
2082 "MYMETHOD", RequestNotSent(), None))
2083 # This will be covered by a different ticket, since we need support
2084 #for resettable body producers:
2085 # self.assertTrue(connection._doRetry("PUT", RequestNotSent(), None))
2088 def test_onlyRetryIfNoResponseReceived(self):
2090 Only L{RequestNotSent}, L{RequestTransmissionFailed} and
2091 L{ResponseNeverReceived} exceptions should be a cause for retrying.
2093 pool = client.HTTPConnectionPool(None)
2094 connection = client._RetryingHTTP11ClientProtocol(None, pool)
2095 self.assertTrue(connection._shouldRetry("GET", RequestNotSent(), None))
2096 self.assertTrue(connection._shouldRetry(
2097 "GET", RequestTransmissionFailed([]), None))
2098 self.assertTrue(connection._shouldRetry(
2099 "GET", ResponseNeverReceived([]),None))
2100 self.assertFalse(connection._shouldRetry(
2101 "GET", ResponseFailed([]), None))
2102 self.assertFalse(connection._shouldRetry(
2103 "GET", ConnectionRefusedError(), None))
2106 def test_wrappedOnPersistentReturned(self):
2108 If L{client.HTTPConnectionPool.getConnection} returns a previously
2109 cached connection, it will get wrapped in a
2110 L{client._RetryingHTTP11ClientProtocol}.
2112 pool = client.HTTPConnectionPool(Clock())
2114 # Add a connection to the cache:
2115 protocol = StubHTTPProtocol()
2116 protocol.makeConnection(StringTransport())
2117 pool._putConnection(123, protocol)
2119 # Retrieve it, it should come back wrapped in a
2120 # _RetryingHTTP11ClientProtocol:
2121 d = pool.getConnection(123, DummyEndpoint())
2123 def gotConnection(connection):
2124 self.assertIsInstance(connection,
2125 client._RetryingHTTP11ClientProtocol)
2126 self.assertIdentical(connection._clientProtocol, protocol)
2127 return d.addCallback(gotConnection)
2130 def test_notWrappedOnNewReturned(self):
2132 If L{client.HTTPConnectionPool.getConnection} returns a new
2133 connection, it will be returned as is.
2135 pool = client.HTTPConnectionPool(None)
2136 d = pool.getConnection(123, DummyEndpoint())
2138 def gotConnection(connection):
2139 # Don't want to use isinstance since potentially the wrapper might
2140 # subclass it at some point:
2141 self.assertIdentical(connection.__class__, HTTP11ClientProtocol)
2142 return d.addCallback(gotConnection)
2145 def retryAttempt(self, willWeRetry):
2147 Fail a first request, possibly retrying depending on argument.
2151 protocol = StubHTTPProtocol()
2152 protocols.append(protocol)
2153 return defer.succeed(protocol)
2155 bodyProducer = object()
2156 request = client.Request("FOO", "/", client.Headers(), bodyProducer,
2159 protocol = protocols[0]
2160 retrier = client._RetryingHTTP11ClientProtocol(protocol, newProtocol)
2162 def _shouldRetry(m, e, bp):
2163 self.assertEqual(m, "FOO")
2164 self.assertIdentical(bp, bodyProducer)
2165 self.assertIsInstance(e, (RequestNotSent, ResponseNeverReceived))
2167 retrier._shouldRetry = _shouldRetry
2169 d = retrier.request(request)
2171 # So far, one request made:
2172 self.assertEqual(len(protocols), 1)
2173 self.assertEqual(len(protocols[0].requests), 1)
2175 # Fail the first request:
2176 protocol.requests[0][1].errback(RequestNotSent())
2180 def test_retryIfShouldRetryReturnsTrue(self):
2182 L{client._RetryingHTTP11ClientProtocol} retries when
2183 L{client._RetryingHTTP11ClientProtocol._shouldRetry} returns C{True}.
2185 d, protocols = self.retryAttempt(True)
2187 self.assertEqual(len(protocols), 2)
2189 protocols[1].requests[0][1].callback(response)
2190 return d.addCallback(self.assertIdentical, response)
2193 def test_dontRetryIfShouldRetryReturnsFalse(self):
2195 L{client._RetryingHTTP11ClientProtocol} does not retry when
2196 L{client._RetryingHTTP11ClientProtocol._shouldRetry} returns C{False}.
2198 d, protocols = self.retryAttempt(False)
2200 self.assertEqual(len(protocols), 1)
2201 return self.assertFailure(d, RequestNotSent)
2204 def test_onlyRetryWithoutBody(self):
2206 L{_RetryingHTTP11ClientProtocol} only retries queries that don't have
2209 This is an implementation restriction; if the restriction is fixed,
2210 this test should be removed and PUT added to list of methods that
2213 pool = client.HTTPConnectionPool(None)
2214 connection = client._RetryingHTTP11ClientProtocol(None, pool)
2215 self.assertTrue(connection._shouldRetry("GET", RequestNotSent(), None))
2216 self.assertFalse(connection._shouldRetry("GET", RequestNotSent(), object()))
2219 def test_onlyRetryOnce(self):
2221 If a L{client._RetryingHTTP11ClientProtocol} fails more than once on
2222 an idempotent query before a response is received, it will not retry.
2224 d, protocols = self.retryAttempt(True)
2225 self.assertEqual(len(protocols), 2)
2226 # Fail the second request too:
2227 protocols[1].requests[0][1].errback(ResponseNeverReceived([]))
2228 # We didn't retry again:
2229 self.assertEqual(len(protocols), 2)
2230 return self.assertFailure(d, ResponseNeverReceived)
2233 def test_dontRetryIfRetryAutomaticallyFalse(self):
2235 If L{HTTPConnectionPool.retryAutomatically} is set to C{False}, don't
2236 wrap connections with retrying logic.
2238 pool = client.HTTPConnectionPool(Clock())
2239 pool.retryAutomatically = False
2241 # Add a connection to the cache:
2242 protocol = StubHTTPProtocol()
2243 protocol.makeConnection(StringTransport())
2244 pool._putConnection(123, protocol)
2246 # Retrieve it, it should come back unwrapped:
2247 d = pool.getConnection(123, DummyEndpoint())
2249 def gotConnection(connection):
2250 self.assertIdentical(connection, protocol)
2251 return d.addCallback(gotConnection)
2254 def test_retryWithNewConnection(self):
2256 L{client.HTTPConnectionPool} creates
2257 {client._RetryingHTTP11ClientProtocol} with a new connection factory
2258 method that creates a new connection using the same key and endpoint
2259 as the wrapped connection.
2261 pool = client.HTTPConnectionPool(Clock())
2263 endpoint = DummyEndpoint()
2266 # Override the pool's _newConnection:
2267 def newConnection(k, e):
2268 newConnections.append((k, e))
2269 pool._newConnection = newConnection
2271 # Add a connection to the cache:
2272 protocol = StubHTTPProtocol()
2273 protocol.makeConnection(StringTransport())
2274 pool._putConnection(key, protocol)
2276 # Retrieve it, it should come back wrapped in a
2277 # _RetryingHTTP11ClientProtocol:
2278 d = pool.getConnection(key, endpoint)
2280 def gotConnection(connection):
2281 self.assertIsInstance(connection,
2282 client._RetryingHTTP11ClientProtocol)
2283 self.assertIdentical(connection._clientProtocol, protocol)
2284 # Verify that the _newConnection method on retrying connection
2285 # calls _newConnection on the pool:
2286 self.assertEqual(newConnections, [])
2287 connection._newConnection()
2288 self.assertEqual(len(newConnections), 1)
2289 self.assertEqual(newConnections[0][0], key)
2290 self.assertIdentical(newConnections[0][1], endpoint)
2291 return d.addCallback(gotConnection)
2296 class CookieTestsMixin(object):
2298 Mixin for unit tests dealing with cookies.
2300 def addCookies(self, cookieJar, uri, cookies):
2302 Add a cookie to a cookie jar.
2304 response = client._FakeUrllib2Response(
2309 client.Headers({'Set-Cookie': cookies}),
2311 request = client._FakeUrllib2Request(uri)
2312 cookieJar.extract_cookies(response, request)
2313 return request, response
2317 class CookieJarTests(unittest.TestCase, CookieTestsMixin):
2319 Tests for L{twisted.web.client._FakeUrllib2Response} and
2320 L{twisted.web.client._FakeUrllib2Request}'s interactions with
2321 C{cookielib.CookieJar} instances.
2323 def makeCookieJar(self):
2325 Create a C{cookielib.CookieJar} with some sample cookies.
2327 cookieJar = cookielib.CookieJar()
2328 reqres = self.addCookies(
2330 'http://example.com:1234/foo?bar',
2331 ['foo=1; cow=moo; Path=/foo; Comment=hello',
2332 'bar=2; Comment=goodbye'])
2333 return cookieJar, reqres
2336 def test_extractCookies(self):
2338 L{cookielib.CookieJar.extract_cookies} extracts cookie information from
2339 fake urllib2 response instances.
2341 jar = self.makeCookieJar()[0]
2342 cookies = dict([(c.name, c) for c in jar])
2344 cookie = cookies['foo']
2345 self.assertEqual(cookie.version, 0)
2346 self.assertEqual(cookie.name, 'foo')
2347 self.assertEqual(cookie.value, '1')
2348 self.assertEqual(cookie.path, '/foo')
2349 self.assertEqual(cookie.comment, 'hello')
2350 self.assertEqual(cookie.get_nonstandard_attr('cow'), 'moo')
2352 cookie = cookies['bar']
2353 self.assertEqual(cookie.version, 0)
2354 self.assertEqual(cookie.name, 'bar')
2355 self.assertEqual(cookie.value, '2')
2356 self.assertEqual(cookie.path, '/')
2357 self.assertEqual(cookie.comment, 'goodbye')
2358 self.assertIdentical(cookie.get_nonstandard_attr('cow'), None)
2361 def test_sendCookie(self):
2363 L{cookielib.CookieJar.add_cookie_header} adds a cookie header to a fake
2364 urllib2 request instance.
2366 jar, (request, response) = self.makeCookieJar()
2368 self.assertIdentical(
2369 request.get_header('Cookie', None),
2372 jar.add_cookie_header(request)
2374 request.get_header('Cookie', None),
2379 class CookieAgentTests(unittest.TestCase, CookieTestsMixin,
2380 FakeReactorAndConnectMixin):
2382 Tests for L{twisted.web.client.CookieAgent}.
2385 self.reactor = self.Reactor()
2388 def test_emptyCookieJarRequest(self):
2390 L{CookieAgent.request} does not insert any C{'Cookie'} header into the
2391 L{Request} object if there is no cookie in the cookie jar for the URI
2392 being requested. Cookies are extracted from the response and stored in
2395 cookieJar = cookielib.CookieJar()
2396 self.assertEqual(list(cookieJar), [])
2398 agent = self.buildAgentForWrapperTest(self.reactor)
2399 cookieAgent = client.CookieAgent(agent, cookieJar)
2400 d = cookieAgent.request(
2401 'GET', 'http://example.com:1234/foo?bar')
2403 def _checkCookie(ignored):
2404 cookies = list(cookieJar)
2405 self.assertEqual(len(cookies), 1)
2406 self.assertEqual(cookies[0].name, 'foo')
2407 self.assertEqual(cookies[0].value, '1')
2409 d.addCallback(_checkCookie)
2411 req, res = self.protocol.requests.pop()
2412 self.assertIdentical(req.headers.getRawHeaders('cookie'), None)
2414 resp = client.Response(
2418 client.Headers({'Set-Cookie': ['foo=1',]}),
2425 def test_requestWithCookie(self):
2427 L{CookieAgent.request} inserts a C{'Cookie'} header into the L{Request}
2428 object when there is a cookie matching the request URI in the cookie
2431 uri = 'http://example.com:1234/foo?bar'
2434 cookieJar = cookielib.CookieJar()
2435 self.addCookies(cookieJar, uri, [cookie])
2436 self.assertEqual(len(list(cookieJar)), 1)
2438 agent = self.buildAgentForWrapperTest(self.reactor)
2439 cookieAgent = client.CookieAgent(agent, cookieJar)
2440 cookieAgent.request('GET', uri)
2442 req, res = self.protocol.requests.pop()
2443 self.assertEqual(req.headers.getRawHeaders('cookie'), [cookie])
2446 def test_secureCookie(self):
2448 L{CookieAgent} is able to handle secure cookies, ie cookies which
2449 should only be handled over https.
2451 uri = 'https://example.com:1234/foo?bar'
2452 cookie = 'foo=1;secure'
2454 cookieJar = cookielib.CookieJar()
2455 self.addCookies(cookieJar, uri, [cookie])
2456 self.assertEqual(len(list(cookieJar)), 1)
2458 agent = self.buildAgentForWrapperTest(self.reactor)
2459 cookieAgent = client.CookieAgent(agent, cookieJar)
2460 cookieAgent.request('GET', uri)
2462 req, res = self.protocol.requests.pop()
2463 self.assertEqual(req.headers.getRawHeaders('cookie'), ['foo=1'])
2466 def test_secureCookieOnInsecureConnection(self):
2468 If a cookie is setup as secure, it won't be sent with the request if
2469 it's not over HTTPS.
2471 uri = 'http://example.com/foo?bar'
2472 cookie = 'foo=1;secure'
2474 cookieJar = cookielib.CookieJar()
2475 self.addCookies(cookieJar, uri, [cookie])
2476 self.assertEqual(len(list(cookieJar)), 1)
2478 agent = self.buildAgentForWrapperTest(self.reactor)
2479 cookieAgent = client.CookieAgent(agent, cookieJar)
2480 cookieAgent.request('GET', uri)
2482 req, res = self.protocol.requests.pop()
2483 self.assertIdentical(None, req.headers.getRawHeaders('cookie'))
2486 def test_portCookie(self):
2488 L{CookieAgent} supports cookies which enforces the port number they
2489 need to be transferred upon.
2491 uri = 'https://example.com:1234/foo?bar'
2492 cookie = 'foo=1;port=1234'
2494 cookieJar = cookielib.CookieJar()
2495 self.addCookies(cookieJar, uri, [cookie])
2496 self.assertEqual(len(list(cookieJar)), 1)
2498 agent = self.buildAgentForWrapperTest(self.reactor)
2499 cookieAgent = client.CookieAgent(agent, cookieJar)
2500 cookieAgent.request('GET', uri)
2502 req, res = self.protocol.requests.pop()
2503 self.assertEqual(req.headers.getRawHeaders('cookie'), ['foo=1'])
2506 def test_portCookieOnWrongPort(self):
2508 When creating a cookie with a port directive, it won't be added to the
2509 L{cookie.CookieJar} if the URI is on a different port.
2511 uri = 'https://example.com:4567/foo?bar'
2512 cookie = 'foo=1;port=1234'
2514 cookieJar = cookielib.CookieJar()
2515 self.addCookies(cookieJar, uri, [cookie])
2516 self.assertEqual(len(list(cookieJar)), 0)
2520 class Decoder1(proxyForInterface(IResponse)):
2522 A test decoder to be used by L{client.ContentDecoderAgent} tests.
2527 class Decoder2(Decoder1):
2529 A test decoder to be used by L{client.ContentDecoderAgent} tests.
2534 class ContentDecoderAgentTests(unittest.TestCase, FakeReactorAndConnectMixin):
2536 Tests for L{client.ContentDecoderAgent}.
2541 Create an L{Agent} wrapped around a fake reactor.
2543 self.reactor = self.Reactor()
2544 self.agent = self.buildAgentForWrapperTest(self.reactor)
2547 def test_acceptHeaders(self):
2549 L{client.ContentDecoderAgent} sets the I{Accept-Encoding} header to the
2550 names of the available decoder objects.
2552 agent = client.ContentDecoderAgent(
2553 self.agent, [('decoder1', Decoder1), ('decoder2', Decoder2)])
2555 agent.request('GET', 'http://example.com/foo')
2557 protocol = self.protocol
2559 self.assertEqual(len(protocol.requests), 1)
2560 req, res = protocol.requests.pop()
2561 self.assertEqual(req.headers.getRawHeaders('accept-encoding'),
2562 ['decoder1,decoder2'])
2565 def test_existingHeaders(self):
2567 If there are existing I{Accept-Encoding} fields,
2568 L{client.ContentDecoderAgent} creates a new field for the decoders it
2571 headers = http_headers.Headers({'foo': ['bar'],
2572 'accept-encoding': ['fizz']})
2573 agent = client.ContentDecoderAgent(
2574 self.agent, [('decoder1', Decoder1), ('decoder2', Decoder2)])
2575 agent.request('GET', 'http://example.com/foo', headers=headers)
2577 protocol = self.protocol
2579 self.assertEqual(len(protocol.requests), 1)
2580 req, res = protocol.requests.pop()
2582 list(req.headers.getAllRawHeaders()),
2583 [('Host', ['example.com']),
2585 ('Accept-Encoding', ['fizz', 'decoder1,decoder2'])])
2588 def test_plainEncodingResponse(self):
2590 If the response is not encoded despited the request I{Accept-Encoding}
2591 headers, L{client.ContentDecoderAgent} simply forwards the response.
2593 agent = client.ContentDecoderAgent(
2594 self.agent, [('decoder1', Decoder1), ('decoder2', Decoder2)])
2595 deferred = agent.request('GET', 'http://example.com/foo')
2597 req, res = self.protocol.requests.pop()
2599 response = Response(('HTTP', 1, 1), 200, 'OK', http_headers.Headers(),
2601 res.callback(response)
2603 return deferred.addCallback(self.assertIdentical, response)
2606 def test_unsupportedEncoding(self):
2608 If an encoding unknown to the L{client.ContentDecoderAgent} is found,
2609 the response is unchanged.
2611 agent = client.ContentDecoderAgent(
2612 self.agent, [('decoder1', Decoder1), ('decoder2', Decoder2)])
2613 deferred = agent.request('GET', 'http://example.com/foo')
2615 req, res = self.protocol.requests.pop()
2617 headers = http_headers.Headers({'foo': ['bar'],
2618 'content-encoding': ['fizz']})
2619 response = Response(('HTTP', 1, 1), 200, 'OK', headers, None)
2620 res.callback(response)
2622 return deferred.addCallback(self.assertIdentical, response)
2625 def test_unknownEncoding(self):
2627 When L{client.ContentDecoderAgent} encounters a decoder it doesn't know
2628 about, it stops decoding even if another encoding is known afterwards.
2630 agent = client.ContentDecoderAgent(
2631 self.agent, [('decoder1', Decoder1), ('decoder2', Decoder2)])
2632 deferred = agent.request('GET', 'http://example.com/foo')
2634 req, res = self.protocol.requests.pop()
2636 headers = http_headers.Headers({'foo': ['bar'],
2638 ['decoder1,fizz,decoder2']})
2639 response = Response(('HTTP', 1, 1), 200, 'OK', headers, None)
2640 res.callback(response)
2643 self.assertNotIdentical(response, result)
2644 self.assertIsInstance(result, Decoder2)
2645 self.assertEqual(['decoder1,fizz'],
2646 result.headers.getRawHeaders('content-encoding'))
2648 return deferred.addCallback(check)
2652 class SimpleAgentProtocol(Protocol):
2654 A L{Protocol} to be used with an L{client.Agent} to receive data.
2656 @ivar finished: L{Deferred} firing when C{connectionLost} is called.
2658 @ivar made: L{Deferred} firing when C{connectionMade} is called.
2660 @ivar received: C{list} of received data.
2664 self.made = Deferred()
2665 self.finished = Deferred()
2669 def connectionMade(self):
2670 self.made.callback(None)
2673 def connectionLost(self, reason):
2674 self.finished.callback(None)
2677 def dataReceived(self, data):
2678 self.received.append(data)
2682 class ContentDecoderAgentWithGzipTests(unittest.TestCase,
2683 FakeReactorAndConnectMixin):
2687 Create an L{Agent} wrapped around a fake reactor.
2689 self.reactor = self.Reactor()
2690 agent = self.buildAgentForWrapperTest(self.reactor)
2691 self.agent = client.ContentDecoderAgent(
2692 agent, [("gzip", client.GzipDecoder)])
2695 def test_gzipEncodingResponse(self):
2697 If the response has a C{gzip} I{Content-Encoding} header,
2698 L{GzipDecoder} wraps the response to return uncompressed data to the
2701 deferred = self.agent.request('GET', 'http://example.com/foo')
2703 req, res = self.protocol.requests.pop()
2705 headers = http_headers.Headers({'foo': ['bar'],
2706 'content-encoding': ['gzip']})
2707 transport = StringTransport()
2708 response = Response(('HTTP', 1, 1), 200, 'OK', headers, transport)
2709 response.length = 12
2710 res.callback(response)
2712 compressor = zlib.compressobj(2, zlib.DEFLATED, 16 + zlib.MAX_WBITS)
2713 data = (compressor.compress('x' * 6) + compressor.compress('y' * 4) +
2716 def checkResponse(result):
2717 self.assertNotIdentical(result, response)
2718 self.assertEqual(result.version, ('HTTP', 1, 1))
2719 self.assertEqual(result.code, 200)
2720 self.assertEqual(result.phrase, 'OK')
2721 self.assertEqual(list(result.headers.getAllRawHeaders()),
2723 self.assertEqual(result.length, UNKNOWN_LENGTH)
2724 self.assertRaises(AttributeError, getattr, result, 'unknown')
2726 response._bodyDataReceived(data[:5])
2727 response._bodyDataReceived(data[5:])
2728 response._bodyDataFinished()
2730 protocol = SimpleAgentProtocol()
2731 result.deliverBody(protocol)
2733 self.assertEqual(protocol.received, ['x' * 6 + 'y' * 4])
2734 return defer.gatherResults([protocol.made, protocol.finished])
2736 deferred.addCallback(checkResponse)
2741 def test_brokenContent(self):
2743 If the data received by the L{GzipDecoder} isn't valid gzip-compressed
2744 data, the call to C{deliverBody} fails with a C{zlib.error}.
2746 deferred = self.agent.request('GET', 'http://example.com/foo')
2748 req, res = self.protocol.requests.pop()
2750 headers = http_headers.Headers({'foo': ['bar'],
2751 'content-encoding': ['gzip']})
2752 transport = StringTransport()
2753 response = Response(('HTTP', 1, 1), 200, 'OK', headers, transport)
2754 response.length = 12
2755 res.callback(response)
2757 data = "not gzipped content"
2759 def checkResponse(result):
2760 response._bodyDataReceived(data)
2762 result.deliverBody(Protocol())
2764 deferred.addCallback(checkResponse)
2765 self.assertFailure(deferred, client.ResponseFailed)
2767 def checkFailure(error):
2768 error.reasons[0].trap(zlib.error)
2769 self.assertIsInstance(error.response, Response)
2771 return deferred.addCallback(checkFailure)
2774 def test_flushData(self):
2776 When the connection with the server is lost, the gzip protocol calls
2777 C{flush} on the zlib decompressor object to get uncompressed data which
2778 may have been buffered.
2780 class decompressobj(object):
2782 def __init__(self, wbits):
2785 def decompress(self, data):
2792 oldDecompressObj = zlib.decompressobj
2793 zlib.decompressobj = decompressobj
2794 self.addCleanup(setattr, zlib, 'decompressobj', oldDecompressObj)
2796 deferred = self.agent.request('GET', 'http://example.com/foo')
2798 req, res = self.protocol.requests.pop()
2800 headers = http_headers.Headers({'content-encoding': ['gzip']})
2801 transport = StringTransport()
2802 response = Response(('HTTP', 1, 1), 200, 'OK', headers, transport)
2803 res.callback(response)
2805 def checkResponse(result):
2806 response._bodyDataReceived('data')
2807 response._bodyDataFinished()
2809 protocol = SimpleAgentProtocol()
2810 result.deliverBody(protocol)
2812 self.assertEqual(protocol.received, ['x', 'y'])
2813 return defer.gatherResults([protocol.made, protocol.finished])
2815 deferred.addCallback(checkResponse)
2820 def test_flushError(self):
2822 If the C{flush} call in C{connectionLost} fails, the C{zlib.error}
2823 exception is caught and turned into a L{ResponseFailed}.
2825 class decompressobj(object):
2827 def __init__(self, wbits):
2830 def decompress(self, data):
2837 oldDecompressObj = zlib.decompressobj
2838 zlib.decompressobj = decompressobj
2839 self.addCleanup(setattr, zlib, 'decompressobj', oldDecompressObj)
2841 deferred = self.agent.request('GET', 'http://example.com/foo')
2843 req, res = self.protocol.requests.pop()
2845 headers = http_headers.Headers({'content-encoding': ['gzip']})
2846 transport = StringTransport()
2847 response = Response(('HTTP', 1, 1), 200, 'OK', headers, transport)
2848 res.callback(response)
2850 def checkResponse(result):
2851 response._bodyDataReceived('data')
2852 response._bodyDataFinished()
2854 protocol = SimpleAgentProtocol()
2855 result.deliverBody(protocol)
2857 self.assertEqual(protocol.received, ['x', 'y'])
2858 return defer.gatherResults([protocol.made, protocol.finished])
2860 deferred.addCallback(checkResponse)
2862 self.assertFailure(deferred, client.ResponseFailed)
2864 def checkFailure(error):
2865 error.reasons[1].trap(zlib.error)
2866 self.assertIsInstance(error.response, Response)
2868 return deferred.addCallback(checkFailure)
2872 class ProxyAgentTests(unittest.TestCase, FakeReactorAndConnectMixin):
2874 Tests for L{client.ProxyAgent}.
2878 self.reactor = self.Reactor()
2879 self.agent = client.ProxyAgent(
2880 TCP4ClientEndpoint(self.reactor, "bar", 5678), self.reactor)
2881 oldEndpoint = self.agent._proxyEndpoint
2882 self.agent._proxyEndpoint = self.StubEndpoint(oldEndpoint, self)
2885 def test_proxyRequest(self):
2887 L{client.ProxyAgent} issues an HTTP request against the proxy, with the
2888 full URI as path, when C{request} is called.
2890 headers = http_headers.Headers({'foo': ['bar']})
2891 # Just going to check the body for identity, so it doesn't need to be
2895 'GET', 'http://example.com:1234/foo?bar', headers, body)
2897 host, port, factory = self.reactor.tcpClients.pop()[:3]
2898 self.assertEqual(host, "bar")
2899 self.assertEqual(port, 5678)
2901 self.assertIsInstance(factory._wrappedFactory,
2902 client._HTTP11ClientFactory)
2904 protocol = self.protocol
2906 # The request should be issued.
2907 self.assertEqual(len(protocol.requests), 1)
2908 req, res = protocol.requests.pop()
2909 self.assertIsInstance(req, Request)
2910 self.assertEqual(req.method, 'GET')
2911 self.assertEqual(req.uri, 'http://example.com:1234/foo?bar')
2914 http_headers.Headers({'foo': ['bar'],
2915 'host': ['example.com:1234']}))
2916 self.assertIdentical(req.bodyProducer, body)
2919 def test_nonPersistent(self):
2921 C{ProxyAgent} connections are not persistent by default.
2923 self.assertEqual(self.agent._pool.persistent, False)
2926 def test_connectUsesConnectionPool(self):
2928 When a connection is made by the C{ProxyAgent}, it uses its pool's
2929 C{getConnection} method to do so, with the endpoint it was constructed
2930 with and a key of C{("http-proxy", endpoint)}.
2932 endpoint = DummyEndpoint()
2933 class DummyPool(object):
2936 def getConnection(this, key, ep):
2937 this.connected = True
2938 self.assertIdentical(ep, endpoint)
2939 # The key is *not* tied to the final destination, but only to
2940 # the address of the proxy, since that's where *we* are
2942 self.assertEqual(key, ("http-proxy", endpoint))
2943 return defer.succeed(StubHTTPProtocol())
2946 agent = client.ProxyAgent(endpoint, self.reactor, pool=pool)
2947 self.assertIdentical(pool, agent._pool)
2949 agent.request('GET', 'http://foo/')
2950 self.assertEqual(agent._pool.connected, True)
2954 class RedirectAgentTests(unittest.TestCase, FakeReactorAndConnectMixin):
2956 Tests for L{client.RedirectAgent}.
2960 self.reactor = self.Reactor()
2961 self.agent = client.RedirectAgent(
2962 self.buildAgentForWrapperTest(self.reactor))
2965 def test_noRedirect(self):
2967 L{client.RedirectAgent} behaves like L{client.Agent} if the response
2968 doesn't contain a redirect.
2970 deferred = self.agent.request('GET', 'http://example.com/foo')
2972 req, res = self.protocol.requests.pop()
2974 headers = http_headers.Headers()
2975 response = Response(('HTTP', 1, 1), 200, 'OK', headers, None)
2976 res.callback(response)
2978 self.assertEqual(0, len(self.protocol.requests))
2980 def checkResponse(result):
2981 self.assertIdentical(result, response)
2983 return deferred.addCallback(checkResponse)
2986 def _testRedirectDefault(self, code):
2988 When getting a redirect, L{RedirectAgent} follows the URL specified in
2989 the L{Location} header field and make a new request.
2991 self.agent.request('GET', 'http://example.com/foo')
2993 host, port = self.reactor.tcpClients.pop()[:2]
2994 self.assertEqual("example.com", host)
2995 self.assertEqual(80, port)
2997 req, res = self.protocol.requests.pop()
2999 headers = http_headers.Headers(
3000 {'location': ['https://example.com/bar']})
3001 response = Response(('HTTP', 1, 1), code, 'OK', headers, None)
3002 res.callback(response)
3004 req2, res2 = self.protocol.requests.pop()
3005 self.assertEqual('GET', req2.method)
3006 self.assertEqual('/bar', req2.uri)
3008 host, port = self.reactor.sslClients.pop()[:2]
3009 self.assertEqual("example.com", host)
3010 self.assertEqual(443, port)
3013 def test_redirect301(self):
3015 L{RedirectAgent} follows redirects on status code 301.
3017 self._testRedirectDefault(301)
3020 def test_redirect302(self):
3022 L{RedirectAgent} follows redirects on status code 302.
3024 self._testRedirectDefault(302)
3027 def test_redirect307(self):
3029 L{RedirectAgent} follows redirects on status code 307.
3031 self._testRedirectDefault(307)
3034 def test_redirect303(self):
3036 L{RedirectAgent} changes the methods to C{GET} when getting a redirect
3037 on a C{POST} request.
3039 self.agent.request('POST', 'http://example.com/foo')
3041 req, res = self.protocol.requests.pop()
3043 headers = http_headers.Headers(
3044 {'location': ['http://example.com/bar']})
3045 response = Response(('HTTP', 1, 1), 303, 'OK', headers, None)
3046 res.callback(response)
3048 req2, res2 = self.protocol.requests.pop()
3049 self.assertEqual('GET', req2.method)
3050 self.assertEqual('/bar', req2.uri)
3053 def test_noLocationField(self):
3055 If no L{Location} header field is found when getting a redirect,
3056 L{RedirectAgent} fails with a L{ResponseFailed} error wrapping a
3057 L{error.RedirectWithNoLocation} exception.
3059 deferred = self.agent.request('GET', 'http://example.com/foo')
3061 req, res = self.protocol.requests.pop()
3063 headers = http_headers.Headers()
3064 response = Response(('HTTP', 1, 1), 301, 'OK', headers, None)
3065 res.callback(response)
3067 self.assertFailure(deferred, client.ResponseFailed)
3069 def checkFailure(fail):
3070 fail.reasons[0].trap(error.RedirectWithNoLocation)
3071 self.assertEqual('http://example.com/foo',
3072 fail.reasons[0].value.uri)
3073 self.assertEqual(301, fail.response.code)
3075 return deferred.addCallback(checkFailure)
3078 def test_307OnPost(self):
3080 When getting a 307 redirect on a C{POST} request, L{RedirectAgent} fais
3081 with a L{ResponseFailed} error wrapping a L{error.PageRedirect}
3084 deferred = self.agent.request('POST', 'http://example.com/foo')
3086 req, res = self.protocol.requests.pop()
3088 headers = http_headers.Headers()
3089 response = Response(('HTTP', 1, 1), 307, 'OK', headers, None)
3090 res.callback(response)
3092 self.assertFailure(deferred, client.ResponseFailed)
3094 def checkFailure(fail):
3095 fail.reasons[0].trap(error.PageRedirect)
3096 self.assertEqual('http://example.com/foo',
3097 fail.reasons[0].value.location)
3098 self.assertEqual(307, fail.response.code)
3100 return deferred.addCallback(checkFailure)
3103 def test_redirectLimit(self):
3105 If the limit of redirects specified to L{RedirectAgent} is reached, the
3106 deferred fires with L{ResponseFailed} error wrapping a
3107 L{InfiniteRedirection} exception.
3109 agent = self.buildAgentForWrapperTest(self.reactor)
3110 redirectAgent = client.RedirectAgent(agent, 1)
3112 deferred = redirectAgent.request('GET', 'http://example.com/foo')
3114 req, res = self.protocol.requests.pop()
3116 headers = http_headers.Headers(
3117 {'location': ['http://example.com/bar']})
3118 response = Response(('HTTP', 1, 1), 302, 'OK', headers, None)
3119 res.callback(response)
3121 req2, res2 = self.protocol.requests.pop()
3123 response2 = Response(('HTTP', 1, 1), 302, 'OK', headers, None)
3124 res2.callback(response2)
3126 self.assertFailure(deferred, client.ResponseFailed)
3128 def checkFailure(fail):
3129 fail.reasons[0].trap(error.InfiniteRedirection)
3130 self.assertEqual('http://example.com/foo',
3131 fail.reasons[0].value.location)
3132 self.assertEqual(302, fail.response.code)
3134 return deferred.addCallback(checkFailure)
3138 if ssl is None or not hasattr(ssl, 'DefaultOpenSSLContextFactory'):
3139 for case in [WebClientSSLTestCase, WebClientRedirectBetweenSSLandPlainText]:
3140 case.skip = "OpenSSL not present"
3142 if not interfaces.IReactorSSL(reactor, None):
3143 for case in [WebClientSSLTestCase, WebClientRedirectBetweenSSLandPlainText]:
3144 case.skip = "Reactor doesn't support SSL"