Use cache busting for css/js files

On app init, short hashes are generated from file checksums to use for
cache busting. These hashes are added into the full file name and used
to symlink to the actual file contents. These symlinks are loaded in the
jinja templates for each page, and can tell the browser to load a new
file if the hash changes.

This is only in place for css and js files, but can be extended in the
future for other file types if needed.
main
Ben Busby 2021-06-30 19:00:01 -04:00
parent c41e0fc239
commit 68fdd55482
No known key found for this signature in database
GPG Key ID: 339B7B7EB5333D14
8 changed files with 73 additions and 23 deletions

View File

@ -2,6 +2,7 @@ from app.filter import clean_query
from app.request import send_tor_signal from app.request import send_tor_signal
from app.utils.session import generate_user_key from app.utils.session import generate_user_key
from app.utils.bangs import gen_bangs_json from app.utils.bangs import gen_bangs_json
from app.utils.misc import gen_file_hash
from flask import Flask from flask import Flask
from flask_session import Session from flask_session import Session
import json import json
@ -30,6 +31,9 @@ app.config['APP_ROOT'] = os.getenv(
app.config['STATIC_FOLDER'] = os.getenv( app.config['STATIC_FOLDER'] = os.getenv(
'STATIC_FOLDER', 'STATIC_FOLDER',
os.path.join(app.config['APP_ROOT'], 'static')) os.path.join(app.config['APP_ROOT'], 'static'))
app.config['BUILD_FOLDER'] = os.path.join(
app.config['STATIC_FOLDER'], 'build')
app.config['CACHE_BUSTING_MAP'] = {}
app.config['LANGUAGES'] = json.load(open( app.config['LANGUAGES'] = json.load(open(
os.path.join(app.config['STATIC_FOLDER'], 'settings/languages.json'))) os.path.join(app.config['STATIC_FOLDER'], 'settings/languages.json')))
app.config['COUNTRIES'] = json.load(open( app.config['COUNTRIES'] = json.load(open(
@ -73,9 +77,6 @@ app.config['CSP'] = 'default-src \'none\';' \
'connect-src \'self\';' \ 'connect-src \'self\';' \
'form-action \'self\';' 'form-action \'self\';'
# Templating functions
app.jinja_env.globals.update(clean_query=clean_query)
if not os.path.exists(app.config['CONFIG_PATH']): if not os.path.exists(app.config['CONFIG_PATH']):
os.makedirs(app.config['CONFIG_PATH']) os.makedirs(app.config['CONFIG_PATH'])
@ -88,6 +89,31 @@ if not os.path.exists(app.config['BANG_PATH']):
if not os.path.exists(app.config['BANG_FILE']): if not os.path.exists(app.config['BANG_FILE']):
gen_bangs_json(app.config['BANG_FILE']) gen_bangs_json(app.config['BANG_FILE'])
# Build new mapping of static files for cache busting
if not os.path.exists(app.config['BUILD_FOLDER']):
os.makedirs(app.config['BUILD_FOLDER'])
cache_busting_dirs = ['css', 'js']
for cb_dir in cache_busting_dirs:
full_cb_dir = os.path.join(app.config['STATIC_FOLDER'], cb_dir)
for cb_file in os.listdir(full_cb_dir):
# Create hash from current file state
full_cb_path = os.path.join(full_cb_dir, cb_file)
cb_file_link = gen_file_hash(full_cb_dir, cb_file)
build_path = os.path.join(app.config['BUILD_FOLDER'], cb_file_link)
os.symlink(full_cb_path, build_path)
# Create mapping for relative path urls
map_path = build_path.replace(app.config['APP_ROOT'], '')
if map_path.startswith('/'):
map_path = map_path[1:]
app.config['CACHE_BUSTING_MAP'][cb_file] = map_path
# Templating functions
app.jinja_env.globals.update(clean_query=clean_query)
app.jinja_env.globals.update(
cb_url=lambda f: app.config['CACHE_BUSTING_MAP'][f])
Session(app) Session(app)
# Attempt to acquire tor identity, to determine if Tor config is available # Attempt to acquire tor identity, to determine if Tor config is available

View File

@ -1,2 +0,0 @@
@import "/static/css/light-theme.css" screen;
@import "/static/css/dark-theme.css" screen and (prefers-color-scheme: dark);

View File

@ -5,14 +5,21 @@
<link rel="search" href="opensearch.xml" type="application/opensearchdescription+xml" title="Whoogle Search"> <link rel="search" href="opensearch.xml" type="application/opensearchdescription+xml" title="Whoogle Search">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="referrer" content="no-referrer"> <meta name="referrer" content="no-referrer">
<link rel="stylesheet" href="static/css/input.css"> <link rel="stylesheet" href="{{ cb_url('input.css') }}">
<link rel="stylesheet" href="static/css/search.css"> <link rel="stylesheet" href="{{ cb_url('search.css') }}">
<link rel="stylesheet" href="static/css/variables.css"> <link rel="stylesheet" href="{{ cb_url('variables.css') }}">
<link rel="stylesheet" href="static/css/header.css"> <link rel="stylesheet" href="{{ cb_url('header.css') }}">
{% if config.theme %} {% if config.theme %}
<link rel="stylesheet" href="static/css/{{ config.theme }}-theme.css"/> {% if config.theme == 'system' %}
<style>
@import "{{ cb_url('light-theme.css') }}" screen;
@import "{{ cb_url('dark-theme.css') }}" screen and (prefers-color-scheme: dark);
</style>
{% else %}
<link rel="stylesheet" href="{{ cb_url(config.theme + '-theme.css') }}"/>
{% endif %}
{% else %} {% else %}
<link rel="stylesheet" href="static/css/{{ 'dark' if config.dark else 'light' }}-theme.css"/> <link rel="stylesheet" href="{{ cb_url(('dark' if config.dark else 'light') + '-theme.css') }}"/>
{% endif %} {% endif %}
<style>{{ config.style }}</style> <style>{{ config.style }}</style>
<title>{{ clean_query(query) }} - Whoogle Search</title> <title>{{ clean_query(query) }} - Whoogle Search</title>
@ -33,7 +40,7 @@
<a id="gh-link" href="https://github.com/benbusby/whoogle-search">{{ translation['github-link'] }}</a> <a id="gh-link" href="https://github.com/benbusby/whoogle-search">{{ translation['github-link'] }}</a>
</p> </p>
</footer> </footer>
<script src="static/js/autocomplete.js"></script> <script src="{{ cb_url('autocomplete.js') }}"></script>
<script src="static/js/utils.js"></script> <script src="{{ cb_url('utils.js') }}"></script>
<script src="static/js/keyboard.js"></script> <script src="{{ cb_url('keyboard.js') }}"></script>
</html> </html>

View File

@ -62,4 +62,4 @@
</header> </header>
{% endif %} {% endif %}
<script type="text/javascript" src="static/js/header.js"></script> <script type="text/javascript" src="{{ cb_url('header.js') }}"></script>

View File

@ -17,17 +17,24 @@
<meta name="referrer" content="no-referrer"> <meta name="referrer" content="no-referrer">
<meta name="msapplication-TileColor" content="#ffffff"> <meta name="msapplication-TileColor" content="#ffffff">
<meta name="msapplication-TileImage" content="static/img/favicon/ms-icon-144x144.png"> <meta name="msapplication-TileImage" content="static/img/favicon/ms-icon-144x144.png">
<script type="text/javascript" src="static/js/autocomplete.js"></script> <script type="text/javascript" src="{{ cb_url('autocomplete.js') }}"></script>
<script type="text/javascript" src="static/js/controller.js"></script> <script type="text/javascript" src="{{ cb_url('controller.js') }}"></script>
<link rel="search" href="opensearch.xml" type="application/opensearchdescription+xml" title="Whoogle Search"> <link rel="search" href="opensearch.xml" type="application/opensearchdescription+xml" title="Whoogle Search">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="static/css/variables.css"> <link rel="stylesheet" href="{{ cb_url('variables.css') }}">
{% if config.theme %} {% if config.theme %}
<link rel="stylesheet" href="static/css/{{ config.theme }}-theme.css"/> {% if config.theme == 'system' %}
<style>
@import "{{ cb_url('light-theme.css') }}" screen;
@import "{{ cb_url('dark-theme.css') }}" screen and (prefers-color-scheme: dark);
</style>
{% else %}
<link rel="stylesheet" href="{{ cb_url(config.theme + '-theme.css') }}"/>
{% endif %}
{% else %} {% else %}
<link rel="stylesheet" href="static/css/{{ 'dark' if config.dark else 'light' }}-theme.css"/> <link rel="stylesheet" href="{{ cb_url(('dark' if config.dark else 'light') + '-theme.css') }}"/>
{% endif %} {% endif %}
<link rel="stylesheet" href="static/css/main.css"> <link rel="stylesheet" href="{{ cb_url('main.css') }}">
<noscript> <noscript>
<style> <style>
#main { display: inherit !important; } #main { display: inherit !important; }

View File

@ -1,4 +1,4 @@
<link rel="stylesheet" href="static/css/logo.css"> <link rel="stylesheet" href="{{ cb_url('logo.css') }}">
<svg id="Layer_1" class="whoogle-svg" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1028 254"> <svg id="Layer_1" class="whoogle-svg" data-name="Layer 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 1028 254">
<defs> <defs>
<style> <style>

10
app/utils/misc.py Normal file
View File

@ -0,0 +1,10 @@
import hashlib
import os
def gen_file_hash(path: str, static_file: str) -> str:
file_contents = open(os.path.join(path, static_file), 'rb').read()
file_hash = hashlib.md5(file_contents).hexdigest()[:8]
filename_split = os.path.splitext(static_file)
return filename_split[0] + '.' + file_hash + filename_split[-1]

2
run
View File

@ -12,6 +12,8 @@ SUBDIR="${1:-app}"
export APP_ROOT="$SCRIPT_DIR/$SUBDIR" export APP_ROOT="$SCRIPT_DIR/$SUBDIR"
export STATIC_FOLDER="$APP_ROOT/static" export STATIC_FOLDER="$APP_ROOT/static"
rm -rf $STATIC_FOLDER/build
# Check for regular vs test run # Check for regular vs test run
if [[ "$SUBDIR" == "test" ]]; then if [[ "$SUBDIR" == "test" ]]; then
# Set up static files for testing # Set up static files for testing