Add caching.py to enable browser caching of files.
This commit is contained in:
parent
ea7401b4f2
commit
b5274fefb9
2 changed files with 88 additions and 1 deletions
72
frontends/etiquette_flask/etiquette_flask/caching.py
Normal file
72
frontends/etiquette_flask/etiquette_flask/caching.py
Normal file
|
@ -0,0 +1,72 @@
|
|||
'''
|
||||
This file provides the FileCacheManager to serve ETag and Cache-Control headers
|
||||
for files on disk.
|
||||
|
||||
We consider the following cases:
|
||||
|
||||
Client does not have the file (or has disabled their cache, effectively same):
|
||||
Server sends file, provides ETag, and tells client to save it for max-age.
|
||||
|
||||
Client has the file, but it has been a long time, beyond the max-age:
|
||||
Client provides the old ETag. If it's still valid, Server responds with
|
||||
304 Not Modified and no data. Client keeps the file.
|
||||
|
||||
Client has the file, and it is within the max-age:
|
||||
Client does not make a request at all.
|
||||
|
||||
This FileCacheManager uses the file's MD5 hash as the ETag, and will only
|
||||
recalculate it if the file's mtime has changed since the last request.
|
||||
'''
|
||||
|
||||
import time
|
||||
|
||||
import etiquette
|
||||
|
||||
from voussoirkit import cacheclass
|
||||
from voussoirkit import pathclass
|
||||
|
||||
class FileCacheManager:
|
||||
def __init__(self, maxlen, max_filesize, max_age):
|
||||
self.cache = cacheclass.Cache(maxlen=maxlen)
|
||||
self.max_filesize = int(max_filesize)
|
||||
self.max_age = int(max_age)
|
||||
|
||||
def get(self, filepath):
|
||||
if (self.max_filesize is not None) and (filepath.size > self.max_filesize):
|
||||
#print('I\'m not going to cache that!')
|
||||
return None
|
||||
|
||||
try:
|
||||
return self.cache[filepath]
|
||||
except KeyError:
|
||||
pass
|
||||
cache_file = CacheFile(filepath, max_age=self.max_age)
|
||||
self.cache[filepath] = cache_file
|
||||
return cache_file
|
||||
|
||||
class CacheFile:
|
||||
def __init__(self, filepath, max_age):
|
||||
self.filepath = filepath
|
||||
self.max_age = max_age
|
||||
self._stored_hash_time = None
|
||||
self._stored_hash_value = None
|
||||
|
||||
def get_etag(self):
|
||||
if self._stored_hash_value is None:
|
||||
refresh = True
|
||||
elif self.filepath.stat.st_mtime > self._stored_hash_time:
|
||||
refresh = True
|
||||
else:
|
||||
refresh = False
|
||||
|
||||
if refresh:
|
||||
self._stored_hash_time = self.filepath.stat.st_mtime
|
||||
self._stored_hash_value = etiquette.helpers.hash_file_md5(self.filepath)
|
||||
return self._stored_hash_value
|
||||
|
||||
def get_headers(self):
|
||||
headers = {
|
||||
'ETag': self.get_etag(),
|
||||
'Cache-Control': 'max-age=%d' % self.max_age,
|
||||
}
|
||||
return headers
|
|
@ -5,8 +5,10 @@ import traceback
|
|||
|
||||
import etiquette
|
||||
|
||||
from voussoirkit import bytestring
|
||||
from voussoirkit import pathclass
|
||||
|
||||
from . import caching
|
||||
from . import jsonify
|
||||
from . import sessions
|
||||
|
||||
|
@ -34,7 +36,11 @@ site.debug = True
|
|||
P = etiquette.photodb.PhotoDB()
|
||||
|
||||
session_manager = sessions.SessionManager(maxlen=10000)
|
||||
|
||||
file_cache_manager = caching.FileCacheManager(
|
||||
maxlen=10000,
|
||||
max_filesize=5 * bytestring.MIBIBYTE,
|
||||
max_age=180,
|
||||
)
|
||||
|
||||
def P_wrapper(function):
|
||||
def P_wrapped(thingid, response_type='html'):
|
||||
|
@ -100,6 +106,13 @@ def send_file(filepath, override_mimetype=None):
|
|||
if not filepath.is_file:
|
||||
flask.abort(404)
|
||||
|
||||
cache_file = file_cache_manager.get(filepath)
|
||||
if cache_file is not None:
|
||||
client_etag = request.headers.get('If-None-Match', None)
|
||||
if client_etag and client_etag == cache_file.get_etag():
|
||||
response = flask.Response(status=304, headers=cache_file.get_headers())
|
||||
return response
|
||||
|
||||
outgoing_headers = {}
|
||||
if override_mimetype is not None:
|
||||
mimetype = override_mimetype
|
||||
|
@ -147,6 +160,8 @@ def send_file(filepath, override_mimetype=None):
|
|||
|
||||
outgoing_headers['Accept-Ranges'] = 'bytes'
|
||||
outgoing_headers['Content-Length'] = (range_max - range_min) + 1
|
||||
if cache_file is not None:
|
||||
outgoing_headers.update(cache_file.get_headers())
|
||||
|
||||
if request.method == 'HEAD':
|
||||
outgoing_data = bytes()
|
||||
|
|
Loading…
Reference in a new issue