2 from twisted.internet import reactor, defer, protocol
3 from twisted.internet.protocol import ClientFactory
4 from twisted.web2.client.interfaces import IHTTPClientManager
5 from twisted.web2.client.http import ProtocolError, ClientRequest, HTTPClientProtocol
6 from twisted.trial import unittest
7 from zope.interface import implements
9 class HTTPClientManager(ClientFactory):
10 """A manager for all HTTP requests to a single site.
15 implements(IHTTPClientManager)
17 def __init__(self, host, port):
23 self.connecting = False
24 self.request_queue = []
25 self.response_queue = []
30 assert(self.closed and not self.connecting)
31 self.connecting = True
32 d = protocol.ClientCreator(reactor, HTTPClientProtocol, self).connectTCP(self.host, self.port)
33 d.addCallback(self.connected)
35 def connected(self, proto):
37 self.connecting = False
43 self.proto.transport.loseConnection()
45 def submitRequest(self, request):
46 request.deferRequest = defer.Deferred()
47 self.request_queue.append(request)
49 return request.deferRequest
51 def processQueue(self):
52 if not self.request_queue:
59 if self.busy and not self.pipeline:
61 if self.response_queue and not self.pipeline:
64 req = self.request_queue.pop(0)
65 self.response_queue.append(req)
66 req.deferResponse = self.proto.submitRequest(req, False)
67 req.deferResponse.addCallback(self.requestComplete)
68 req.deferResponse.addErrback(self.requestError)
70 def requestComplete(self, resp):
71 req = self.response_queue.pop(0)
72 req.deferRequest.callback(resp)
74 def requestError(self, error):
75 req = self.response_queue.pop(0)
76 req.deferRequest.errback(error)
78 def clientBusy(self, proto):
81 def clientIdle(self, proto):
85 def clientPipelining(self, proto):
89 def clientGone(self, proto):
90 for req in self.response_queue:
91 req.deferRequest.errback(ProtocolError('lost connection'))
95 self.connecting = False
96 self.response_queue = []
98 if self.request_queue:
101 class TestDownloader(unittest.TestCase):
106 def gotResp(self, resp, num, expect):
107 self.failUnless(resp.code >= 200 and resp.code < 300, "Got a non-200 response: %r" % resp.code)
108 self.failUnless(resp.stream.length == expect, "Length was incorrect, got %r, expected %r" % (resp.stream.length, expect))
111 def test_download(self):
112 host = 'www.camrdale.org'
113 self.client = HTTPClientManager(host, 80)
115 lastDefer = defer.Deferred()
117 d = self.client.submitRequest(ClientRequest("GET", '/robots.txt', {'Host':host}, None))
118 d.addCallback(self.gotResp, 1, 309)
119 d.addBoth(lastDefer.callback)
123 host = 'www.camrdale.org'
124 self.client = HTTPClientManager(host, 80)
126 lastDefer = defer.Deferred()
128 d = self.client.submitRequest(ClientRequest("HEAD", '/robots.txt', {'Host':host}, None))
129 d.addCallback(self.gotResp, 1, 0)
130 d.addBoth(lastDefer.callback)
133 def test_multiple_downloads(self):
134 host = 'www.camrdale.org'
135 self.client = HTTPClientManager(host, 80)
137 lastDefer = defer.Deferred()
139 def newRequest(path, num, expect, last=False):
140 d = self.client.submitRequest(ClientRequest("GET", path, {'Host':host}, None))
141 d.addCallback(self.gotResp, num, expect)
143 d.addCallback(lastDefer.callback)
145 newRequest("/", 1, 3433)
146 newRequest("/blog/", 2, 37121)
147 newRequest("/camrdale.html", 3, 2234)
148 self.pending_calls.append(reactor.callLater(1, newRequest, '/robots.txt', 4, 309))
149 self.pending_calls.append(reactor.callLater(10, newRequest, '/wikilink.html', 5, 3084))
150 self.pending_calls.append(reactor.callLater(30, newRequest, '/sitemap.html', 6, 4750))
151 self.pending_calls.append(reactor.callLater(31, newRequest, '/PlanetLab.html', 7, 2783))
152 self.pending_calls.append(reactor.callLater(32, newRequest, '/openid.html', 8, 2525))
153 self.pending_calls.append(reactor.callLater(32, newRequest, '/subpage.html', 9, 2381))
154 self.pending_calls.append(reactor.callLater(62, newRequest, '/sitemap2.rss', 0, 302362, True))
157 def test_range(self):
158 host = 'www.camrdale.org'
159 self.client = HTTPClientManager(host, 80)
161 lastDefer = defer.Deferred()
163 d = self.client.submitRequest(ClientRequest("GET", '/robots.txt', {'Host':host, 'Range': ('bytes', [(100, 199)])}, None))
164 d.addCallback(self.gotResp, 1, 100)
165 d.addBoth(lastDefer.callback)
169 for p in self.pending_calls:
172 self.pending_calls = []