.oO SearXNG Developer Documentation Oo.
Loading...
Searching...
No Matches
searx.webapp Namespace Reference

Functions

 get_locale ()
 
 _get_browser_language (req, lang_list)
 
 _get_locale_rfc5646 (locale)
 
 code_highlighter (codelines, language=None)
 
 get_result_template (str theme_name, str template_name)
 
 custom_url_for (str endpoint, **values)
 
 morty_proxify (str url)
 
 image_proxify (str url)
 
 get_translations ()
 
 get_enabled_categories (typing.Iterable[str] category_names)
 
 get_pretty_url (urllib.parse.ParseResult parsed_url)
 
 get_client_settings ()
 
 render (str template_name, **kwargs)
 
 pre_request ()
 
 add_default_headers (flask.Response response)
 
 post_request (flask.Response response)
 
 index_error (str output_format, str error_message)
 
 index ()
 
 health ()
 
 client_token (token=None)
 
 rss_xsl ()
 
 search ()
 
 about ()
 
 info (pagename, locale)
 
 autocompleter ()
 
 preferences ()
 
 image_proxy ()
 
 engine_descriptions ()
 
 stats ()
 
 stats_errors ()
 
 stats_checker ()
 
 stats_open_metrics ()
 
 robots ()
 
 opensearch ()
 
 favicon ()
 
 clear_cookies ()
 
 config ()
 
 page_not_found (_e)
 
 run ()
 
bool is_werkzeug_reload_active ()
 
 init ()
 

Variables

 logger = logger.getChild('webapp')
 
 static_files = get_static_files(settings['ui']['static_path'])
 
 default_theme = settings['ui']['default_theme']
 
 templates_path = settings['ui']['templates_path']
 
 themes = get_themes(templates_path)
 
 result_templates = get_result_templates(templates_path)
 
dict STATS_SORT_PARAMETERS
 
 app = Flask(__name__, static_folder=settings['ui']['static_path'], template_folder=templates_path)
 
 trim_blocks
 
 lstrip_blocks
 
 secret_key
 
 babel = Babel(app, locale_selector=get_locale)
 
 methods
 
 endpoint
 
 view_func
 
 application = app
 

Detailed Description

WebbApp

Function Documentation

◆ _get_browser_language()

searx.webapp._get_browser_language ( req,
lang_list )
protected

Definition at line 171 of file webapp.py.

171def _get_browser_language(req, lang_list):
172 client = ClientPref.from_http_request(req)
173 locale = match_locale(client.locale_tag, lang_list, fallback='en')
174 return locale
175
176

Referenced by pre_request().

+ Here is the caller graph for this function:

◆ _get_locale_rfc5646()

searx.webapp._get_locale_rfc5646 ( locale)
protected
Get locale name for <html lang="...">
Chrom* browsers don't detect the language when there is a subtag (ie a territory).
For example "zh-TW" is detected but not "zh-Hant-TW".
This function returns a locale without the subtag.

Definition at line 177 of file webapp.py.

177def _get_locale_rfc5646(locale):
178 """Get locale name for <html lang="...">
179 Chrom* browsers don't detect the language when there is a subtag (ie a territory).
180 For example "zh-TW" is detected but not "zh-Hant-TW".
181 This function returns a locale without the subtag.
182 """
183 parts = locale.split('-')
184 return parts[0].lower() + '-' + parts[-1].upper()
185
186
187# code-highlighter
188@app.template_filter('code_highlighter')

Referenced by render().

+ Here is the caller graph for this function:

◆ about()

searx.webapp.about ( )
Redirect to about page

Definition at line 780 of file webapp.py.

780def about():
781 """Redirect to about page"""
782 # custom_url_for is going to add the locale
783 return redirect(custom_url_for('info', pagename='about'))
784
785
786@app.route('/info/<locale>/<pagename>', methods=['GET'])

References custom_url_for().

+ Here is the call graph for this function:

◆ add_default_headers()

searx.webapp.add_default_headers ( flask.Response response)

Definition at line 513 of file webapp.py.

513def add_default_headers(response: flask.Response):
514 # set default http headers
515 for header, value in settings['server']['default_http_headers'].items():
516 if header in response.headers:
517 continue
518 response.headers[header] = value
519 return response
520
521
522@app.after_request

◆ autocompleter()

searx.webapp.autocompleter ( )
Return autocompleter results

Definition at line 803 of file webapp.py.

803def autocompleter():
804 """Return autocompleter results"""
805
806 # run autocompleter
807 results = []
808
809 # set blocked engines
810 disabled_engines = sxng_request.preferences.engines.get_disabled()
811
812 # parse query
813 raw_text_query = RawTextQuery(sxng_request.form.get('q', ''), disabled_engines)
814 sug_prefix = raw_text_query.getQuery()
815
816 for obj in searx.answerers.STORAGE.ask(sug_prefix):
817 if isinstance(obj, Answer):
818 results.append(obj.answer)
819
820 # normal autocompletion results only appear if no inner results returned
821 # and there is a query part
822 if len(raw_text_query.autocomplete_list) == 0 and len(sug_prefix) > 0:
823
824 # get SearXNG's locale and autocomplete backend from cookie
825 sxng_locale = sxng_request.preferences.get_value('language')
826 backend_name = sxng_request.preferences.get_value('autocomplete')
827
828 for result in search_autocomplete(backend_name, sug_prefix, sxng_locale):
829 # attention: this loop will change raw_text_query object and this is
830 # the reason why the sug_prefix was stored before (see above)
831 if result != sug_prefix:
832 results.append(raw_text_query.changeQuery(result).getFullQuery())
833
834 if len(raw_text_query.autocomplete_list) > 0:
835 for autocomplete_text in raw_text_query.autocomplete_list:
836 results.append(raw_text_query.get_autocomplete_full_query(autocomplete_text))
837
838 if sxng_request.headers.get('X-Requested-With') == 'XMLHttpRequest':
839 # the suggestion request comes from the searx search form
840 suggestions = json.dumps(results)
841 mimetype = 'application/json'
842 else:
843 # the suggestion request comes from browser's URL bar
844 suggestions = json.dumps([sug_prefix, results])
845 mimetype = 'application/x-suggestions+json'
846
847 suggestions = escape(suggestions, False)
848 return Response(suggestions, mimetype=mimetype)
849
850
851@app.route('/preferences', methods=['GET', 'POST'])

◆ clear_cookies()

searx.webapp.clear_cookies ( )

Definition at line 1255 of file webapp.py.

1255def clear_cookies():
1256 resp = make_response(redirect(url_for('index', _external=True)))
1257 for cookie_name in sxng_request.cookies:
1258 resp.delete_cookie(cookie_name)
1259 return resp
1260
1261
1262@app.route('/config')

◆ client_token()

searx.webapp.client_token ( token = None)

Definition at line 597 of file webapp.py.

597def client_token(token=None):
598 link_token.ping(sxng_request, token)
599 return Response('', mimetype='text/css', headers={"Cache-Control": "no-store, max-age=0"})
600
601
602@app.route('/rss.xsl', methods=['GET', 'POST'])

◆ code_highlighter()

searx.webapp.code_highlighter ( codelines,
language = None )

Definition at line 189 of file webapp.py.

