[mod] remove option ui.static_use_hash (cache busting) (#5004)

Cache busting has caused serious problems for users in the past, here are two
examples:

- https://github.com/searxng/searxng/issues/4419
- https://github.com/searxng/searxng/issues/4481

And it makes development and deployment significantly more complex because it
binds the client side to the server side:

- https://github.com/searxng/searxng/pull/4466

In the light of a decoupled development of the WEB clients from the server side:

- https://github.com/searxng/searxng/pull/4988

is it appropriate to abandon this feature. In fact,  it has been ineffective
since #4436 anyway.

However, the benefit has always been questionable, since at best only a few kB
of data are saved (at least in the context of an image_proxy, the effect is below
the detection limit). Ultimately, the client is responsible for caching.

Related: https://github.com/searxng/searxng/issues?q=label%3A%22clear%20browser%20cache%22

Closes: https://github.com/searxng/searxng/pull/4466
Closes: https://github.com/searxng/searxng/issues/1326
Closes: https://github.com/searxng/searxng/issues/964

Signed-off-by: Markus Heiser <markus.heiser@darmarit.de>
This commit is contained in:
Markus Heiser 2025-07-11 16:53:36 +02:00 committed by GitHub
parent 9149175ff2
commit 574b285efa
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 62 additions and 104 deletions

View file

@ -29,8 +29,8 @@ up and maintained by the scripts from our :ref:`toolboxing`.
Reference architecture of a public SearXNG setup. Reference architecture of a public SearXNG setup.
The reference installation activates ``server.limiter``, ``server.image_proxy`` The reference installation activates ``server.limiter`` and
and ``ui.static_use_hash`` (:origin:`/etc/searxng/settings.yml ``server.image_proxy`` (:origin:`/etc/searxng/settings.yml
<utils/templates/etc/searxng/settings.yml>`) <utils/templates/etc/searxng/settings.yml>`)
.. literalinclude:: ../../utils/templates/etc/searxng/settings.yml .. literalinclude:: ../../utils/templates/etc/searxng/settings.yml

View file

@ -86,7 +86,6 @@ below. This setup:
- enables :ref:`limiter <limiter>` to protect against bots - enables :ref:`limiter <limiter>` to protect against bots
- enables :ref:`image proxy <image_proxy>` for better privacy - enables :ref:`image proxy <image_proxy>` for better privacy
- enables :ref:`cache busting <static_use_hash>` to save bandwidth
Modify the ``/etc/searxng/settings.yml`` to your needs: Modify the ``/etc/searxng/settings.yml`` to your needs:
@ -129,4 +128,3 @@ configuration file.
If everything works fine, hit ``[CTRL-C]`` to stop the *webapp* and disable the If everything works fine, hit ``[CTRL-C]`` to stop the *webapp* and disable the
debug option in ``settings.yml``. You can now exit SearXNG user bash session (enter exit debug option in ``settings.yml``. You can now exit SearXNG user bash session (enter exit
command twice). At this point SearXNG is not demonized; uwsgi allows this. command twice). At this point SearXNG is not demonized; uwsgi allows this.

View file

@ -181,10 +181,7 @@ uWSGI setup
Create the configuration ini-file according to your distribution and restart the Create the configuration ini-file according to your distribution and restart the
uwsgi application. As shown below, the :ref:`installation scripts` installs by uwsgi application. As shown below, the :ref:`installation scripts` installs by
default: default a uWSGI setup that listens on a socket.
- a uWSGI setup that listens on a socket and
- enables :ref:`cache busting <static_use_hash>`.
.. tabs:: .. tabs::

View file

@ -10,7 +10,6 @@
.. code:: yaml .. code:: yaml
ui: ui:
static_use_hash: false
default_locale: "" default_locale: ""
query_in_title: false query_in_title: false
infinite_scroll: false infinite_scroll: false
@ -23,11 +22,6 @@
hotkeys: default hotkeys: default
url_formatting: pretty url_formatting: pretty
.. _static_use_hash:
``static_use_hash`` : ``$SEARXNG_STATIC_USE_HASH``
Enables `cache busting`_ of static files.
``default_locale`` : ``default_locale`` :
SearXNG interface language. If blank, the locale is detected by using the SearXNG interface language. If blank, the locale is detected by using the
browser language. If it doesn't work, or you are deploying a language browser language. If it doesn't work, or you are deploying a language

View file

@ -58,10 +58,6 @@ and then, to name just a few:
- Bot protection has been switched from filtron to SearXNG's :ref:`limiter - Bot protection has been switched from filtron to SearXNG's :ref:`limiter
<limiter>`, this requires a :ref:`Valkey <settings valkey>` database. <limiter>`, this requires a :ref:`Valkey <settings valkey>` database.
- To save bandwidth :ref:`cache busting <static_use_hash>` has been implemented.
To get in use, the ``static-expires`` needs to be set in the :ref:`uwsgi
setup`.
To stay tuned and get in use of the new features, instance maintainers have to To stay tuned and get in use of the new features, instance maintainers have to
update the SearXNG code regularly (see :ref:`update searxng`). As the above update the SearXNG code regularly (see :ref:`update searxng`). As the above
examples show, this is not always enough, sometimes services have to be set up examples show, this is not always enough, sometimes services have to be set up

View file

@ -119,8 +119,6 @@ valkey:
ui: ui:
# Custom static path - leave it blank if you didn't change # Custom static path - leave it blank if you didn't change
static_path: "" static_path: ""
# Is overwritten by ${SEARXNG_STATIC_USE_HASH}.
static_use_hash: false
# Custom templates path - leave it blank if you didn't change # Custom templates path - leave it blank if you didn't change
templates_path: "" templates_path: ""
# query_in_title: When true, the result page's titles contains the query # query_in_title: When true, the result page's titles contains the query

View file

@ -194,7 +194,6 @@ SCHEMA = {
}, },
'ui': { 'ui': {
'static_path': SettingsDirectoryValue(str, os.path.join(searx_dir, 'static')), 'static_path': SettingsDirectoryValue(str, os.path.join(searx_dir, 'static')),
'static_use_hash': SettingsValue(bool, False, 'SEARXNG_STATIC_USE_HASH'),
'templates_path': SettingsDirectoryValue(str, os.path.join(searx_dir, 'templates')), 'templates_path': SettingsDirectoryValue(str, os.path.join(searx_dir, 'templates')),
'default_theme': SettingsValue(str, 'simple'), 'default_theme': SettingsValue(str, 'simple'),
'default_locale': SettingsValue(str, ''), 'default_locale': SettingsValue(str, ''),

View file

@ -76,7 +76,6 @@ from searx.engines import (
from searx import webutils from searx import webutils
from searx.webutils import ( from searx.webutils import (
highlight_content, highlight_content,
get_static_files,
get_result_templates, get_result_templates,
get_themes, get_themes,
exception_classname_to_text, exception_classname_to_text,
@ -131,7 +130,6 @@ warnings.simplefilter("always")
# about static # about static
logger.debug('static directory is %s', settings['ui']['static_path']) logger.debug('static directory is %s', settings['ui']['static_path'])
static_files = get_static_files(settings['ui']['static_path'])
# about templates # about templates
logger.debug('templates directory is %s', settings['ui']['templates_path']) logger.debug('templates directory is %s', settings['ui']['templates_path'])
@ -239,25 +237,44 @@ def get_result_template(theme_name: str, template_name: str):
return 'result_templates/' + template_name return 'result_templates/' + template_name
_STATIC_FILES: list[str] = []
def custom_url_for(endpoint: str, **values): def custom_url_for(endpoint: str, **values):
suffix = "" global _STATIC_FILES # pylint: disable=global-statement
if endpoint == 'static' and values.get('filename'): if not _STATIC_FILES:
file_hash = static_files.get(values['filename']) _STATIC_FILES = webutils.get_static_file_list()
if not file_hash:
if endpoint == "static" and values.get("filename"):
# We need to verify the "filename" argument: in the jinja templates
# there could be call like:
# url_for('static', filename='img/favicon.png')
# which should map to:
# static/themes/<theme_name>/img/favicon.png
arg_filename = values["filename"]
if arg_filename not in _STATIC_FILES:
# try file in the current theme # try file in the current theme
theme_name = sxng_request.preferences.get_value('theme') theme_name = sxng_request.preferences.get_value("theme")
filename_with_theme = "themes/{}/{}".format(theme_name, values['filename']) arg_filename = f"themes/{theme_name}/{arg_filename}"
file_hash = static_files.get(filename_with_theme) if arg_filename in _STATIC_FILES:
if file_hash: values["filename"] = arg_filename
values['filename'] = filename_with_theme
if get_setting('ui.static_use_hash') and file_hash: if endpoint == "info" and "locale" not in values:
suffix = "?" + file_hash
if endpoint == 'info' and 'locale' not in values: # We need to verify the "locale" argument: in the jinja templates there
locale = sxng_request.preferences.get_value('locale') # could be call like:
if infopage.INFO_PAGES.get_page(values['pagename'], locale) is None: # url_for('info', pagename='about')
# which should map to:
# info/<locale>/about
locale = sxng_request.preferences.get_value("locale")
if infopage.INFO_PAGES.get_page(values["pagename"], locale) is None:
locale = infopage.INFO_PAGES.locale_default locale = infopage.INFO_PAGES.locale_default
values['locale'] = locale values["locale"] = locale
return url_for(endpoint, **values) + suffix
return url_for(endpoint, **values)
def image_proxify(url: str): def image_proxify(url: str):

View file

@ -12,14 +12,15 @@ import re
import itertools import itertools
import json import json
from datetime import datetime, timedelta from datetime import datetime, timedelta
from typing import Iterable, List, Tuple, Dict, TYPE_CHECKING from typing import Iterable, List, Tuple, TYPE_CHECKING
from io import StringIO from io import StringIO
from codecs import getincrementalencoder from codecs import getincrementalencoder
from flask_babel import gettext, format_date # type: ignore from flask_babel import gettext, format_date # type: ignore
from searx import logger, settings from searx import logger, get_setting
from searx.engines import DEFAULT_CATEGORY from searx.engines import DEFAULT_CATEGORY
if TYPE_CHECKING: if TYPE_CHECKING:
@ -177,30 +178,22 @@ def get_themes(templates_path):
return os.listdir(templates_path) return os.listdir(templates_path)
def get_hash_for_file(file: pathlib.Path) -> str: def get_static_file_list() -> list[str]:
m = hashlib.sha1() file_list = []
with file.open('rb') as f: static_path = pathlib.Path(str(get_setting("ui.static_path")))
m.update(f.read())
return m.hexdigest()
def _walk(path: pathlib.Path):
def get_static_files(static_path: str) -> Dict[str, str]: for f in path.iterdir():
static_files: Dict[str, str] = {} if f.name.startswith('.'):
static_path_path = pathlib.Path(static_path)
def walk(path: pathlib.Path):
for file in path.iterdir():
if file.name.startswith('.'):
# ignore hidden file # ignore hidden file
continue continue
if file.is_file(): if f.is_file():
static_files[str(file.relative_to(static_path_path))] = get_hash_for_file(file) file_list.append(str(f.relative_to(static_path)))
if file.is_dir() and file.name not in ('node_modules', 'src'): if f.is_dir():
# ignore "src" and "node_modules" directories _walk(f)
walk(file)
walk(static_path_path) _walk(static_path)
return static_files return file_list
def get_result_templates(templates_path): def get_result_templates(templates_path):
@ -331,7 +324,7 @@ def group_engines_in_tab(engines: Iterable[Engine]) -> List[Tuple[str, Iterable[
def engine_sort_key(engine): def engine_sort_key(engine):
return (engine.about.get('language', ''), engine.name) return (engine.about.get('language', ''), engine.name)
tabs = list(settings['categories_as_tabs'].keys()) tabs = list(get_setting('categories_as_tabs').keys())
subgroups = itertools.groupby(sorted(engines, key=get_subgroup), get_subgroup) subgroups = itertools.groupby(sorted(engines, key=get_subgroup), get_subgroup)
sorted_groups = sorted(((name, list(engines)) for name, engines in subgroups), key=group_sort_key) sorted_groups = sorted(((name, list(engines)) for name, engines in subgroups), key=group_sort_key)

View file

@ -25,9 +25,6 @@ class ViewsTestCase(SearxTestCase): # pylint: disable=too-many-public-methods
pass pass
self.setattr4test(searx.search.processors, 'initialize_processor', dummy) self.setattr4test(searx.search.processors, 'initialize_processor', dummy)
# remove sha for the static file so the tests don't have to care about
# the changing URLs
self.setattr4test(searx.webapp, 'static_files', {})
# set some defaults # set some defaults
test_results = [ test_results = [

View file

@ -33,9 +33,6 @@ LoadModule proxy_http_module ${APACHE_MODULES}/mod_proxy_http.so
</Location> </Location>
# uWSGI serves the static files and in settings.yml we use:: # To serve the static files via the HTTP server
#
# ui:
# static_use_hash: true
# #
# Alias ${SEARXNG_URL_PATH}/static/ ${SEARXNG_STATIC}/ # Alias ${SEARXNG_URL_PATH}/static/ ${SEARXNG_STATIC}/

View file

@ -33,9 +33,6 @@ LoadModule proxy_uwsgi_module ${APACHE_MODULES}/mod_proxy_uwsgi.so
</Location> </Location>
# uWSGI serves the static files and in settings.yml we use:: # To serve the static files via the HTTP server
#
# ui:
# static_use_hash: true
# #
# Alias ${SEARXNG_URL_PATH}/static/ ${SEARXNG_STATIC}/ # Alias ${SEARXNG_URL_PATH}/static/ ${SEARXNG_STATIC}/

View file

@ -19,10 +19,7 @@ location ${SEARXNG_URL_PATH} {
} }
# uWSGI serves the static files and in settings.yml we use:: # To serve the static files via the HTTP server
#
# ui:
# static_use_hash: true
# #
# location ${SEARXNG_URL_PATH}/static/ { # location ${SEARXNG_URL_PATH}/static/ {
# alias ${SEARXNG_STATIC}/; # alias ${SEARXNG_STATIC}/;

View file

@ -16,10 +16,7 @@ location ${SEARXNG_URL_PATH} {
uwsgi_param HTTP_X_FORWARDED_FOR \$proxy_add_x_forwarded_for; uwsgi_param HTTP_X_FORWARDED_FOR \$proxy_add_x_forwarded_for;
} }
# uWSGI serves the static files and in settings.yml we use:: # To serve the static files via the HTTP server
#
# ui:
# static_use_hash: true
# #
# location ${SEARXNG_URL_PATH}/static/ { # location ${SEARXNG_URL_PATH}/static/ {
# alias ${SEARXNG_STATIC}/; # alias ${SEARXNG_STATIC}/;

View file

@ -25,9 +25,6 @@ valkey:
# URL to connect valkey database. Is overwritten by ${SEARXNG_VALKEY_URL}. # URL to connect valkey database. Is overwritten by ${SEARXNG_VALKEY_URL}.
url: valkey://localhost:6379/0 url: valkey://localhost:6379/0
ui:
static_use_hash: true
# preferences: # preferences:
# lock: # lock:
# - autocomplete # - autocomplete

View file

@ -75,11 +75,7 @@ pythonpath = ${SEARXNG_SRC}
http = ${SEARXNG_INTERNAL_HTTP} http = ${SEARXNG_INTERNAL_HTTP}
buffer-size = 8192 buffer-size = 8192
# uWSGI serves the static files and in settings.yml we use:: # To serve the static files via the WSGI server
#
# ui:
# static_use_hash: true
#
static-map = /static=${SEARXNG_STATIC} static-map = /static=${SEARXNG_STATIC}
static-gzip-all = True static-gzip-all = True
offload-threads = %k offload-threads = %k

View file

@ -72,11 +72,7 @@ pythonpath = ${SEARXNG_SRC}
socket = ${SEARXNG_UWSGI_SOCKET} socket = ${SEARXNG_UWSGI_SOCKET}
buffer-size = 8192 buffer-size = 8192
# uWSGI serves the static files and in settings.yml we use:: # To serve the static files via the WSGI server
#
# ui:
# static_use_hash: true
#
static-map = /static=${SEARXNG_STATIC} static-map = /static=${SEARXNG_STATIC}
static-gzip-all = True static-gzip-all = True
offload-threads = %k offload-threads = %k

View file

@ -78,11 +78,7 @@ pythonpath = ${SEARXNG_SRC}
http = ${SEARXNG_INTERNAL_HTTP} http = ${SEARXNG_INTERNAL_HTTP}
buffer-size = 8192 buffer-size = 8192
# uWSGI serves the static files and in settings.yml we use:: # To serve the static files via the WSGI server
#
# ui:
# static_use_hash: true
#
static-map = /static=${SEARXNG_STATIC} static-map = /static=${SEARXNG_STATIC}
static-gzip-all = True static-gzip-all = True
offload-threads = %k offload-threads = %k

View file

@ -75,11 +75,7 @@ pythonpath = ${SEARXNG_SRC}
socket = ${SEARXNG_UWSGI_SOCKET} socket = ${SEARXNG_UWSGI_SOCKET}
buffer-size = 8192 buffer-size = 8192
# uWSGI serves the static files and in settings.yml we use:: # To serve the static files via the WSGI server
#
# ui:
# static_use_hash: true
#
static-map = /static=${SEARXNG_STATIC} static-map = /static=${SEARXNG_STATIC}
static-gzip-all = True static-gzip-all = True
offload-threads = %k offload-threads = %k