Move cached_endpoint to decorators.
This commit is contained in:
parent
11b846a3e0
commit
bb82c1e4e7
4 changed files with 64 additions and 69 deletions
|
@ -1,70 +1,7 @@
|
||||||
import flask; from flask import request
|
|
||||||
import functools
|
|
||||||
import time
|
|
||||||
|
|
||||||
from voussoirkit import cacheclass
|
from voussoirkit import cacheclass
|
||||||
from voussoirkit import passwordy
|
|
||||||
|
|
||||||
import etiquette
|
import etiquette
|
||||||
|
|
||||||
def cached_endpoint(max_age):
|
|
||||||
'''
|
|
||||||
The cached_endpoint decorator can be used on slow endpoints that don't need
|
|
||||||
to be constantly updated or endpoints that produce large, static responses.
|
|
||||||
|
|
||||||
WARNING: The return value of the endpoint is shared with all users.
|
|
||||||
You should never use this cache on an endpoint that provides private
|
|
||||||
or personalized data, and you should not try to pass other headers through
|
|
||||||
the response.
|
|
||||||
|
|
||||||
When the function is run, its return value is stored and a random etag is
|
|
||||||
generated so that subsequent runs can respond with 304. This way, large
|
|
||||||
response bodies do not need to be transmitted often.
|
|
||||||
|
|
||||||
Given a nonzero max_age, the endpoint will only be run once per max_age
|
|
||||||
seconds on a global basis (not per-user). This way, you can prevent a slow
|
|
||||||
function from being run very often. In-between requests will just receive
|
|
||||||
the previous return value (still using 200 or 304 as appropriate for the
|
|
||||||
client's provided etag).
|
|
||||||
|
|
||||||
An example use case would be large-sized data dumps that don't need to be
|
|
||||||
precisely up to date every time.
|
|
||||||
'''
|
|
||||||
state = {
|
|
||||||
'max_age': max_age,
|
|
||||||
'stored_value': None,
|
|
||||||
'stored_etag': None,
|
|
||||||
'headers': {'ETag': None, 'Cache-Control': f'max-age={max_age}'},
|
|
||||||
'last_run': 0,
|
|
||||||
}
|
|
||||||
|
|
||||||
def wrapper(function):
|
|
||||||
@functools.wraps(function)
|
|
||||||
def wrapped(*args, **kwargs):
|
|
||||||
if (not state['max_age']) or (time.time() - state['last_run'] > state['max_age']):
|
|
||||||
value = function(*args, **kwargs)
|
|
||||||
if isinstance(value, flask.Response):
|
|
||||||
if value.headers.get('Content-Type'):
|
|
||||||
state['headers']['Content-Type'] = value.headers.get('Content-Type')
|
|
||||||
value = value.response
|
|
||||||
if value != state['stored_value']:
|
|
||||||
state['stored_value'] = value
|
|
||||||
state['stored_etag'] = passwordy.random_hex(20)
|
|
||||||
state['headers']['ETag'] = state['stored_etag']
|
|
||||||
state['last_run'] = time.time()
|
|
||||||
else:
|
|
||||||
value = state['stored_value']
|
|
||||||
|
|
||||||
client_etag = request.headers.get('If-None-Match', None)
|
|
||||||
if client_etag == state['stored_etag']:
|
|
||||||
response = flask.Response(status=304, headers=state['headers'])
|
|
||||||
else:
|
|
||||||
response = flask.Response(value, status=200, headers=state['headers'])
|
|
||||||
|
|
||||||
return response
|
|
||||||
return wrapped
|
|
||||||
return wrapper
|
|
||||||
|
|
||||||
class FileCacheManager:
|
class FileCacheManager:
|
||||||
'''
|
'''
|
||||||
The FileCacheManager serves ETag and Cache-Control headers for disk files.
|
The FileCacheManager serves ETag and Cache-Control headers for disk files.
|
||||||
|
|
|
@ -1,11 +1,71 @@
|
||||||
import flask
|
import flask; from flask import request
|
||||||
from flask import request
|
|
||||||
import functools
|
import functools
|
||||||
|
import time
|
||||||
|
|
||||||
|
from voussoirkit import passwordy
|
||||||
|
|
||||||
import etiquette
|
import etiquette
|
||||||
|
|
||||||
from . import jsonify
|
from . import jsonify
|
||||||
|
|
||||||
|
def cached_endpoint(max_age):
|
||||||
|
'''
|
||||||
|
The cached_endpoint decorator can be used on slow endpoints that don't need
|
||||||
|
to be constantly updated or endpoints that produce large, static responses.
|
||||||
|
|
||||||
|
WARNING: The return value of the endpoint is shared with all users.
|
||||||
|
You should never use this cache on an endpoint that provides private
|
||||||
|
or personalized data, and you should not try to pass other headers through
|
||||||
|
the response.
|
||||||
|
|
||||||
|
When the function is run, its return value is stored and a random etag is
|
||||||
|
generated so that subsequent runs can respond with 304. This way, large
|
||||||
|
response bodies do not need to be transmitted often.
|
||||||
|
|
||||||
|
Given a nonzero max_age, the endpoint will only be run once per max_age
|
||||||
|
seconds on a global basis (not per-user). This way, you can prevent a slow
|
||||||
|
function from being run very often. In-between requests will just receive
|
||||||
|
the previous return value (still using 200 or 304 as appropriate for the
|
||||||
|
client's provided etag).
|
||||||
|
|
||||||
|
An example use case would be large-sized data dumps that don't need to be
|
||||||
|
precisely up to date every time.
|
||||||
|
'''
|
||||||
|
state = {
|
||||||
|
'max_age': max_age,
|
||||||
|
'stored_value': None,
|
||||||
|
'stored_etag': None,
|
||||||
|
'headers': {'ETag': None, 'Cache-Control': f'max-age={max_age}'},
|
||||||
|
'last_run': 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
def wrapper(function):
|
||||||
|
@functools.wraps(function)
|
||||||
|
def wrapped(*args, **kwargs):
|
||||||
|
if (not state['max_age']) or (time.time() - state['last_run'] > state['max_age']):
|
||||||
|
value = function(*args, **kwargs)
|
||||||
|
if isinstance(value, flask.Response):
|
||||||
|
if value.headers.get('Content-Type'):
|
||||||
|
state['headers']['Content-Type'] = value.headers.get('Content-Type')
|
||||||
|
value = value.response
|
||||||
|
if value != state['stored_value']:
|
||||||
|
state['stored_value'] = value
|
||||||
|
state['stored_etag'] = passwordy.random_hex(20)
|
||||||
|
state['headers']['ETag'] = state['stored_etag']
|
||||||
|
state['last_run'] = time.time()
|
||||||
|
else:
|
||||||
|
value = state['stored_value']
|
||||||
|
|
||||||
|
client_etag = request.headers.get('If-None-Match', None)
|
||||||
|
if client_etag == state['stored_etag']:
|
||||||
|
response = flask.Response(status=304, headers=state['headers'])
|
||||||
|
else:
|
||||||
|
response = flask.Response(value, status=200, headers=state['headers'])
|
||||||
|
|
||||||
|
return response
|
||||||
|
return wrapped
|
||||||
|
return wrapper
|
||||||
|
|
||||||
def catch_etiquette_exception(function):
|
def catch_etiquette_exception(function):
|
||||||
'''
|
'''
|
||||||
If an EtiquetteException is raised, automatically catch it and convert it
|
If an EtiquetteException is raised, automatically catch it and convert it
|
||||||
|
|
|
@ -6,7 +6,6 @@ from voussoirkit import stringtools
|
||||||
|
|
||||||
import etiquette
|
import etiquette
|
||||||
|
|
||||||
from .. import caching
|
|
||||||
from .. import common
|
from .. import common
|
||||||
from .. import decorators
|
from .. import decorators
|
||||||
from .. import jsonify
|
from .. import jsonify
|
||||||
|
@ -159,7 +158,7 @@ def post_album_edit(album_id):
|
||||||
# Album listings ###################################################################################
|
# Album listings ###################################################################################
|
||||||
|
|
||||||
@site.route('/all_albums.json')
|
@site.route('/all_albums.json')
|
||||||
@caching.cached_endpoint(max_age=0)
|
@decorators.cached_endpoint(max_age=0)
|
||||||
def get_all_album_names():
|
def get_all_album_names():
|
||||||
all_albums = {album.display_name: album.id for album in common.P.get_albums()}
|
all_albums = {album.display_name: album.id for album in common.P.get_albums()}
|
||||||
response = {'updated': int(time.time()), 'albums': all_albums}
|
response = {'updated': int(time.time()), 'albums': all_albums}
|
||||||
|
|
|
@ -3,7 +3,6 @@ import time
|
||||||
|
|
||||||
import etiquette
|
import etiquette
|
||||||
|
|
||||||
from .. import caching
|
|
||||||
from .. import common
|
from .. import common
|
||||||
from .. import decorators
|
from .. import decorators
|
||||||
from .. import jsonify
|
from .. import jsonify
|
||||||
|
@ -65,7 +64,7 @@ def post_tag_remove_child(tagname):
|
||||||
# Tag listings #####################################################################################
|
# Tag listings #####################################################################################
|
||||||
|
|
||||||
@site.route('/all_tags.json')
|
@site.route('/all_tags.json')
|
||||||
@caching.cached_endpoint(max_age=0)
|
@decorators.cached_endpoint(max_age=0)
|
||||||
def get_all_tag_names():
|
def get_all_tag_names():
|
||||||
all_tags = list(common.P.get_all_tag_names())
|
all_tags = list(common.P.get_all_tag_names())
|
||||||
all_synonyms = common.P.get_all_synonyms()
|
all_synonyms = common.P.get_all_synonyms()
|
||||||
|
|
Loading…
Reference in a new issue