189def code_highlighter(codelines, language=None):
190 if not language:
191 language = 'text'
192
193 try:
194 # find lexer by programming language
195 lexer = get_lexer_by_name(language, stripall=True)
196
197 except Exception as e: # pylint: disable=broad-except
198 logger.warning("pygments lexer: %s " % e)
199 # if lexer is not found, using default one
200 lexer = get_lexer_by_name('text', stripall=True)
201
202 html_code = ''
203 tmp_code = ''
204 last_line = None
205 line_code_start = None
206
207 # parse lines
208 for line, code in codelines:
209 if not last_line:
210 line_code_start = line
211
212 # new codeblock is detected
213 if last_line is not None and last_line + 1 != line:
214
215 # highlight last codepart
216 formatter = HtmlFormatter(linenos='inline', linenostart=line_code_start, cssclass="code-highlight")
217 html_code = html_code + highlight(tmp_code, lexer, formatter)
218
219 # reset conditions for next codepart
220 tmp_code = ''
221 line_code_start = line
222
223 # add codepart
224 tmp_code += code + '\n'
225
226 # update line
227 last_line = line
228
229 # highlight last codepart
230 formatter = HtmlFormatter(linenos='inline', linenostart=line_code_start, cssclass="code-highlight")
231 html_code = html_code + highlight(tmp_code, lexer, formatter)
232
233 return html_code
234
235

◆ config()

searx.webapp.config ( )
Return configuration in JSON format.

Definition at line 1263 of file webapp.py.

1263def config():
1264 """Return configuration in JSON format."""
1265 _engines = []
1266 for name, engine in engines.items():
1267 if not sxng_request.preferences.validate_token(engine):
1268 continue
1269
1270 _languages = engine.traits.languages.keys()
1271 _engines.append(
1272 {
1273 'name': name,
1274 'categories': engine.categories,
1275 'shortcut': engine.shortcut,
1276 'enabled': not engine.disabled,
1277 'paging': engine.paging,
1278 'language_support': engine.language_support,
1279 'languages': list(_languages),
1280 'regions': list(engine.traits.regions.keys()),
1281 'safesearch': engine.safesearch,
1282 'time_range_support': engine.time_range_support,
1283 'timeout': engine.timeout,
1284 }
1285 )
1286
1287 _plugins = []
1288 for _ in searx.plugins.STORAGE:
1289 _plugins.append({'name': _.id, 'enabled': _.active})
1290
1291 _limiter_cfg = limiter.get_cfg()
1292
1293 return jsonify(
1294 {
1295 'categories': list(categories.keys()),
1296 'engines': _engines,
1297 'plugins': _plugins,
1298 'instance_name': settings['general']['instance_name'],
1299 'locales': LOCALE_NAMES,
1300 'default_locale': settings['ui']['default_locale'],
1301 'autocomplete': settings['search']['autocomplete'],
1302 'safe_search': settings['search']['safe_search'],
1303 'default_theme': settings['ui']['default_theme'],
1304 'version': VERSION_STRING,
1305 'brand': {
1306 'PRIVACYPOLICY_URL': get_setting('general.privacypolicy_url'),
1307 'CONTACT_URL': get_setting('general.contact_url'),
1308 'GIT_URL': GIT_URL,
1309 'GIT_BRANCH': GIT_BRANCH,
1310 'DOCS_URL': get_setting('brand.docs_url'),
1311 },
1312 'limiter': {
1313 'enabled': limiter.is_installed(),
1314 'botdetection.ip_limit.link_token': _limiter_cfg.get('botdetection.ip_limit.link_token'),
1315 'botdetection.ip_lists.pass_searxng_org': _limiter_cfg.get('botdetection.ip_lists.pass_searxng_org'),
1316 },
1317 'doi_resolvers': list(settings['doi_resolvers'].keys()),
1318 'default_doi_resolver': settings['default_doi_resolver'],
1319 'public_instance': settings['server']['public_instance'],
1320 }
1321 )
1322
1323
1324@app.errorhandler(404)

References searx.get_setting().

+ Here is the call graph for this function:

◆ custom_url_for()

searx.webapp.custom_url_for ( str endpoint,
** values )

Definition at line 243 of file webapp.py.

243def custom_url_for(endpoint: str, **values):
244 suffix = ""
245 if endpoint == 'static' and values.get('filename'):
246 file_hash = static_files.get(values['filename'])
247 if not file_hash:
248 # try file in the current theme
249 theme_name = sxng_request.preferences.get_value('theme')
250 filename_with_theme = "themes/{}/{}".format(theme_name, values['filename'])
251 file_hash = static_files.get(filename_with_theme)
252 if file_hash:
253 values['filename'] = filename_with_theme
254 if get_setting('ui.static_use_hash') and file_hash:
255 suffix = "?" + file_hash
256 if endpoint == 'info' and 'locale' not in values:
257 locale = sxng_request.preferences.get_value('locale')
258 if infopage.INFO_PAGES.get_page(values['pagename'], locale) is None:
259 locale = infopage.INFO_PAGES.locale_default
260 values['locale'] = locale
261 return url_for(endpoint, **values) + suffix
262
263

References searx.get_setting().

Referenced by about(), get_client_settings(), and render().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ engine_descriptions()

searx.webapp.engine_descriptions ( )

Definition at line 1086 of file webapp.py.

1086def engine_descriptions():
1087 locale = get_locale().split('_')[0]
1088 result = ENGINE_DESCRIPTIONS['en'].copy()
1089 if locale != 'en':
1090 for engine, description in ENGINE_DESCRIPTIONS.get(locale, {}).items():
1091 result[engine] = description
1092 for engine, description in result.items():
1093 if len(description) == 2 and description[1] == 'ref':
1094 ref_engine, ref_lang = description[0].split(':')
1095 description = ENGINE_DESCRIPTIONS[ref_lang][ref_engine]
1096 if isinstance(description, str):
1097 description = [description, 'wikipedia']
1098 result[engine] = description
1099
1100 # overwrite by about:description (from settings)
1101 for engine_name, engine_mod in engines.items():
1102 descr = getattr(engine_mod, 'about', {}).get('description', None)
1103 if descr is not None:
1104 result[engine_name] = [descr, "SearXNG config"]
1105
1106 return jsonify(result)
1107
1108
1109@app.route('/stats', methods=['GET'])

References get_locale().

+ Here is the call graph for this function:

◆ favicon()

searx.webapp.favicon ( )

Definition at line 1245 of file webapp.py.

1245def favicon():
1246 theme = sxng_request.preferences.get_value("theme")
1247 return send_from_directory(
1248 os.path.join(app.root_path, settings['ui']['static_path'], 'themes', theme, 'img'), # type: ignore
1249 'favicon.png',
1250 mimetype='image/vnd.microsoft.icon',
1251 )
1252
1253
1254@app.route('/clear_cookies')

◆ get_client_settings()

searx.webapp.get_client_settings ( )

Definition at line 348 of file webapp.py.

348def get_client_settings():
349 req_pref = sxng_request.preferences
350 return {
351 'autocomplete': req_pref.get_value('autocomplete'),
352 'autocomplete_min': get_setting('search.autocomplete_min'),
353 'method': req_pref.get_value('method'),
354 'infinite_scroll': req_pref.get_value('infinite_scroll'),
355 'translations': get_translations(),
356 'search_on_category_select': req_pref.get_value('search_on_category_select'),
357 'hotkeys': req_pref.get_value('hotkeys'),
358 'url_formatting': req_pref.get_value('url_formatting'),
359 'theme_static_path': custom_url_for('static', filename='themes/simple'),
360 'results_on_new_tab': req_pref.get_value('results_on_new_tab'),
361 'favicon_resolver': req_pref.get_value('favicon_resolver'),
362 'advanced_search': req_pref.get_value('advanced_search'),
363 'query_in_title': req_pref.get_value('query_in_title'),
364 'safesearch': str(req_pref.get_value('safesearch')),
365 'theme': req_pref.get_value('theme'),
366 'doi_resolver': get_doi_resolver(),
367 }
368
369

