@@ -36,14 +36,53 @@ logger = logger.getChild('search')
36number_of_searches = 0
36number_of_searches = 0
39−def search_request_wrapper(fn, url, engine_name, **kwargs):
39+def send_http_request(engine, request_params, timeout_limit):
40− ret = None
40+ response = None
41− engine = engines[engine_name]
43− ret = fn(url, **kwargs)
42+ # create dictionary which contain all
43+ # informations about the request
45+ headers=request_params['headers'],
46+ cookies=request_params['cookies'],
47+ timeout=timeout_limit,
48+ verify=request_params['verify']
50+ # specific type of request (GET or POST)
51+ if request_params['method'] == 'GET':
52+ req = requests_lib.get
54+ req = requests_lib.post
55+ request_args['data'] = request_params['data']
57+ # for page_load_time stats
58+ time_before_request = time()
61+ response = req(request_params['url'], **request_args)
44 with threading.RLock():
63 with threading.RLock():
64+ # no error : reset the suspend variables
45 engine.continuous_errors = 0
65 engine.continuous_errors = 0
46 engine.suspend_end_time = 0
66 engine.suspend_end_time = 0
67+ # update stats with current page-load-time
68+ # only the HTTP request
69+ engine.stats['page_load_time'] += time() - time_before_request
70+ engine.stats['page_load_count'] += 1
72+ # is there a timeout (no parsing in this case)
73+ timeout_overhead = 0.2 # seconds
74+ search_duration = time() - request_params['started']
75+ if search_duration > timeout_limit + timeout_overhead:
76+ logger.exception('engine timeout on HTTP request:'
77+ '{0} (search duration : {1} ms, time-out: {2} )'
78+ .format(engine.name, search_duration, timeout_limit))
79+ with threading.RLock():
80+ engine.stats['errors'] += 1
83+ # everything is ok : return the response
48 # increase errors stats
87 # increase errors stats
49 with threading.RLock():
88 with threading.RLock():
@@ -52,20 +91,62 @@ def search_request_wrapper(fn, url, engine_name, **kwargs):
52 engine.suspend_end_time = time() + min(60, engine.continuous_errors)
91 engine.suspend_end_time = time() + min(60, engine.continuous_errors)
54 # print engine name and specific error message
93 # print engine name and specific error message
55− logger.exception('engine crash: {0}'.format(engine_name))
94+ logger.exception('engine crash: {0}'.format(engine.name))
98+def search_one_request(engine_name, query, request_params, result_container, timeout_limit):
99+ engine = engines[engine_name]
101+ # update request parameters dependent on
102+ # search-engine (contained in engines folder)
103+ engine.request(query, request_params)
105+ # TODO add support of offline engines
106+ if request_params['url'] is None:
109+ # ignoring empty urls
110+ if not request_params['url']:
56− return ret
111+ return False
114+ response = send_http_request(engine, request_params, timeout_limit)
119+ # parse the response
120+ response.search_params = request_params
121+ search_results = engine.response(response)
124+ for result in search_results:
125+ result['engine'] = engine.name
127+ result_container.extend(engine.name, search_results)
133+ with threading.RLock():
134+ # update stats : total time
135+ engine.stats['engine_time'] += time() - request_params['started']
136+ engine.stats['engine_time_count'] += 1
59−def threaded_requests(requests):
141+
60− timeout_limit = max(r[2]['timeout'] for r in requests)
142+def search_multiple_requests(requests, result_container, timeout_limit):
61− search_start = time()
143+ start_time = time()
62 search_id = uuid4().__str__()
144 search_id = uuid4().__str__()
63− for fn, url, request_args, engine_name in requests:
146+ for engine_name, query, request_params in requests:
64− request_args['timeout'] = timeout_limit
65 th = threading.Thread(
147 th = threading.Thread(
66− target=search_request_wrapper,
148+ target=search_one_request,
67− args=(fn, url, engine_name),
149+ args=(engine_name, query, request_params, result_container, timeout_limit),
69 name=search_id,
150 name=search_id,
71 th._engine_name = engine_name
152 th._engine_name = engine_name
@@ -73,7 +154,7 @@ def threaded_requests(requests):
74 for th in threading.enumerate():
155 for th in threading.enumerate():
75 if th.name == search_id:
156 if th.name == search_id:
76− remaining_time = max(0.0, timeout_limit - (time() - search_start))
157+ remaining_time = max(0.0, timeout_limit - (time() - start_time))
77 th.join(remaining_time)
158 th.join(remaining_time)
78 if th.isAlive():
159 if th.isAlive():
79 logger.warning('engine timeout: {0}'.format(th._engine_name))
160 logger.warning('engine timeout: {0}'.format(th._engine_name))
@@ -91,44 +172,6 @@ def default_request_params():
94−# create a callback wrapper for the search engine results
95−def make_callback(engine_name, callback, params, result_container):
97− # creating a callback wrapper for the search engine results
98− def process_callback(response, **kwargs):
99− # check if redirect comparing to the True value,
100− # because resp can be a Mock object, and any attribut name returns something.
101− if response.is_redirect is True:
102− logger.debug('{0} redirect on: {1}'.format(engine_name, response))
105− response.search_params = params
107− search_duration = time() - params['started']
108− # update stats with current page-load-time
109− with threading.RLock():
110− engines[engine_name].stats['page_load_time'] += search_duration
112− timeout_overhead = 0.2 # seconds
113− timeout_limit = engines[engine_name].timeout + timeout_overhead
115− if search_duration > timeout_limit:
116− with threading.RLock():
117− engines[engine_name].stats['errors'] += 1
121− search_results = callback(response)
124− for result in search_results:
125− result['engine'] = engine_name
127− result_container.extend(engine_name, search_results)
129− return process_callback
132def get_search_query_from_webapp(preferences, form):
175def get_search_query_from_webapp(preferences, form):
133 query = None
176 query = None
134 query_engines = []
177 query_engines = []
@@ -255,6 +298,10 @@ class Search(object):
255 def search(self):
298 def search(self):
256 global number_of_searches
299 global number_of_searches
258 answerers_results = ask(self.search_query)
305 answerers_results = ask(self.search_query)
260 if answerers_results:
307 if answerers_results:
@@ -274,6 +321,9 @@ class Search(object):
275 search_query = self.search_query
322 search_query = self.search_query
324+ # max of all selected engine timeout
277 # start search-reqest for all selected engines
327 # start search-reqest for all selected engines
278 for selected_engine in search_query.engines:
328 for selected_engine in search_query.engines:
279 if selected_engine['name'] not in engines:
329 if selected_engine['name'] not in engines:
@@ -303,7 +353,7 @@ class Search(object):
303 request_params = default_request_params()
353 request_params = default_request_params()
304 request_params['headers']['User-Agent'] = user_agent
354 request_params['headers']['User-Agent'] = user_agent
305 request_params['category'] = selected_engine['category']
355 request_params['category'] = selected_engine['category']
306− request_params['started'] = time()
356+ request_params['started'] = start_time
307 request_params['pageno'] = search_query.pageno
357 request_params['pageno'] = search_query.pageno
309 if hasattr(engine, 'language') and engine.language:
359 if hasattr(engine, 'language') and engine.language:
@@ -315,52 +365,16 @@ class Search(object):
315 request_params['safesearch'] = search_query.safesearch
365 request_params['safesearch'] = search_query.safesearch
316 request_params['time_range'] = search_query.time_range
366 request_params['time_range'] = search_query.time_range
318− # update request parameters dependent on
319− # search-engine (contained in engines folder)
320− engine.request(search_query.query.encode('utf-8'), request_params)
322− if request_params['url'] is None:
323− # TODO add support of offline engines
326− # create a callback wrapper for the search engine results
327− callback = make_callback(
328− selected_engine['name'],
331− self.result_container)
333− # create dictionary which contain all
334− # informations about the request
335− request_args = dict(
336− headers=request_params['headers'],
337− hooks=dict(response=callback),
338− cookies=request_params['cookies'],
339− timeout=engine.timeout,
340− verify=request_params['verify']
343− # specific type of request (GET or POST)
344− if request_params['method'] == 'GET':
345− req = requests_lib.get
347− req = requests_lib.post
348− request_args['data'] = request_params['data']
350− # ignoring empty urls
351− if not request_params['url']:
354 # append request to list
368 # append request to list
355− requests.append((req, request_params['url'],
357− selected_engine['name']))
369+ requests.append((selected_engine['name'], search_query.query.encode('utf-8'), request_params))
371+ # update timeout_limit
372+ timeout_limit = max(timeout_limit, engine.timeout)
359− if not requests:
374+ if requests:
360− return self.result_container
361− # send all search-request
375+ # send all search-request
362− threaded_requests(requests)
376+ search_multiple_requests(requests, self.result_container, timeout_limit - (time() - start_time))
363− start_new_thread(gc.collect, tuple())
377+ start_new_thread(gc.collect, tuple())
365 # return results, suggestions, answers and infoboxes
379 # return results, suggestions, answers and infoboxes
366 return self.result_container
380 return self.result_container