Improve naming and comments in FileEtagManager
This commit is contained in:
parent
bb82c1e4e7
commit
8ab248a34e
2 changed files with 56 additions and 25 deletions
|
@ -2,9 +2,10 @@ from voussoirkit import cacheclass
|
||||||
|
|
||||||
import etiquette
|
import etiquette
|
||||||
|
|
||||||
class FileCacheManager:
|
class FileEtagManager:
|
||||||
'''
|
'''
|
||||||
The FileCacheManager serves ETag and Cache-Control headers for disk files.
|
The FileEtagManager serves ETag and Cache-Control headers for disk files to
|
||||||
|
enable client-side caching.
|
||||||
|
|
||||||
We consider the following cases:
|
We consider the following cases:
|
||||||
|
|
||||||
|
@ -22,11 +23,50 @@ class FileCacheManager:
|
||||||
file's mtime has changed since the last request.
|
file's mtime has changed since the last request.
|
||||||
'''
|
'''
|
||||||
def __init__(self, maxlen, max_age, max_filesize):
|
def __init__(self, maxlen, max_age, max_filesize):
|
||||||
|
'''
|
||||||
|
max_len:
|
||||||
|
The number of files to track in this cache.
|
||||||
|
max_age:
|
||||||
|
Integer number of seconds that will be send to the client as
|
||||||
|
Cache-Control:max-age=x
|
||||||
|
max_filesize:
|
||||||
|
Integer number of bytes. Because we use the file's MD5 as its etag,
|
||||||
|
you may wish to prevent the reading of large files. Files larger
|
||||||
|
than this size will not be etagged.
|
||||||
|
'''
|
||||||
self.cache = cacheclass.Cache(maxlen=maxlen)
|
self.cache = cacheclass.Cache(maxlen=maxlen)
|
||||||
self.max_age = int(max_age)
|
self.max_age = int(max_age)
|
||||||
self.max_filesize = max(int(max_filesize), 0) or None
|
self.max_filesize = max(int(max_filesize), 0) or None
|
||||||
|
|
||||||
def get(self, filepath):
|
def get_304_headers(self, request, filepath):
|
||||||
|
'''
|
||||||
|
Given a request object and a filepath that we would like to send back
|
||||||
|
as the response, check if the client's provided etag matches the
|
||||||
|
server's cached etag, and return the headers to be used in a 304
|
||||||
|
response (etag, cache-control).
|
||||||
|
|
||||||
|
If the client did not provide an etag, or their etag does not match the
|
||||||
|
current file, or the file cannot be cached, return None.
|
||||||
|
'''
|
||||||
|
client_etag = request.headers.get('If-None-Match', None)
|
||||||
|
if client_etag is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
server_value = self.get_file(filepath)
|
||||||
|
if server_value is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
server_etag = server_value.get_etag()
|
||||||
|
if client_etag != server_etag:
|
||||||
|
return None
|
||||||
|
|
||||||
|
return server_value.get_headers()
|
||||||
|
|
||||||
|
def get_file(self, filepath):
|
||||||
|
'''
|
||||||
|
Return a FileEtag object if the filepath can be cached, or None if it
|
||||||
|
cannot (size greater than max_filesize).
|
||||||
|
'''
|
||||||
try:
|
try:
|
||||||
return self.cache[filepath]
|
return self.cache[filepath]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
|
@ -35,26 +75,15 @@ class FileCacheManager:
|
||||||
if (self.max_filesize is not None) and (filepath.size > self.max_filesize):
|
if (self.max_filesize is not None) and (filepath.size > self.max_filesize):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
cache_file = CacheFile(filepath, max_age=self.max_age)
|
cache_file = FileEtag(filepath, max_age=self.max_age)
|
||||||
self.cache[filepath] = cache_file
|
self.cache[filepath] = cache_file
|
||||||
return cache_file
|
return cache_file
|
||||||
|
|
||||||
def matches(self, request, filepath):
|
class FileEtag:
|
||||||
client_etag = request.headers.get('If-None-Match', None)
|
'''
|
||||||
if client_etag is None:
|
This class represents an individual disk file that is being managed by the
|
||||||
return False
|
FileEtagManager.
|
||||||
|
'''
|
||||||
server_value = self.get(filepath)
|
|
||||||
if server_value is None:
|
|
||||||
return False
|
|
||||||
|
|
||||||
server_etag = server_value.get_etag()
|
|
||||||
if client_etag != server_etag:
|
|
||||||
return False
|
|
||||||
|
|
||||||
return server_value.get_headers()
|
|
||||||
|
|
||||||
class CacheFile:
|
|
||||||
def __init__(self, filepath, max_age):
|
def __init__(self, filepath, max_age):
|
||||||
self.filepath = filepath
|
self.filepath = filepath
|
||||||
self.max_age = int(max_age)
|
self.max_age = int(max_age)
|
||||||
|
@ -68,6 +97,7 @@ class CacheFile:
|
||||||
if do_refresh:
|
if do_refresh:
|
||||||
self._stored_hash_time = mtime
|
self._stored_hash_time = mtime
|
||||||
self._stored_hash_value = etiquette.helpers.hash_file_md5(self.filepath)
|
self._stored_hash_value = etiquette.helpers.hash_file_md5(self.filepath)
|
||||||
|
|
||||||
return self._stored_hash_value
|
return self._stored_hash_value
|
||||||
|
|
||||||
def get_headers(self):
|
def get_headers(self):
|
||||||
|
|
|
@ -45,7 +45,7 @@ site.debug = True
|
||||||
site.localhost_only = False
|
site.localhost_only = False
|
||||||
|
|
||||||
session_manager = sessions.SessionManager(maxlen=10000)
|
session_manager = sessions.SessionManager(maxlen=10000)
|
||||||
file_cache_manager = caching.FileCacheManager(
|
file_etag_manager = caching.FileEtagManager(
|
||||||
maxlen=10000,
|
maxlen=10000,
|
||||||
max_filesize=5 * bytestring.MIBIBYTE,
|
max_filesize=5 * bytestring.MIBIBYTE,
|
||||||
max_age=BROWSER_CACHE_DURATION,
|
max_age=BROWSER_CACHE_DURATION,
|
||||||
|
@ -240,7 +240,7 @@ def send_file(filepath, override_mimetype=None):
|
||||||
|
|
||||||
file_size = filepath.size
|
file_size = filepath.size
|
||||||
|
|
||||||
headers = file_cache_manager.matches(request=request, filepath=filepath)
|
headers = file_etag_manager.get_304_headers(request=request, filepath=filepath)
|
||||||
if headers:
|
if headers:
|
||||||
response = flask.Response(status=304, headers=headers)
|
response = flask.Response(status=304, headers=headers)
|
||||||
return response
|
return response
|
||||||
|
@ -292,9 +292,10 @@ def send_file(filepath, override_mimetype=None):
|
||||||
|
|
||||||
outgoing_headers['Accept-Ranges'] = 'bytes'
|
outgoing_headers['Accept-Ranges'] = 'bytes'
|
||||||
outgoing_headers['Content-Length'] = (range_max - range_min) + 1
|
outgoing_headers['Content-Length'] = (range_max - range_min) + 1
|
||||||
cache_file = file_cache_manager.get(filepath)
|
|
||||||
if cache_file is not None:
|
file_etag = file_etag_manager.get_file(filepath)
|
||||||
outgoing_headers.update(cache_file.get_headers())
|
if file_etag is not None:
|
||||||
|
outgoing_headers.update(file_etag.get_headers())
|
||||||
|
|
||||||
if request.method == 'HEAD':
|
if request.method == 'HEAD':
|
||||||
outgoing_data = bytes()
|
outgoing_data = bytes()
|
||||||
|
|
Loading…
Reference in a new issue