References custom_url_for(), searx.get_setting(), and get_translations().

Referenced by render().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ get_enabled_categories()

searx.webapp.get_enabled_categories ( typing.Iterable[str] category_names)
The categories in ``category_names```for which there is no active engine
are filtered out and a reduced list is returned.

Definition at line 322 of file webapp.py.

322def get_enabled_categories(category_names: typing.Iterable[str]):
323 """The categories in ``category_names```for which there is no active engine
324 are filtered out and a reduced list is returned."""
325
326 enabled_engines = [item[0] for item in sxng_request.preferences.engines.get_enabled()]
327 enabled_categories = set()
328 for engine_name in enabled_engines:
329 enabled_categories.update(engines[engine_name].categories)
330 return [x for x in category_names if x in enabled_categories]
331
332

Referenced by render().

+ Here is the caller graph for this function:

◆ get_locale()

searx.webapp.get_locale ( )

Definition at line 162 of file webapp.py.

162def get_locale():
163 locale = localeselector()
164 logger.debug("%s uses locale `%s`", urllib.parse.quote(sxng_request.url), locale)
165 return locale
166
167

Referenced by engine_descriptions().

+ Here is the caller graph for this function:

◆ get_pretty_url()

searx.webapp.get_pretty_url ( urllib.parse.ParseResult parsed_url)

Definition at line 333 of file webapp.py.

333def get_pretty_url(parsed_url: urllib.parse.ParseResult):
334 url_formatting_pref = sxng_request.preferences.get_value('url_formatting')
335
336 if url_formatting_pref == 'full':
337 return [parsed_url.geturl()]
338
339 if url_formatting_pref == 'host':
340 return [parsed_url.netloc]
341
342 path = parsed_url.path
343 path = path[:-1] if len(path) > 0 and path[-1] == '/' else path
344 path = unquote(path.replace("/", " › "))
345 return [parsed_url.scheme + "://" + parsed_url.netloc, path]
346
347

◆ get_result_template()

searx.webapp.get_result_template ( str theme_name,
str template_name )

Definition at line 236 of file webapp.py.

236def get_result_template(theme_name: str, template_name: str):
237 themed_path = theme_name + '/result_templates/' + template_name
238 if themed_path in result_templates:
239 return themed_path
240 return 'result_templates/' + template_name
241
242

◆ get_translations()

searx.webapp.get_translations ( )

Definition at line 311 of file webapp.py.

311def get_translations():
312 return {
313 # when there is autocompletion
314 'no_item_found': gettext('No item found'),
315 # /preferences: the source of the engine description (wikipedata, wikidata, website)
316 'Source': gettext('Source'),
317 # infinite scroll
318 'error_loading_next_page': gettext('Error loading the next page'),
319 }
320
321

Referenced by get_client_settings().

+ Here is the caller graph for this function:

◆ health()

searx.webapp.health ( )

Definition at line 592 of file webapp.py.

592def health():
593 return Response('OK', mimetype='text/plain')
594
595
596@app.route('/client<token>.css', methods=['GET', 'POST'])

◆ image_proxify()

searx.webapp.image_proxify ( str url)

Definition at line 282 of file webapp.py.

282def image_proxify(url: str):
283 if not url:
284 return url
285
286 if url.startswith('//'):
287 url = 'https:' + url
288
289 if not sxng_request.preferences.get_value('image_proxy'):
290 return url
291
292 if url.startswith('data:image/'):
293 # 50 is an arbitrary number to get only the beginning of the image.
294 partial_base64 = url[len('data:image/') : 50].split(';')
295 if (
296 len(partial_base64) == 2
297 and partial_base64[0] in ['gif', 'png', 'jpeg', 'pjpeg', 'webp', 'tiff', 'bmp']
298 and partial_base64[1].startswith('base64,')
299 ):
300 return url
301 return None
302
303 if settings['result_proxy']['url']:
304 return morty_proxify(url)
305
306 h = new_hmac(settings['server']['secret_key'], url.encode())
307
308 return '{0}?{1}'.format(url_for('image_proxy'), urlencode(dict(url=url.encode(), h=h)))
309
310

References morty_proxify().

+ Here is the call graph for this function:

◆ image_proxy()

searx.webapp.image_proxy ( )

Definition at line 1013 of file webapp.py.

1013def image_proxy():
1014 # pylint: disable=too-many-return-statements, too-many-branches
1015
1016 url = sxng_request.args.get('url')
1017 if not url:
1018 return '', 400
1019
1020 if not is_hmac_of(settings['server']['secret_key'], url.encode(), sxng_request.args.get('h', '')):
1021 return '', 400
1022
1023 maximum_size = 5 * 1024 * 1024
1024 forward_resp = False
1025 resp = None
1026 try:
1027 request_headers = {
1028 'User-Agent': gen_useragent(),
1029 'Accept': 'image/webp,*/*',
1030 'Accept-Encoding': 'gzip, deflate',
1031 'Sec-GPC': '1',
1032 'DNT': '1',
1033 }
1034 set_context_network_name('image_proxy')
1035 resp, stream = http_stream(method='GET', url=url, headers=request_headers, allow_redirects=True)
1036 content_length = resp.headers.get('Content-Length')
1037 if content_length and content_length.isdigit() and int(content_length) > maximum_size:
1038 return 'Max size', 400
1039
1040 if resp.status_code != 200:
1041 logger.debug('image-proxy: wrong response code: %i', resp.status_code)
1042 if resp.status_code >= 400:
1043 return '', resp.status_code
1044 return '', 400
1045
1046 if not resp.headers.get('Content-Type', '').startswith('image/') and not resp.headers.get(
1047 'Content-Type', ''
1048 ).startswith('binary/octet-stream'):
1049 logger.debug('image-proxy: wrong content-type: %s', resp.headers.get('Content-Type', ''))
1050 return '', 400
1051
1052 forward_resp = True
1053 except httpx.HTTPError:
1054 logger.exception('HTTP error')
1055 return '', 400
1056 finally:
1057 if resp and not forward_resp:
1058 # the code is about to return an HTTP 400 error to the browser
1059 # we make sure to close the response between searxng and the HTTP server
1060 try:
1061 resp.close()
1062 except httpx.HTTPError:
1063 logger.exception('HTTP error on closing')
1064
1065 def close_stream():
1066 nonlocal resp, stream
1067 try:
1068 if resp:
1069 resp.close()
1070 del resp
1071 del stream
1072 except httpx.HTTPError as e:
1073 logger.debug('Exception while closing response', e)
1074
1075 try:
1076 headers = dict_subset(resp.headers, {'Content-Type', 'Content-Encoding', 'Content-Length', 'Length'})
1077 response = Response(stream, mimetype=resp.headers['Content-Type'], headers=headers, direct_passthrough=True)
1078 response.call_on_close(close_stream)
1079 return response
1080 except httpx.HTTPError:
1081 close_stream()
1082 return '', 400
1083
1084
1085@app.route('/engine_descriptions.json', methods=['GET'])

◆ index()

searx.webapp.index ( )
Render index page.

Definition at line 574 of file webapp.py.

574def index():
575 """Render index page."""
576
577 # redirect to search if there's a query in the request
578 if sxng_request.form.get('q'):
579 query = ('?' + sxng_request.query_string.decode()) if sxng_request.query_string else ''
580 return redirect(url_for('search') + query, 308)
581
582 return render(
583 # fmt: off
584 'index.html',
585 selected_categories=get_selected_categories(sxng_request.preferences, sxng_request.form),
586 current_locale = sxng_request.preferences.get_value("locale"),
587 # fmt: on
588 )
589
590
591@app.route('/healthz', methods=['GET'])

References render().

+ Here is the call graph for this function:

◆ index_error()

searx.webapp.index_error ( str output_format,
str error_message )

Definition at line 544 of file webapp.py.

544def index_error(output_format: str, error_message: str):
545 if output_format == 'json':
546 return Response(json.dumps({'error': error_message}), mimetype='application/json')
547 if output_format == 'csv':
548 response = Response('', mimetype='application/csv')
549 cont_disp = 'attachment;Filename=searx.csv'
550 response.headers.add('Content-Disposition', cont_disp)
551 return response
552
553 if output_format == 'rss':
554 response_rss = render(
555 'opensearch_response_rss.xml',
556 results=[],
557 q=sxng_request.form['q'] if 'q' in sxng_request.form else '',
558 number_of_results=0,
559 error_message=error_message,
560 )
561 return Response(response_rss, mimetype='text/xml')
562
563 # html
564 sxng_request.errors.append(gettext('search error'))
565 return render(
566 # fmt: off
567 'index.html',
568 selected_categories=get_selected_categories(sxng_request.preferences, sxng_request.form),
569 # fmt: on
570 )
571
572
573@app.route('/', methods=['GET', 'POST'])

References render().

Referenced by search().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ info()

searx.webapp.info ( pagename,
locale )
Render page of online user documentation

Definition at line 787 of file webapp.py.

787def info(pagename, locale):
788 """Render page of online user documentation"""
789 page = infopage.INFO_PAGES.get_page(pagename, locale)
790 if page is None:
791 flask.abort(404)
792
793 user_locale = sxng_request.preferences.get_value('locale')
794 return render(
795 'info.html',
796 all_pages=infopage.INFO_PAGES.iter_pages(user_locale, fallback_to_default=True),
797 active_page=page,
798 active_pagename=pagename,
799 )
800
801
802@app.route('/autocompleter', methods=['GET', 'POST'])

References render().

+ Here is the call graph for this function:

◆ init()

searx.webapp.init ( )

Definition at line 1398 of file webapp.py.

1398def init():
1399
1400 if searx.sxng_debug or app.debug:
1401 app.debug = True
1402 searx.sxng_debug = True
1403
1404 # check secret_key in production
1405
1406 if not app.debug and get_setting("server.secret_key") == 'ultrasecretkey':
1407 logger.error("server.secret_key is not changed. Please use something else instead of ultrasecretkey.")
1408 sys.exit(1)
1409
1410 # When automatic reloading is activated stop Flask from initialising twice.
1411 # - https://github.com/pallets/flask/issues/5307#issuecomment-1774646119
1412 # - https://stackoverflow.com/a/25504196
1413
1414 reloader_active = is_werkzeug_reload_active()
1415 werkzeug_run_main = is_running_from_reloader()
1416
1417 if reloader_active and not werkzeug_run_main:
1418 logger.info("in reloading mode and not in main loop, cancel the initialization")
1419 return
1420
1421 locales_initialize()
1422 redis_initialize()
1424
1425 metrics: bool = get_setting("general.enable_metrics") # type: ignore
1426 searx.search.initialize(enable_checker=True, check_network=True, enable_metrics=metrics)
1427
1428 limiter.initialize(app, settings)
1429 favicons.init()
1430
1431
initialize(app)
Definition __init__.py:108
initialize(settings_engines=None, enable_checker=False, check_network=False, enable_metrics=True)
Definition __init__.py:32

References searx.get_setting(), searx.plugins.initialize(), searx.search.initialize(), and is_werkzeug_reload_active().

+ Here is the call graph for this function:

◆ is_werkzeug_reload_active()

bool searx.webapp.is_werkzeug_reload_active ( )
Returns ``True`` if server is is launched by :ref:`werkzeug.serving` and
the ``use_reload`` argument was set to ``True``.  If this is the case, it
should be avoided that the server is initialized twice (:py:obj:`init`,
:py:obj:`run`).

.. _werkzeug.serving:
   https://werkzeug.palletsprojects.com/en/stable/serving/#werkzeug.serving.run_simple

Definition at line 1366 of file webapp.py.

1366def is_werkzeug_reload_active() -> bool:
1367 """Returns ``True`` if server is is launched by :ref:`werkzeug.serving` and
1368 the ``use_reload`` argument was set to ``True``. If this is the case, it
1369 should be avoided that the server is initialized twice (:py:obj:`init`,
1370 :py:obj:`run`).
1371
1372 .. _werkzeug.serving:
1373 https://werkzeug.palletsprojects.com/en/stable/serving/#werkzeug.serving.run_simple
1374 """
1375
1376 if "uwsgi" in sys.argv:
1377 # server was launched by uWSGI
1378 return False
1379
1380 # https://github.com/searxng/searxng/pull/1656#issuecomment-1214198941
1381 # https://github.com/searxng/searxng/pull/1616#issuecomment-1206137468
1382
1383 frames = inspect.stack()
1384
1385 if len(frames) > 1 and frames[-2].filename.endswith('flask/cli.py'):
1386 # server was launched by "flask run", is argument "--reload" set?
1387 if "--reload" in sys.argv or "--debug" in sys.argv:
1388 return True
1389
1390 elif frames[0].filename.endswith('searx/webapp.py'):
1391 # server was launched by "python -m searx.webapp" / see run()
1392 if searx.sxng_debug:
1393 return True
1394
1395 return False
1396
1397

Referenced by init().

+ Here is the caller graph for this function:

◆ morty_proxify()

searx.webapp.morty_proxify ( str url)

Definition at line 264 of file webapp.py.

264def morty_proxify(url: str):
265 if not url:
266 return url
267
268 if url.startswith('//'):
269 url = 'https:' + url
270
271 if not settings['result_proxy']['url']:
272 return url
273
274 url_params = dict(mortyurl=url)
275
276 if settings['result_proxy']['key']:
277 url_params['mortyhash'] = hmac.new(settings['result_proxy']['key'], url.encode(), hashlib.sha256).hexdigest()
278
279 return '{0}?{1}'.format(settings['result_proxy']['url'], urlencode(url_params))
280
281

Referenced by image_proxify().

+ Here is the caller graph for this function:

◆ opensearch()

searx.webapp.opensearch ( )

Definition at line 1228 of file webapp.py.

1228def opensearch():
1229 method = sxng_request.preferences.get_value('method')
1230 autocomplete = sxng_request.preferences.get_value('autocomplete')
1231
1232 # chrome/chromium only supports HTTP GET....
1233 if sxng_request.headers.get('User-Agent', '').lower().find('webkit') >= 0:
1234 method = 'GET'
1235
1236 if method not in ('POST', 'GET'):
1237 method = 'POST'
1238
1239 ret = render('opensearch.xml', opensearch_method=method, autocomplete=autocomplete)
1240 resp = Response(response=ret, status=200, mimetype="application/opensearchdescription+xml")
1241 return resp
1242
1243
1244@app.route('/favicon.ico')

References render().

+ Here is the call graph for this function:

◆ page_not_found()

searx.webapp.page_not_found ( _e)

Definition at line 1325 of file webapp.py.

1325def page_not_found(_e):
1326 return render('404.html'), 404
1327
1328

References render().

+ Here is the call graph for this function:

◆ post_request()

searx.webapp.post_request ( flask.Response response)

Definition at line 523 of file webapp.py.

523def post_request(response: flask.Response):
524 total_time = default_timer() - sxng_request.start_time
525 timings_all = [
526 'total;dur=' + str(round(total_time * 1000, 3)),
527 'render;dur=' + str(round(sxng_request.render_time * 1000, 3)),
528 ]
529 if len(sxng_request.timings) > 0:
530 timings = sorted(sxng_request.timings, key=lambda t: t.total)
531 timings_total = [
532 'total_' + str(i) + '_' + t.engine + ';dur=' + str(round(t.total * 1000, 3)) for i, t in enumerate(timings)
533 ]
534 timings_load = [
535 'load_' + str(i) + '_' + t.engine + ';dur=' + str(round(t.load * 1000, 3))
536 for i, t in enumerate(timings)
537 if t.load
538 ]
539 timings_all = timings_all + timings_total + timings_load
540 response.headers.add('Server-Timing', ', '.join(timings_all))
541 return response
542
543

◆ pre_request()

searx.webapp.pre_request ( )

Definition at line 451 of file webapp.py.

451def pre_request():
452 sxng_request.start_time = default_timer() # pylint: disable=assigning-non-slot
453 sxng_request.render_time = 0 # pylint: disable=assigning-non-slot
454 sxng_request.timings = [] # pylint: disable=assigning-non-slot
455 sxng_request.errors = [] # pylint: disable=assigning-non-slot
456
457 client_pref = ClientPref.from_http_request(sxng_request)
458 # pylint: disable=redefined-outer-name
459 preferences = Preferences(themes, list(categories.keys()), engines, searx.plugins.STORAGE, client_pref)
460
461 user_agent = sxng_request.headers.get('User-Agent', '').lower()
462 if 'webkit' in user_agent and 'android' in user_agent:
463 preferences.key_value_settings['method'].value = 'GET'
464 sxng_request.preferences = preferences # pylint: disable=assigning-non-slot
465
466 try:
467 preferences.parse_dict(sxng_request.cookies)
468
469 except Exception as e: # pylint: disable=broad-except
470 logger.exception(e, exc_info=True)
471 sxng_request.errors.append(gettext('Invalid settings, please edit your preferences'))
472
473 # merge GET, POST vars
474 # HINT request.form is of type werkzeug.datastructures.ImmutableMultiDict
475 sxng_request.form = dict(sxng_request.form.items()) # type: ignore
476 for k, v in sxng_request.args.items():
477 if k not in sxng_request.form:
478 sxng_request.form[k] = v
479
480 if sxng_request.form.get('preferences'):
481 preferences.parse_encoded_data(sxng_request.form['preferences'])
482 else:
483 try:
484 preferences.parse_dict(sxng_request.form)
485 except Exception as e: # pylint: disable=broad-except
486 logger.exception(e, exc_info=True)
487 sxng_request.errors.append(gettext('Invalid settings'))
488
489 # language is defined neither in settings nor in preferences
490 # use browser headers
491 if not preferences.get_value("language"):
492 language = _get_browser_language(sxng_request, settings['search']['languages'])
493 preferences.parse_dict({"language": language})
494 logger.debug('set language %s (from browser)', preferences.get_value("language"))
495
496 # UI locale is defined neither in settings nor in preferences
497 # use browser headers
498 if not preferences.get_value("locale"):
499 locale = _get_browser_language(sxng_request, LOCALE_NAMES.keys())
500 preferences.parse_dict({"locale": locale})
501 logger.debug('set locale %s (from browser)', preferences.get_value("locale"))
502
503 # request.user_plugins
504 sxng_request.user_plugins = [] # pylint: disable=assigning-non-slot
505 allowed_plugins = preferences.plugins.get_enabled()
506 disabled_plugins = preferences.plugins.get_disabled()
507 for plugin in searx.plugins.STORAGE:
508 if (plugin.id not in disabled_plugins) or plugin.id in allowed_plugins:
509 sxng_request.user_plugins.append(plugin.id)
510
511
512@app.after_request

References _get_browser_language().

+ Here is the call graph for this function:

◆ preferences()

searx.webapp.preferences ( )
Render preferences page && save user preferences

Definition at line 852 of file webapp.py.

852def preferences():
853 """Render preferences page && save user preferences"""
854
855 # pylint: disable=too-many-locals, too-many-return-statements, too-many-branches
856 # pylint: disable=too-many-statements
857
858 # save preferences using the link the /preferences?preferences=...
859 if sxng_request.args.get('preferences') or sxng_request.form.get('preferences'):
860 resp = make_response(redirect(url_for('index', _external=True)))
861 return sxng_request.preferences.save(resp)
862
863 # save preferences
864 if sxng_request.method == 'POST':
865 resp = make_response(redirect(url_for('index', _external=True)))
866 try:
867 sxng_request.preferences.parse_form(sxng_request.form)
868 except ValidationException:
869 sxng_request.errors.append(gettext('Invalid settings, please edit your preferences'))
870 return resp
871 return sxng_request.preferences.save(resp)
872
873 # render preferences
874 image_proxy = sxng_request.preferences.get_value('image_proxy') # pylint: disable=redefined-outer-name
875 disabled_engines = sxng_request.preferences.engines.get_disabled()
876 allowed_plugins = sxng_request.preferences.plugins.get_enabled()
877
878 # stats for preferences page
879 filtered_engines = dict(filter(lambda kv: sxng_request.preferences.validate_token(kv[1]), engines.items()))
880
881 engines_by_category = {}
882
883 for c in categories: # pylint: disable=consider-using-dict-items
884 engines_by_category[c] = [e for e in categories[c] if e.name in filtered_engines]
885 # sort the engines alphabetically since the order in settings.yml is meaningless.
886 list.sort(engines_by_category[c], key=lambda e: e.name)
887
888 # get first element [0], the engine time,
889 # and then the second element [1] : the time (the first one is the label)
890 stats = {} # pylint: disable=redefined-outer-name
891 max_rate95 = 0
892 for _, e in filtered_engines.items():
893 h = histogram('engine', e.name, 'time', 'total')
894 median = round(h.percentage(50), 1) if h.count > 0 else None
895 rate80 = round(h.percentage(80), 1) if h.count > 0 else None
896 rate95 = round(h.percentage(95), 1) if h.count > 0 else None
897
898 max_rate95 = max(max_rate95, rate95 or 0)
899
900 result_count_sum = histogram('engine', e.name, 'result', 'count').sum
901 successful_count = counter('engine', e.name, 'search', 'count', 'successful')
902 result_count = int(result_count_sum / float(successful_count)) if successful_count else 0
903
904 stats[e.name] = {
905 'time': median,
906 'rate80': rate80,
907 'rate95': rate95,
908 'warn_timeout': e.timeout > settings['outgoing']['request_timeout'],
909 'supports_selected_language': e.traits.is_locale_supported(
910 str(sxng_request.preferences.get_value('language') or 'all')
911 ),
912 'result_count': result_count,
913 }
914 # end of stats
915
916 # reliabilities
917 reliabilities = {}
918 engine_errors = get_engine_errors(filtered_engines)
919 checker_results = checker_get_result()
920 checker_results = (
921 checker_results['engines'] if checker_results['status'] == 'ok' and 'engines' in checker_results else {}
922 )
923 for _, e in filtered_engines.items():
924 checker_result = checker_results.get(e.name, {})
925 checker_success = checker_result.get('success', True)
926 errors = engine_errors.get(e.name) or []
927 if counter('engine', e.name, 'search', 'count', 'sent') == 0:
928 # no request
929 reliability = None
930 elif checker_success and not errors:
931 reliability = 100
932 elif 'simple' in checker_result.get('errors', {}):
933 # the basic (simple) test doesn't work: the engine is broken according to the checker
934 # even if there is no exception
935 reliability = 0
936 else:
937 # pylint: disable=consider-using-generator
938 reliability = 100 - sum([error['percentage'] for error in errors if not error.get('secondary')])
939
940 reliabilities[e.name] = {
941 'reliability': reliability,
942 'errors': [],
943 'checker': checker_results.get(e.name, {}).get('errors', {}).keys(),
944 }
945 # keep the order of the list checker_results[e.name]['errors'] and deduplicate.
946 # the first element has the highest percentage rate.
947 reliabilities_errors = []
948 for error in errors:
949 error_user_text = None
950 if error.get('secondary') or 'exception_classname' not in error:
951 continue
952 error_user_text = exception_classname_to_text.get(error.get('exception_classname'))
953 if not error:
954 error_user_text = exception_classname_to_text[None]
955 if error_user_text not in reliabilities_errors:
956 reliabilities_errors.append(error_user_text)
957 reliabilities[e.name]['errors'] = reliabilities_errors
958
959 # supports
960 supports = {}
961 for _, e in filtered_engines.items():
962 supports_selected_language = e.traits.is_locale_supported(
963 str(sxng_request.preferences.get_value('language') or 'all')
964 )
965 safesearch = e.safesearch
966 time_range_support = e.time_range_support
967 for checker_test_name in checker_results.get(e.name, {}).get('errors', {}):
968 if supports_selected_language and checker_test_name.startswith('lang_'):
969 supports_selected_language = '?'
970 elif safesearch and checker_test_name == 'safesearch':
971 safesearch = '?'
972 elif time_range_support and checker_test_name == 'time_range':
973 time_range_support = '?'
974 supports[e.name] = {
975 'supports_selected_language': supports_selected_language,
976 'safesearch': safesearch,
977 'time_range_support': time_range_support,
978 }
979
980 return render(
981 # fmt: off
982 'preferences.html',
983 preferences = True,
984 selected_categories = get_selected_categories(sxng_request.preferences, sxng_request.form),
985 locales = LOCALE_NAMES,
986 current_locale = sxng_request.preferences.get_value("locale"),
987 image_proxy = image_proxy,
988 engines_by_category = engines_by_category,
989 stats = stats,
990 max_rate95 = max_rate95,
991 reliabilities = reliabilities,
992 supports = supports,
993 answer_storage = searx.answerers.STORAGE.info,
994 disabled_engines = disabled_engines,
995 autocomplete_backends = autocomplete_backends,
996 favicon_resolver_names = favicons.proxy.CFG.resolver_map.keys(),
997 shortcuts = {y: x for x, y in engine_shortcuts.items()},
998 themes = themes,
999 plugins_storage = searx.plugins.STORAGE.info,
1000 current_doi_resolver = get_doi_resolver(),
1001 allowed_plugins = allowed_plugins,
1002 preferences_url_params = sxng_request.preferences.get_as_url_params(),
1003 locked_preferences = get_setting("preferences.lock", []),
1004 doi_resolvers = get_setting("doi_resolvers", {}),
1005 # fmt: on
1006 )
1007
1008

References searx.get_setting(), and render().

+ Here is the call graph for this function:

◆ render()

searx.webapp.render ( str template_name,
** kwargs )

Definition at line 370 of file webapp.py.

370def render(template_name: str, **kwargs):
371 # values from the preferences
372 # pylint: disable=too-many-statements
373 client_settings = get_client_settings()
374 kwargs['client_settings'] = str(
375 base64.b64encode(
376 bytes(
377 json.dumps(client_settings),
378 encoding='utf-8',
379 )
380 ),
381 encoding='utf-8',
382 )
383 kwargs['preferences'] = sxng_request.preferences
384 kwargs.update(client_settings)
385
386 # values from the HTTP requests
387 kwargs['endpoint'] = 'results' if 'q' in kwargs else sxng_request.endpoint
388 kwargs['cookies'] = sxng_request.cookies
389 kwargs['errors'] = sxng_request.errors
390 kwargs['link_token'] = link_token.get_token()
391
392 kwargs['categories_as_tabs'] = list(settings['categories_as_tabs'].keys())
393 kwargs['categories'] = get_enabled_categories(settings['categories_as_tabs'].keys())
394 kwargs['DEFAULT_CATEGORY'] = DEFAULT_CATEGORY
395
396 # i18n
397 kwargs['sxng_locales'] = [l for l in sxng_locales if l[0] in settings['search']['languages']]
398
399 locale = sxng_request.preferences.get_value('locale')
400 kwargs['locale_rfc5646'] = _get_locale_rfc5646(locale)
401
402 if locale in RTL_LOCALES and 'rtl' not in kwargs:
403 kwargs['rtl'] = True
404
405 if 'current_language' not in kwargs:
406 kwargs['current_language'] = parse_lang(sxng_request.preferences, {}, RawTextQuery('', []))
407
408 # values from settings
409 kwargs['search_formats'] = [x for x in settings['search']['formats'] if x != 'html']
410 kwargs['instance_name'] = get_setting('general.instance_name')
411 kwargs['searx_version'] = VERSION_STRING
412 kwargs['searx_git_url'] = GIT_URL
413 kwargs['enable_metrics'] = get_setting('general.enable_metrics')
414 kwargs['get_setting'] = get_setting
415 kwargs['get_pretty_url'] = get_pretty_url
416
417 # values from settings: donation_url
418 donation_url = get_setting('general.donation_url')
419 if donation_url is True:
420 donation_url = custom_url_for('info', pagename='donate')
421 kwargs['donation_url'] = donation_url
422
423 # helpers to create links to other pages
424 kwargs['url_for'] = custom_url_for # override url_for function in templates
425 kwargs['image_proxify'] = image_proxify
426 kwargs['favicon_url'] = favicons.favicon_url
427 kwargs['proxify'] = morty_proxify if settings['result_proxy']['url'] is not None else None
428 kwargs['proxify_results'] = settings['result_proxy']['proxify_results']
429 kwargs['cache_url'] = settings['ui']['cache_url']
430 kwargs['get_result_template'] = get_result_template
431 kwargs['opensearch_url'] = (
432 url_for('opensearch')
433 + '?'
434 + urlencode(
435 {
436 'method': sxng_request.preferences.get_value('method'),
437 'autocomplete': sxng_request.preferences.get_value('autocomplete'),
438 }
439 )
440 )
441 kwargs['urlparse'] = urlparse
442
443 start_time = default_timer()
444 result = render_template('{}/{}'.format(kwargs['theme'], template_name), **kwargs)
445 sxng_request.render_time += default_timer() - start_time # pylint: disable=assigning-non-slot
446
447 return result
448
449
450@app.before_request

References _get_locale_rfc5646(), custom_url_for(), get_client_settings(), get_enabled_categories(), and searx.get_setting().

Referenced by index(), index_error(), info(), opensearch(), page_not_found(), preferences(), search(), and stats().

+ Here is the call graph for this function:
+ Here is the caller graph for this function:

◆ robots()

searx.webapp.robots ( )

Definition at line 1214 of file webapp.py.

1214def robots():
1215 return Response(
1216 """User-agent: *
1217Allow: /info/en/about
1218Disallow: /stats
1219Disallow: /image_proxy
1220Disallow: /preferences
1221Disallow: /*?*q=*
1222""",
1223 mimetype='text/plain',
1224 )
1225
1226
1227@app.route('/opensearch.xml', methods=['GET'])

◆ rss_xsl()

searx.webapp.rss_xsl ( )

Definition at line 603 of file webapp.py.

603def rss_xsl():
604 return render_template(
605 f"{sxng_request.preferences.get_value('theme')}/rss.xsl",
606 url_for=custom_url_for,
607 )
608
609
610@app.route('/search', methods=['GET', 'POST'])

◆ run()

searx.webapp.run ( )
Runs the application on a local development server.

This run method is only called when SearXNG is started via ``__main__``::

    python -m searx.webapp

Do not use :ref:`run() <flask.Flask.run>` in a production setting.  It is
not intended to meet security and performance requirements for a production
server.

It is not recommended to use this function for development with automatic
reloading as this is badly supported.  Instead you should be using the flask
command line script’s run support::

    flask --app searx.webapp run --debug --reload --host 127.0.0.1 --port 8888

.. _Flask.run: https://flask.palletsprojects.com/en/stable/api/#flask.Flask.run

Definition at line 1329 of file webapp.py.

1329def run():
1330 """Runs the application on a local development server.
1331
1332 This run method is only called when SearXNG is started via ``__main__``::
1333
1334 python -m searx.webapp
1335
1336 Do not use :ref:`run() <flask.Flask.run>` in a production setting. It is
1337 not intended to meet security and performance requirements for a production
1338 server.
1339
1340 It is not recommended to use this function for development with automatic
1341 reloading as this is badly supported. Instead you should be using the flask
1342 command line script’s run support::
1343
1344 flask --app searx.webapp run --debug --reload --host 127.0.0.1 --port 8888
1345
1346 .. _Flask.run: https://flask.palletsprojects.com/en/stable/api/#flask.Flask.run
1347 """
1348
1349 host: str = get_setting("server.bind_address") # type: ignore
1350 port: int = get_setting("server.port") # type: ignore
1351
1352 if searx.sxng_debug:
1353 logger.debug("run local development server (DEBUG) on %s:%s", host, port)
1354 app.run(
1355 debug=True,
1356 port=port,
1357 host=host,
1358 threaded=True,
1359 extra_files=[DEFAULT_SETTINGS_FILE],
1360 )
1361 else:
1362 logger.debug("run local development server on %s:%s", host, port)
1363 app.run(port=port, host=host, threaded=True)
1364
1365

References searx.get_setting().

+ Here is the call graph for this function:

◆ search()

searx.webapp.search ( )
Search query in q and return results.

Supported outputs: html, json, csv, rss.

Definition at line 611 of file webapp.py.

611def search():
612 """Search query in q and return results.
613
614 Supported outputs: html, json, csv, rss.
615 """
616 # pylint: disable=too-many-locals, too-many-return-statements, too-many-branches
617 # pylint: disable=too-many-statements
618
619 # output_format
620 output_format = sxng_request.form.get('format', 'html')
621 if output_format not in OUTPUT_FORMATS:
622 output_format = 'html'
623
624 if output_format not in settings['search']['formats']:
625 flask.abort(403)
626
627 # check if there is query (not None and not an empty string)
628 if not sxng_request.form.get('q'):
629 if output_format == 'html':
630 return render(
631 # fmt: off
632 'index.html',
633 selected_categories=get_selected_categories(sxng_request.preferences, sxng_request.form),
634 # fmt: on
635 )
636 return index_error(output_format, 'No query'), 400
637
638 # search
639 search_query = None
640 raw_text_query = None
641 result_container = None
642 try:
643 search_query, raw_text_query, _, _, selected_locale = get_search_query_from_webapp(
644 sxng_request.preferences, sxng_request.form
645 )
646 search_obj = searx.search.SearchWithPlugins(search_query, sxng_request, sxng_request.user_plugins)
647 result_container = search_obj.search()
648
649 except SearxParameterException as e:
650 logger.exception('search error: SearxParameterException')
651 return index_error(output_format, e.message), 400
652 except Exception as e: # pylint: disable=broad-except
653 logger.exception(e, exc_info=True)
654 return index_error(output_format, gettext('search error')), 500
655
656 # 1. check if the result is a redirect for an external bang
657 if result_container.redirect_url:
658 return redirect(result_container.redirect_url)
659
660 # 2. add Server-Timing header for measuring performance characteristics of
661 # web applications
662 sxng_request.timings = result_container.get_timings() # pylint: disable=assigning-non-slot
663
664 # 3. formats without a template
665
666 if output_format == 'json':
667
668 response = webutils.get_json_response(search_query, result_container)
669 return Response(response, mimetype='application/json')
670
671 if output_format == 'csv':
672
673 csv = webutils.CSVWriter(StringIO())
674 webutils.write_csv_response(csv, result_container)
675 csv.stream.seek(0)
676
677 response = Response(csv.stream.read(), mimetype='application/csv')
678 cont_disp = 'attachment;Filename=searx_-_{0}.csv'.format(search_query.query)
679 response.headers.add('Content-Disposition', cont_disp)
680 return response
681
682 # 4. formats rendered by a template / RSS & HTML
683
684 current_template = None
685 previous_result = None
686
687 results = result_container.get_ordered_results()
688
689 if search_query.redirect_to_first_result and results:
690 return redirect(results[0]['url'], 302)
691
692 for result in results:
693 if output_format == 'html':
694 if 'content' in result and result['content']:
695 result['content'] = highlight_content(escape(result['content'][:1024]), search_query.query)
696 if 'title' in result and result['title']:
697 result['title'] = highlight_content(escape(result['title'] or ''), search_query.query)
698
699 # set result['open_group'] = True when the template changes from the previous result
700 # set result['close_group'] = True when the template changes on the next result
701 if current_template != result.template:
702 result.open_group = True
703 if previous_result:
704 previous_result.close_group = True # pylint: disable=unsupported-assignment-operation
705 current_template = result.template
706 previous_result = result
707
708 if previous_result:
709 previous_result.close_group = True
710
711 # 4.a RSS
712
713 if output_format == 'rss':
714 response_rss = render(
715 'opensearch_response_rss.xml',
716 results=results,
717 q=sxng_request.form['q'],
718 number_of_results=result_container.number_of_results,
719 )
720 return Response(response_rss, mimetype='text/xml')
721
722 # 4.b HTML
723
724 # suggestions: use RawTextQuery to get the suggestion URLs with the same bang
725 suggestion_urls = list(
726 map(
727 lambda suggestion: {'url': raw_text_query.changeQuery(suggestion).getFullQuery(), 'title': suggestion},
728 result_container.suggestions,
729 )
730 )
731
732 correction_urls = list(
733 map(
734 lambda correction: {'url': raw_text_query.changeQuery(correction).getFullQuery(), 'title': correction},
735 result_container.corrections,
736 )
737 )
738
739 # engine_timings: get engine response times sorted from slowest to fastest
740 engine_timings = sorted(result_container.get_timings(), reverse=True, key=lambda e: e.total)
741 max_response_time = engine_timings[0].total if engine_timings else None
742 engine_timings_pairs = [(timing.engine, timing.total) for timing in engine_timings]
743
744 # search_query.lang contains the user choice (all, auto, en, ...)
745 # when the user choice is "auto", search.search_query.lang contains the detected language
746 # otherwise it is equals to search_query.lang
747 return render(
748 # fmt: off
749 'results.html',
750 results = results,
751 q=sxng_request.form['q'],
752 selected_categories = search_query.categories,
753 pageno = search_query.pageno,
754 time_range = search_query.time_range or '',
755 number_of_results = format_decimal(result_container.number_of_results),
756 suggestions = suggestion_urls,
757 answers = result_container.answers,
758 corrections = correction_urls,
759 infoboxes = result_container.infoboxes,
760 engine_data = result_container.engine_data,
761 paging = result_container.paging,
762 unresponsive_engines = webutils.get_translated_errors(
763 result_container.unresponsive_engines
764 ),
765 current_locale = sxng_request.preferences.get_value("locale"),
766 current_language = selected_locale,
767 search_language = match_locale(
768 search_obj.search_query.lang,
769 settings['search']['languages'],
770 fallback=sxng_request.preferences.get_value("language")
771 ),
772 timeout_limit = sxng_request.form.get('timeout_limit', None),
773 timings = engine_timings_pairs,
774 max_response_time = max_response_time
775 # fmt: on
776 )
777
778
779@app.route('/about', methods=['GET'])

References index_error(), and render().

+ Here is the call graph for this function:

◆ stats()

searx.webapp.stats ( )
Render engine statistics page.

Definition at line 1110 of file webapp.py.

1110def stats():
1111 """Render engine statistics page."""
1112 sort_order = sxng_request.args.get('sort', default='name', type=str)
1113 selected_engine_name = sxng_request.args.get('engine', default=None, type=str)
1114
1115 filtered_engines = dict(filter(lambda kv: sxng_request.preferences.validate_token(kv[1]), engines.items()))
1116 if selected_engine_name:
1117 if selected_engine_name not in filtered_engines:
1118 selected_engine_name = None
1119 else:
1120 filtered_engines = [selected_engine_name]
1121
1122 checker_results = checker_get_result()
1123 checker_results = (
1124 checker_results['engines'] if checker_results['status'] == 'ok' and 'engines' in checker_results else {}
1125 )
1126
1127 engine_stats = get_engines_stats(filtered_engines)
1128 engine_reliabilities = get_reliabilities(filtered_engines, checker_results)
1129
1130 if sort_order not in STATS_SORT_PARAMETERS:
1131 sort_order = 'name'
1132
1133 reverse, key_name, default_value = STATS_SORT_PARAMETERS[sort_order]
1134
1135 def get_key(engine_stat):
1136 reliability = engine_reliabilities.get(engine_stat['name'], {}).get('reliability', 0)
1137 reliability_order = 0 if reliability else 1
1138 if key_name == 'reliability':
1139 key = reliability
1140 reliability_order = 0
1141 else:
1142 key = engine_stat.get(key_name) or default_value
1143 if reverse:
1144 reliability_order = 1 - reliability_order
1145 return (reliability_order, key, engine_stat['name'])
1146
1147 technical_report = []
1148 for error in engine_reliabilities.get(selected_engine_name, {}).get('errors', []):
1149 technical_report.append(
1150 f"\
1151 Error: {error['exception_classname'] or error['log_message']} \
1152 Parameters: {error['log_parameters']} \
1153 File name: {error['filename'] }:{ error['line_no'] } \
1154 Error Function: {error['function']} \
1155 Code: {error['code']} \
1156 ".replace(
1157 ' ' * 12, ''
1158 ).strip()
1159 )
1160 technical_report = ' '.join(technical_report)
1161
1162 engine_stats['time'] = sorted(engine_stats['time'], reverse=reverse, key=get_key)
1163 return render(
1164 # fmt: off
1165 'stats.html',
1166 sort_order = sort_order,
1167 engine_stats = engine_stats,
1168 engine_reliabilities = engine_reliabilities,
1169 selected_engine_name = selected_engine_name,
1170 searx_git_branch = GIT_BRANCH,
1171 technical_report = technical_report,
1172 # fmt: on
1173 )
1174
1175
1176@app.route('/stats/errors', methods=['GET'])

References render().

+ Here is the call graph for this function:

◆ stats_checker()

searx.webapp.stats_checker ( )

Definition at line 1184 of file webapp.py.

1184def stats_checker():
1185 result = checker_get_result()
1186 return jsonify(result)
1187
1188
1189@app.route('/metrics')

◆ stats_errors()

searx.webapp.stats_errors ( )

Definition at line 1177 of file webapp.py.

1177def stats_errors():
1178 filtered_engines = dict(filter(lambda kv: sxng_request.preferences.validate_token(kv[1]), engines.items()))
1179 result = get_engine_errors(filtered_engines)
1180 return jsonify(result)
1181
1182
1183@app.route('/stats/checker', methods=['GET'])

◆ stats_open_metrics()

searx.webapp.stats_open_metrics ( )

Definition at line 1190 of file webapp.py.

1190def stats_open_metrics():
1191 password = settings['general'].get("open_metrics")
1192
1193 if not (settings['general'].get("enable_metrics") and password):
1194 return Response('open metrics is disabled', status=404, mimetype='text/plain')
1195
1196 if not sxng_request.authorization or sxng_request.authorization.password != password:
1197 return Response('access forbidden', status=401, mimetype='text/plain')
1198
1199 filtered_engines = dict(filter(lambda kv: sxng_request.preferences.validate_token(kv[1]), engines.items()))
1200
1201 checker_results = checker_get_result()
1202 checker_results = (
1203 checker_results['engines'] if checker_results['status'] == 'ok' and 'engines' in checker_results else {}
1204 )
1205
1206 engine_stats = get_engines_stats(filtered_engines)
1207 engine_reliabilities = get_reliabilities(filtered_engines, checker_results)
1208 metrics_text = openmetrics(engine_stats, engine_reliabilities)
1209
1210 return Response(metrics_text, mimetype='text/plain')
1211
1212
1213@app.route('/robots.txt', methods=['GET'])

Variable Documentation

◆ app

searx.webapp.app = Flask(__name__, static_folder=settings['ui']['static_path'], template_folder=templates_path)

Definition at line 153 of file webapp.py.

◆ application

searx.webapp.application = app

Definition at line 1432 of file webapp.py.

◆ babel

searx.webapp.babel = Babel(app, locale_selector=get_locale)

Definition at line 168 of file webapp.py.

◆ default_theme

searx.webapp.default_theme = settings['ui']['default_theme']

Definition at line 139 of file webapp.py.

◆ endpoint

searx.webapp.endpoint

Definition at line 1009 of file webapp.py.

◆ logger

searx.webapp.logger = logger.getChild('webapp')

Definition at line 129 of file webapp.py.

◆ lstrip_blocks

searx.webapp.lstrip_blocks

Definition at line 156 of file webapp.py.

◆ methods

searx.webapp.methods

Definition at line 1009 of file webapp.py.

◆ result_templates

searx.webapp.result_templates = get_result_templates(templates_path)

Definition at line 142 of file webapp.py.

◆ secret_key

searx.webapp.secret_key

Definition at line 159 of file webapp.py.

◆ static_files

searx.webapp.static_files = get_static_files(settings['ui']['static_path'])

Definition at line 135 of file webapp.py.

◆ STATS_SORT_PARAMETERS

dict searx.webapp.STATS_SORT_PARAMETERS
Initial value:
1= {
2 'name': (False, 'name', ''),
3 'score': (True, 'score_per_result', 0),
4 'result_count': (True, 'result_count', 0),
5 'time': (False, 'total', 0),
6 'reliability': (False, 'reliability', 100),
7}

Definition at line 144 of file webapp.py.

◆ templates_path

searx.webapp.templates_path = settings['ui']['templates_path']

Definition at line 140 of file webapp.py.

◆ themes

searx.webapp.themes = get_themes(templates_path)

Definition at line 141 of file webapp.py.

◆ trim_blocks

searx.webapp.trim_blocks

Definition at line 155 of file webapp.py.

◆ view_func

searx.webapp.view_func

Definition at line 1009 of file webapp.py.