@@ -1,14 +1,22 @@
1import logging
1import logging
2import pycurl
2import pycurl
4+from itertools import cycle
3from StringIO import StringIO
5from StringIO import StringIO
4from urllib import urlencode
7from urllib import urlencode
10+CURL_SHARE = pycurl.CurlShare()
11+CURL_SHARE.setopt(pycurl.SH_SHARE, pycurl.LOCK_DATA_DNS)
12+MULTI_HANDLER = pycurl.CurlMulti()
7def __test_callback(*args):
15def __test_callback(*args):
8 print "callback called"
16 print "callback called"
11−def get_new_connection(source_address=None):
19+def get_connection(source_address=None):
12 # pycurl initialization
20 # pycurl initialization
13 h = pycurl.Curl()
21 h = pycurl.Curl()
@@ -18,46 +26,14 @@ def get_new_connection(source_address=None):
18 # consistently use ipv4
26 # consistently use ipv4
19 h.setopt(h.IPRESOLVE, pycurl.IPRESOLVE_V4)
27 h.setopt(h.IPRESOLVE, pycurl.IPRESOLVE_V4)
29+ h.setopt(pycurl.SHARE, CURL_SHARE)
21 if source_address:
31 if source_address:
22 h.setopt(h.INTERFACE, source_address)
32 h.setopt(h.INTERFACE, source_address)
27−class ConnectionSources(object):
28− def __init__(self, sources=None):
32− self.sources.append(get_new_connection(s))
34− self.sources.append(get_new_connection())
37− def get_source(self):
38− source = self.sources[self.ptr]
39− self.ptr = (self.ptr + 1) % len(self.sources)
43−class ConnectionCache(object):
44− def __init__(self, source_ips=None):
45− self.source_ips = source_ips if source_ips else None
46− self.connections = {}
48− def get_connection(self, key):
49− if key in self.connections:
50− return self.connections[key].get_source()
52− sources = ConnectionSources(self.source_ips)
53− self.connections[key] = sources
54− return sources.get_source()
57−CONNECTION_CACHE = ConnectionCache()
58−MULTI_HANDLER = pycurl.CurlMulti()
61class RequestContainer(object):
37class RequestContainer(object):
62 def __init__(self,
38 def __init__(self,
@@ -110,29 +86,27 @@ class ResponseContainer(object):
112class MultiRequest(object):
88class MultiRequest(object):
113− def __init__(self, connection_cache=None, multi_handler=None):
89+ def __init__(self, multi_handler=None, source_ips=None):
114 self.requests = {}
90 self.requests = {}
116− if connection_cache:
117− self.connection_cache = connection_cache
119− self.connection_cache = CONNECTION_CACHE
121 if multi_handler:
92 if multi_handler:
122 self._curl_multi_handler = multi_handler
93 self._curl_multi_handler = multi_handler
124 self._curl_multi_handler = MULTI_HANDLER
95 self._curl_multi_handler = MULTI_HANDLER
97+ self.source_ips = cycle(source_ips) if source_ips else cycle([None])
126− def add(self, connection_name, url, **kwargs):
99+ def add(self, url, **kwargs):
127− handle = self.connection_cache.get_connection(connection_name)
100+ handle = get_connection(next(self.source_ips))
128 request_container = RequestContainer(url, handle, **kwargs)
101 request_container = RequestContainer(url, handle, **kwargs)
130 self._curl_multi_handler.add_handle(handle)
103 self._curl_multi_handler.add_handle(handle)
133 self.requests[handle] = request_container
107 self.requests[handle] = request_container
135− def perform_requests(self):
109+ def send_requests(self):
136 select_timeout = 0.1
110 select_timeout = 0.1
138 # set timeout
112 # set timeout
@@ -154,6 +128,7 @@ class MultiRequest(object):
155 h.unsetopt(h.COOKIE)
129 h.unsetopt(h.COOKIE)
131+ search_start = time()
157 handles_num = len(self.requests)
132 handles_num = len(self.requests)
158 while handles_num:
133 while handles_num:
159 self._curl_multi_handler.select(select_timeout)
134 self._curl_multi_handler.select(select_timeout)
@@ -164,23 +139,35 @@ class MultiRequest(object):
164 _, success_list, error_list = self._curl_multi_handler.info_read()
139 _, success_list, error_list = self._curl_multi_handler.info_read()
165 # calling callbacks
140 # calling callbacks
166 for h in success_list:
141 for h in success_list:
142+ th = threading.Thread(
143+ target=self.requests[h].finish(),
144+ name='search_request',
167− self.requests[h].finish()
147+ # self.requests[h].finish()
168 for h, err_code, err_string in error_list:
148 for h, err_code, err_string in error_list:
169 logging.warn('Error on %s: "%s"', self.requests[h].url, err_string)
149 logging.warn('Error on %s: "%s"', self.requests[h].url, err_string)
170 handles_num -= len(success_list) + len(error_list)
150 handles_num -= len(success_list) + len(error_list)
171 if ret != pycurl.E_CALL_MULTI_PERFORM:
151 if ret != pycurl.E_CALL_MULTI_PERFORM:
154+ for th in threading.enumerate():
155+ if th.name == 'search_request':
156+ remaining_time = max(0.0, timeout - (time() - search_start))
157+ th.join(remaining_time)
159+ logging.warning('engine timeout: {0}'.format(th._engine_name))
174 # self._curl_multi_handler.close()
161 # self._curl_multi_handler.close()
175 return self.requests.values()
162 return self.requests.values()
178if __name__ == '__main__':
165if __name__ == '__main__':
179 r = MultiRequest()
166 r = MultiRequest()
180− r.add('a', 'http://httpbin.org/delay/0', headers={'User-Agent': 'x'})
167+ r.add('http://httpbin.org/delay/0', headers={'User-Agent': 'x'})
181− r.add('d', 'http://127.0.0.1:7777/', headers={'User-Agent': 'x'})
168+ r.add('http://127.0.0.1:7777/', headers={'User-Agent': 'x'})
182− r.add('b', 'http://httpbin.org/delay/0', cookies={'as': 'sa', 'bb': 'cc'})
169+ r.add('http://httpbin.org/delay/0', cookies={'as': 'sa', 'bb': 'cc'})
183− r.add('c', 'http://httpbin.org/delay/0', callback=__test_callback, timeout=1.0, headers={'User-Agent': 'x'})
170+ r.add('http://httpbin.org/delay/0', callback=__test_callback, timeout=1.0, headers={'User-Agent': 'x'})
184− for v in r.perform_requests():
171+ for v in r.send_requests():
185 print v.url
172 print v.url
186 print v.response.text
173 print v.response.text