190 lexer = get_lexer_by_name(language, stripall=
True)
192 except Exception
as e:
193 logger.warning(
"pygments lexer: %s " % e)
195 lexer = get_lexer_by_name(
'text', stripall=
True)
200 line_code_start =
None
203 for line, code
in codelines:
205 line_code_start = line
208 if last_line
is not None and last_line + 1 != line:
211 formatter = HtmlFormatter(linenos=
'inline', linenostart=line_code_start, cssclass=
"code-highlight")
212 html_code = html_code + highlight(tmp_code, lexer, formatter)
216 line_code_start = line
219 tmp_code += code +
'\n'
225 formatter = HtmlFormatter(linenos=
'inline', linenostart=line_code_start, cssclass=
"code-highlight")
226 html_code = html_code + highlight(tmp_code, lexer, formatter)
371 kwargs[
'client_settings'] = str(
374 json.dumps(client_settings),
380 kwargs[
'preferences'] = sxng_request.preferences
381 kwargs.update(client_settings)
384 kwargs[
'endpoint'] =
'results' if 'q' in kwargs
else sxng_request.endpoint
385 kwargs[
'cookies'] = sxng_request.cookies
386 kwargs[
'errors'] = sxng_request.errors
387 kwargs[
'link_token'] = link_token.get_token()
389 kwargs[
'categories_as_tabs'] = list(settings[
'categories_as_tabs'].keys())
391 kwargs[
'DEFAULT_CATEGORY'] = DEFAULT_CATEGORY
394 kwargs[
'sxng_locales'] = [l
for l
in sxng_locales
if l[0]
in settings[
'search'][
'languages']]
396 locale = sxng_request.preferences.get_value(
'locale')
399 if locale
in RTL_LOCALES
and 'rtl' not in kwargs:
402 if 'current_language' not in kwargs:
403 kwargs[
'current_language'] = parse_lang(sxng_request.preferences, {},
RawTextQuery(
'', []))
406 kwargs[
'search_formats'] = [x
for x
in settings[
'search'][
'formats']
if x !=
'html']
407 kwargs[
'instance_name'] =
get_setting(
'general.instance_name')
408 kwargs[
'searx_version'] = VERSION_STRING
409 kwargs[
'searx_git_url'] = GIT_URL
410 kwargs[
'enable_metrics'] =
get_setting(
'general.enable_metrics')
411 kwargs[
'get_setting'] = get_setting
412 kwargs[
'get_pretty_url'] = get_pretty_url
416 if donation_url
is True:
418 kwargs[
'donation_url'] = donation_url
421 kwargs[
'url_for'] = custom_url_for
422 kwargs[
'image_proxify'] = image_proxify
423 kwargs[
'favicon_url'] = favicons.favicon_url
424 kwargs[
'cache_url'] = settings[
'ui'][
'cache_url']
425 kwargs[
'get_result_template'] = get_result_template
426 kwargs[
'opensearch_url'] = (
427 url_for(
'opensearch')
431 'method': sxng_request.preferences.get_value(
'method'),
432 'autocomplete': sxng_request.preferences.get_value(
'autocomplete'),
436 kwargs[
'urlparse'] = urlparse
438 start_time = default_timer()
439 result = render_template(
'{}/{}'.format(kwargs[
'theme'], template_name), **kwargs)
440 sxng_request.render_time += default_timer() - start_time
447 sxng_request.start_time = default_timer()
448 sxng_request.render_time = 0
449 sxng_request.timings = []
450 sxng_request.errors = []
452 client_pref = ClientPref.from_http_request(sxng_request)
454 preferences =
Preferences(themes, list(categories.keys()), engines, searx.plugins.STORAGE, client_pref)
456 user_agent = sxng_request.headers.get(
'User-Agent',
'').lower()
457 if 'webkit' in user_agent
and 'android' in user_agent:
458 preferences.key_value_settings[
'method'].value =
'GET'
459 sxng_request.preferences = preferences
462 preferences.parse_dict(sxng_request.cookies)
464 except Exception
as e:
465 logger.exception(e, exc_info=
True)
466 sxng_request.errors.append(gettext(
'Invalid settings, please edit your preferences'))
470 sxng_request.form = dict(sxng_request.form.items())
471 for k, v
in sxng_request.args.items():
472 if k
not in sxng_request.form:
473 sxng_request.form[k] = v
475 if sxng_request.form.get(
'preferences'):
476 preferences.parse_encoded_data(sxng_request.form[
'preferences'])
479 preferences.parse_dict(sxng_request.form)
480 except Exception
as e:
481 logger.exception(e, exc_info=
True)
482 sxng_request.errors.append(gettext(
'Invalid settings'))
486 if not preferences.get_value(
"language"):
488 preferences.parse_dict({
"language": language})
489 logger.debug(
'set language %s (from browser)', preferences.get_value(
"language"))
493 if not preferences.get_value(
"locale"):
495 preferences.parse_dict({
"locale": locale})
496 logger.debug(
'set locale %s (from browser)', preferences.get_value(
"locale"))
499 sxng_request.user_plugins = []
500 allowed_plugins = preferences.plugins.get_enabled()
501 disabled_plugins = preferences.plugins.get_disabled()
502 for plugin
in searx.plugins.STORAGE:
503 if (plugin.id
not in disabled_plugins)
or plugin.id
in allowed_plugins:
504 sxng_request.user_plugins.append(plugin.id)
519 total_time = default_timer() - sxng_request.start_time
521 'total;dur=' + str(round(total_time * 1000, 3)),
522 'render;dur=' + str(round(sxng_request.render_time * 1000, 3)),
524 if len(sxng_request.timings) > 0:
525 timings = sorted(sxng_request.timings, key=
lambda t: t.total)
527 'total_' + str(i) +
'_' + t.engine +
';dur=' + str(round(t.total * 1000, 3))
for i, t
in enumerate(timings)
530 'load_' + str(i) +
'_' + t.engine +
';dur=' + str(round(t.load * 1000, 3))
531 for i, t
in enumerate(timings)
534 timings_all = timings_all + timings_total + timings_load
535 response.headers.add(
'Server-Timing',
', '.join(timings_all))
607 """Search query in q and return results.
609 Supported outputs: html, json, csv, rss.
615 output_format = sxng_request.form.get(
'format',
'html')
616 if output_format
not in OUTPUT_FORMATS:
617 output_format =
'html'
619 if output_format
not in settings[
'search'][
'formats']:
623 if not sxng_request.form.get(
'q'):
624 if output_format ==
'html':
628 selected_categories=get_selected_categories(sxng_request.preferences, sxng_request.form),
635 raw_text_query =
None
636 result_container =
None
638 search_query, raw_text_query, _, _, selected_locale = get_search_query_from_webapp(
639 sxng_request.preferences, sxng_request.form
642 result_container = search_obj.search()
644 except SearxParameterException
as e:
645 logger.exception(
'search error: SearxParameterException')
647 except Exception
as e:
648 logger.exception(e, exc_info=
True)
649 return index_error(output_format, gettext(
'search error')), 500
652 if result_container.redirect_url:
653 return redirect(result_container.redirect_url)
657 sxng_request.timings = result_container.get_timings()
661 if output_format ==
'json':
663 response = webutils.get_json_response(search_query, result_container)
664 return Response(response, mimetype=
'application/json')
666 if output_format ==
'csv':
669 webutils.write_csv_response(csv, result_container)
672 response = Response(csv.stream.read(), mimetype=
'application/csv')
673 cont_disp =
'attachment;Filename=searx_-_{0}.csv'.format(search_query.query)
674 response.headers.add(
'Content-Disposition', cont_disp)
679 current_template =
None
680 previous_result =
None
682 results = result_container.get_ordered_results()
684 if search_query.redirect_to_first_result
and results:
685 return redirect(results[0][
'url'], 302)
687 for result
in results:
688 if output_format ==
'html':
689 if 'content' in result
and result[
'content']:
690 result[
'content'] = highlight_content(escape(result[
'content'][:1024]), search_query.query)
691 if 'title' in result
and result[
'title']:
692 result[
'title'] = highlight_content(escape(result[
'title']
or ''), search_query.query)
696 if current_template != result.template:
697 result.open_group =
True
699 previous_result.close_group =
True
700 current_template = result.template
701 previous_result = result
704 previous_result.close_group =
True
708 if output_format ==
'rss':
710 'opensearch_response_rss.xml',
712 q=sxng_request.form[
'q'],
713 number_of_results=result_container.number_of_results,
715 return Response(response_rss, mimetype=
'text/xml')
720 suggestion_urls = list(
722 lambda suggestion: {
'url': raw_text_query.changeQuery(suggestion).getFullQuery(),
'title': suggestion},
723 result_container.suggestions,
727 correction_urls = list(
729 lambda correction: {
'url': raw_text_query.changeQuery(correction).getFullQuery(),
'title': correction},
730 result_container.corrections,
735 engine_timings = sorted(result_container.get_timings(), reverse=
True, key=
lambda e: e.total)
736 max_response_time = engine_timings[0].total
if engine_timings
else None
737 engine_timings_pairs = [(timing.engine, timing.total)
for timing
in engine_timings]
746 q=sxng_request.form[
'q'],
747 selected_categories = search_query.categories,
748 pageno = search_query.pageno,
749 time_range = search_query.time_range
or '',
750 number_of_results = format_decimal(result_container.number_of_results),
751 suggestions = suggestion_urls,
752 answers = result_container.answers,
753 corrections = correction_urls,
754 infoboxes = result_container.infoboxes,
755 engine_data = result_container.engine_data,
756 paging = result_container.paging,
757 unresponsive_engines = webutils.get_translated_errors(
758 result_container.unresponsive_engines
760 current_locale = sxng_request.preferences.get_value(
"locale"),
761 current_language = selected_locale,
762 search_language = match_locale(
763 search_obj.search_query.lang,
764 settings[
'search'][
'languages'],
765 fallback=sxng_request.preferences.get_value(
"language")
767 timeout_limit = sxng_request.form.get(
'timeout_limit',
None),
768 timings = engine_timings_pairs,
769 max_response_time = max_response_time
774@app.route('/about', methods=['GET'])
799 """Return autocompleter results"""
805 disabled_engines = sxng_request.preferences.engines.get_disabled()
808 raw_text_query =
RawTextQuery(sxng_request.form.get(
'q',
''), disabled_engines)
809 sug_prefix = raw_text_query.getQuery()
811 for obj
in searx.answerers.STORAGE.ask(sug_prefix):
812 if isinstance(obj, Answer):
813 results.append(obj.answer)
817 if len(raw_text_query.autocomplete_list) == 0
and len(sug_prefix) > 0:
820 sxng_locale = sxng_request.preferences.get_value(
'language')
821 backend_name = sxng_request.preferences.get_value(
'autocomplete')
823 for result
in search_autocomplete(backend_name, sug_prefix, sxng_locale):
826 if result != sug_prefix:
827 results.append(raw_text_query.changeQuery(result).getFullQuery())
829 if len(raw_text_query.autocomplete_list) > 0:
830 for autocomplete_text
in raw_text_query.autocomplete_list:
831 results.append(raw_text_query.get_autocomplete_full_query(autocomplete_text))
833 if sxng_request.headers.get(
'X-Requested-With') ==
'XMLHttpRequest':
835 suggestions = json.dumps(results)
836 mimetype =
'application/json'
839 suggestions = json.dumps([sug_prefix, results])
840 mimetype =
'application/x-suggestions+json'
842 suggestions = escape(suggestions,
False)
843 return Response(suggestions, mimetype=mimetype)
846@app.route('/preferences', methods=['GET', 'POST'])
848 """Render preferences page && save user preferences"""
854 if sxng_request.args.get(
'preferences')
or sxng_request.form.get(
'preferences'):
855 resp = make_response(redirect(url_for(
'index', _external=
True)))
856 return sxng_request.preferences.save(resp)
859 if sxng_request.method ==
'POST':
860 resp = make_response(redirect(url_for(
'index', _external=
True)))
862 sxng_request.preferences.parse_form(sxng_request.form)
863 except ValidationException:
864 sxng_request.errors.append(gettext(
'Invalid settings, please edit your preferences'))
866 return sxng_request.preferences.save(resp)
869 image_proxy = sxng_request.preferences.get_value(
'image_proxy')
870 disabled_engines = sxng_request.preferences.engines.get_disabled()
871 allowed_plugins = sxng_request.preferences.plugins.get_enabled()
874 filtered_engines = dict(filter(
lambda kv: sxng_request.preferences.validate_token(kv[1]), engines.items()))
876 engines_by_category = {}
879 engines_by_category[c] = [e
for e
in categories[c]
if e.name
in filtered_engines]
881 list.sort(engines_by_category[c], key=
lambda e: e.name)
887 for _, e
in filtered_engines.items():
888 h = histogram(
'engine', e.name,
'time',
'total')
889 median = round(h.percentage(50), 1)
if h.count > 0
else None
890 rate80 = round(h.percentage(80), 1)
if h.count > 0
else None
891 rate95 = round(h.percentage(95), 1)
if h.count > 0
else None
893 max_rate95 = max(max_rate95, rate95
or 0)
895 result_count_sum = histogram(
'engine', e.name,
'result',
'count').sum
896 successful_count = counter(
'engine', e.name,
'search',
'count',
'successful')
897 result_count = int(result_count_sum / float(successful_count))
if successful_count
else 0
903 'warn_timeout': e.timeout > settings[
'outgoing'][
'request_timeout'],
904 'supports_selected_language': e.traits.is_locale_supported(
905 str(sxng_request.preferences.get_value(
'language')
or 'all')
907 'result_count': result_count,
913 engine_errors = get_engine_errors(filtered_engines)
914 checker_results = checker_get_result()
916 checker_results[
'engines']
if checker_results[
'status'] ==
'ok' and 'engines' in checker_results
else {}
918 for _, e
in filtered_engines.items():
919 checker_result = checker_results.get(e.name, {})
920 checker_success = checker_result.get(
'success',
True)
921 errors = engine_errors.get(e.name)
or []
922 if counter(
'engine', e.name,
'search',
'count',
'sent') == 0:
925 elif checker_success
and not errors:
927 elif 'simple' in checker_result.get(
'errors', {}):
933 reliability = 100 - sum([error[
'percentage']
for error
in errors
if not error.get(
'secondary')])
935 reliabilities[e.name] = {
936 'reliability': reliability,
938 'checker': checker_results.get(e.name, {}).get(
'errors', {}).keys(),
942 reliabilities_errors = []
944 error_user_text =
None
945 if error.get(
'secondary')
or 'exception_classname' not in error:
947 error_user_text = exception_classname_to_text.get(error.get(
'exception_classname'))
949 error_user_text = exception_classname_to_text[
None]
950 if error_user_text
not in reliabilities_errors:
951 reliabilities_errors.append(error_user_text)
952 reliabilities[e.name][
'errors'] = reliabilities_errors
956 for _, e
in filtered_engines.items():
957 supports_selected_language = e.traits.is_locale_supported(
958 str(sxng_request.preferences.get_value(
'language')
or 'all')
960 safesearch = e.safesearch
961 time_range_support = e.time_range_support
962 for checker_test_name
in checker_results.get(e.name, {}).get(
'errors', {}):
963 if supports_selected_language
and checker_test_name.startswith(
'lang_'):
964 supports_selected_language =
'?'
965 elif safesearch
and checker_test_name ==
'safesearch':
967 elif time_range_support
and checker_test_name ==
'time_range':
968 time_range_support =
'?'
970 'supports_selected_language': supports_selected_language,
971 'safesearch': safesearch,
972 'time_range_support': time_range_support,
979 selected_categories = get_selected_categories(sxng_request.preferences, sxng_request.form),
980 locales = LOCALE_NAMES,
981 current_locale = sxng_request.preferences.get_value(
"locale"),
982 image_proxy = image_proxy,
983 engines_by_category = engines_by_category,
985 max_rate95 = max_rate95,
986 reliabilities = reliabilities,
988 answer_storage = searx.answerers.STORAGE.info,
989 disabled_engines = disabled_engines,
990 autocomplete_backends = autocomplete_backends,
991 favicon_resolver_names = favicons.proxy.CFG.resolver_map.keys(),
992 shortcuts = {y: x
for x, y
in engine_shortcuts.items()},
994 plugins_storage = searx.plugins.STORAGE.info,
995 current_doi_resolver = get_doi_resolver(),
996 allowed_plugins = allowed_plugins,
997 preferences_url_params = sxng_request.preferences.get_as_url_params(),
998 locked_preferences =
get_setting(
"preferences.lock", []),
1011 url = sxng_request.args.get(
'url')
1015 if not is_hmac_of(settings[
'server'][
'secret_key'], url.encode(), sxng_request.args.get(
'h',
'')):
1018 maximum_size = 5 * 1024 * 1024
1019 forward_resp =
False
1023 'User-Agent': gen_useragent(),
1024 'Accept':
'image/webp,*/*',
1025 'Accept-Encoding':
'gzip, deflate',
1029 set_context_network_name(
'image_proxy')
1030 resp, stream = http_stream(method=
'GET', url=url, headers=request_headers, allow_redirects=
True)
1031 content_length = resp.headers.get(
'Content-Length')
1032 if content_length
and content_length.isdigit()
and int(content_length) > maximum_size:
1033 return 'Max size', 400
1035 if resp.status_code != 200:
1036 logger.debug(
'image-proxy: wrong response code: %i', resp.status_code)
1037 if resp.status_code >= 400:
1038 return '', resp.status_code
1041 if not resp.headers.get(
'Content-Type',
'').startswith(
'image/')
and not resp.headers.get(
1043 ).startswith(
'binary/octet-stream'):
1044 logger.debug(
'image-proxy: wrong content-type: %s', resp.headers.get(
'Content-Type',
''))
1048 except httpx.HTTPError:
1049 logger.exception(
'HTTP error')
1052 if resp
and not forward_resp:
1057 except httpx.HTTPError:
1058 logger.exception(
'HTTP error on closing')
1061 nonlocal resp, stream
1067 except httpx.HTTPError
as e:
1068 logger.debug(
'Exception while closing response', e)
1071 headers = dict_subset(resp.headers, {
'Content-Type',
'Content-Encoding',
'Content-Length',
'Length'})
1072 response = Response(stream, mimetype=resp.headers[
'Content-Type'], headers=headers, direct_passthrough=
True)
1073 response.call_on_close(close_stream)
1075 except httpx.HTTPError:
1080@app.route('/engine_descriptions.json', methods=['GET'])
1082 sxng_ui_lang_tag =
get_locale().replace(
"_",
"-")
1083 sxng_ui_lang_tag = LOCALE_BEST_MATCH.get(sxng_ui_lang_tag, sxng_ui_lang_tag)
1085 result = ENGINE_DESCRIPTIONS[
'en'].copy()
1086 if sxng_ui_lang_tag !=
'en':
1087 for engine, description
in ENGINE_DESCRIPTIONS.get(sxng_ui_lang_tag, {}).items():
1088 result[engine] = description
1089 for engine, description
in result.items():
1090 if len(description) == 2
and description[1] ==
'ref':
1091 ref_engine, ref_lang = description[0].split(
':')
1092 description = ENGINE_DESCRIPTIONS[ref_lang][ref_engine]
1093 if isinstance(description, str):
1094 description = [description,
'wikipedia']
1095 result[engine] = description
1098 for engine_name, engine_mod
in engines.items():
1099 descr = getattr(engine_mod,
'about', {}).get(
'description',
None)
1100 if descr
is not None:
1101 result[engine_name] = [descr,
"SearXNG config"]
1103 return jsonify(result)
1106@app.route('/stats', methods=['GET'])
1108 """Render engine statistics page."""
1109 sort_order = sxng_request.args.get(
'sort', default=
'name', type=str)
1110 selected_engine_name = sxng_request.args.get(
'engine', default=
None, type=str)
1112 filtered_engines = dict(filter(
lambda kv: sxng_request.preferences.validate_token(kv[1]), engines.items()))
1113 if selected_engine_name:
1114 if selected_engine_name
not in filtered_engines:
1115 selected_engine_name =
None
1117 filtered_engines = [selected_engine_name]
1119 checker_results = checker_get_result()
1121 checker_results[
'engines']
if checker_results[
'status'] ==
'ok' and 'engines' in checker_results
else {}
1124 engine_stats = get_engines_stats(filtered_engines)
1125 engine_reliabilities = get_reliabilities(filtered_engines, checker_results)
1127 if sort_order
not in STATS_SORT_PARAMETERS:
1130 reverse, key_name, default_value = STATS_SORT_PARAMETERS[sort_order]
1132 def get_key(engine_stat):
1133 reliability = engine_reliabilities.get(engine_stat[
'name'], {}).get(
'reliability', 0)
1134 reliability_order = 0
if reliability
else 1
1135 if key_name ==
'reliability':
1137 reliability_order = 0
1139 key = engine_stat.get(key_name)
or default_value
1141 reliability_order = 1 - reliability_order
1142 return (reliability_order, key, engine_stat[
'name'])
1144 technical_report = []
1145 for error
in engine_reliabilities.get(selected_engine_name, {}).get(
'errors', []):
1146 technical_report.append(
1148 Error: {error['exception_classname'] or error['log_message']} \
1149 Parameters: {error['log_parameters']} \
1150 File name: {error['filename'] }:{ error['line_no'] } \
1151 Error Function: {error['function']} \
1152 Code: {error['code']} \
1157 technical_report =
' '.join(technical_report)
1159 engine_stats[
'time'] = sorted(engine_stats[
'time'], reverse=reverse, key=get_key)
1163 sort_order = sort_order,
1164 engine_stats = engine_stats,
1165 engine_reliabilities = engine_reliabilities,
1166 selected_engine_name = selected_engine_name,
1167 searx_git_branch = GIT_BRANCH,
1168 technical_report = technical_report,
1173@app.route('/stats/errors', methods=['GET'])
1188 password = settings[
'general'].get(
"open_metrics")
1190 if not (settings[
'general'].get(
"enable_metrics")
and password):
1191 return Response(
'open metrics is disabled', status=404, mimetype=
'text/plain')
1193 if not sxng_request.authorization
or sxng_request.authorization.password != password:
1194 return Response(
'access forbidden', status=401, mimetype=
'text/plain')
1196 filtered_engines = dict(filter(
lambda kv: sxng_request.preferences.validate_token(kv[1]), engines.items()))
1198 checker_results = checker_get_result()
1200 checker_results[
'engines']
if checker_results[
'status'] ==
'ok' and 'engines' in checker_results
else {}
1203 engine_stats = get_engines_stats(filtered_engines)
1204 engine_reliabilities = get_reliabilities(filtered_engines, checker_results)
1205 metrics_text = openmetrics(engine_stats, engine_reliabilities)
1207 return Response(metrics_text, mimetype=
'text/plain')
1210@app.route('/robots.txt', methods=['GET'])