Skip to content

Commit 26da6a5

Browse files
committed
Use default send_file max-age consistently.
Prior to this commit, the send_file max-age hook and config were only used for the static file handler. Now they are used when calling helpers.send_file directly.
1 parent 7c79ce6 commit 26da6a5

File tree

6 files changed

+88
-45
lines changed

6 files changed

+88
-45
lines changed

CHANGES

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -53,14 +53,15 @@ Relase date to be decided, codename to be chosen.
5353
- View functions can now return a tuple with the first instance being an
5454
instance of :class:`flask.Response`. This allows for returning
5555
``jsonify(error="error msg"), 400`` from a view function.
56-
- :class:`flask.Flask` now provides a `get_send_file_options` hook for
57-
subclasses to override behavior of serving static files from Flask when using
58-
:meth:`flask.Flask.send_static_file` based on keywords in
59-
:func:`flask.helpers.send_file`. This hook is provided a filename, which for
60-
example allows changing cache controls by file extension. The default
61-
max-age for `send_static_file` can be configured through a new
62-
``SEND_FILE_MAX_AGE_DEFAULT`` configuration variable, regardless of whether
63-
the `get_send_file_options` hook is used.
56+
- :class:`~flask.Flask` and :class:`~flask.Blueprint` now provide a
57+
:meth:`~flask.Flask.get_send_file_max_age` hook for subclasses to override
58+
behavior of serving static files from Flask when using
59+
:meth:`flask.Flask.send_static_file` (used for the default static file
60+
handler) and :func:`~flask.helpers.send_file`. This hook is provided a
61+
filename, which for example allows changing cache controls by file extension.
62+
The default max-age for `send_file` and static files can be configured
63+
through a new ``SEND_FILE_MAX_AGE_DEFAULT`` configuration variable, which is
64+
used in the default `get_send_file_max_age` implementation.
6465
- Fixed an assumption in sessions implementation which could break message
6566
flashing on sessions implementations which use external storage.
6667
- Changed the behavior of tuple return values from functions. They are no

docs/config.rst

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -111,12 +111,15 @@ The following configuration values are used internally by Flask:
111111
content length greater than this by
112112
returning a 413 status code.
113113
``SEND_FILE_MAX_AGE_DEFAULT``: Default cache control max age to use with
114-
:meth:`flask.Flask.send_static_file`, in
114+
:meth:`~flask.Flask.send_static_file` (the
115+
default static file handler) and
116+
:func:`~flask.send_file`, in
115117
seconds. Override this value on a per-file
116118
basis using the
117-
:meth:`flask.Flask.get_send_file_options` and
118-
:meth:`flask.Blueprint.get_send_file_options`
119-
hooks. Defaults to 43200 (12 hours).
119+
:meth:`~flask.Flask.get_send_file_max_age`
120+
hook on :class:`~flask.Flask` or
121+
:class:`~flask.Blueprint`,
122+
respectively. Defaults to 43200 (12 hours).
120123
``TRAP_HTTP_EXCEPTIONS`` If this is set to ``True`` Flask will
121124
not execute the error handlers of HTTP
122125
exceptions but instead treat the

flask/app.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1041,12 +1041,6 @@ def _register_error_handler(self, key, code_or_exception, f):
10411041
self.error_handler_spec.setdefault(key, {}).setdefault(None, []) \
10421042
.append((code_or_exception, f))
10431043

1044-
def get_send_file_options(self, filename):
1045-
# Override: Hooks in SEND_FILE_MAX_AGE_DEFAULT config.
1046-
options = super(Flask, self).get_send_file_options(filename)
1047-
options['cache_timeout'] = self.config['SEND_FILE_MAX_AGE_DEFAULT']
1048-
return options
1049-
10501044
@setupmethod
10511045
def template_filter(self, name=None):
10521046
"""A decorator that is used to register custom template filter.

flask/helpers.py

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -406,7 +406,7 @@ def get_flashed_messages(with_categories=False, category_filter=[]):
406406

407407
def send_file(filename_or_fp, mimetype=None, as_attachment=False,
408408
attachment_filename=None, add_etags=True,
409-
cache_timeout=60 * 60 * 12, conditional=False):
409+
cache_timeout=None, conditional=False):
410410
"""Sends the contents of a file to the client. This will use the
411411
most efficient method available and configured. By default it will
412412
try to use the WSGI server's file_wrapper support. Alternatively
@@ -420,10 +420,6 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False,
420420
guessing requires a `filename` or an `attachment_filename` to be
421421
provided.
422422
423-
Note `get_send_file_options` in :class:`flask.Flask` hooks the
424-
``SEND_FILE_MAX_AGE_DEFAULT`` configuration variable to set the default
425-
cache_timeout.
426-
427423
Please never pass filenames to this function from user sources without
428424
checking them first. Something like this is usually sufficient to
429425
avoid security problems::
@@ -443,6 +439,9 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False,
443439
able to, otherwise attach an etag yourself. This functionality
444440
will be removed in Flask 1.0
445441
442+
.. versionchanged:: 0.9
443+
cache_timeout pulls its default from application config, when None.
444+
446445
:param filename_or_fp: the filename of the file to send. This is
447446
relative to the :attr:`~Flask.root_path` if a
448447
relative path is specified.
@@ -459,7 +458,11 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False,
459458
differs from the file's filename.
460459
:param add_etags: set to `False` to disable attaching of etags.
461460
:param conditional: set to `True` to enable conditional responses.
462-
:param cache_timeout: the timeout in seconds for the headers.
461+
462+
:param cache_timeout: the timeout in seconds for the headers. When `None`
463+
(default), this value is set by
464+
:meth:`~Flask.get_send_file_max_age` of
465+
:data:`~flask.current_app`.
463466
"""
464467
mtime = None
465468
if isinstance(filename_or_fp, basestring):
@@ -523,6 +526,8 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False,
523526
rv.last_modified = int(mtime)
524527

525528
rv.cache_control.public = True
529+
if cache_timeout is None:
530+
cache_timeout = current_app.get_send_file_max_age(filename)
526531
if cache_timeout:
527532
rv.cache_control.max_age = cache_timeout
528533
rv.expires = int(time() + cache_timeout)
@@ -757,26 +762,31 @@ def jinja_loader(self):
757762
return FileSystemLoader(os.path.join(self.root_path,
758763
self.template_folder))
759764

760-
def get_send_file_options(self, filename):
761-
"""Provides keyword arguments to send to :func:`send_from_directory`.
765+
def get_send_file_max_age(self, filename):
766+
"""Provides default cache_timeout for the :func:`send_file` functions.
767+
768+
By default, this function returns ``SEND_FILE_MAX_AGE_DEFAULT`` from
769+
the configuration of :data:`~flask.current_app`.
770+
771+
Static file functions such as :func:`send_from_directory` use this
772+
function, and :func:`send_file` calls this function on
773+
:data:`~flask.current_app` when the given cache_timeout is `None`. If a
774+
cache_timeout is given in :func:`send_file`, that timeout is used;
775+
otherwise, this method is called.
762776
763777
This allows subclasses to change the behavior when sending files based
764778
on the filename. For example, to set the cache timeout for .js files
765-
to 60 seconds (note the options are keywords for :func:`send_file`)::
779+
to 60 seconds::
766780
767781
class MyFlask(flask.Flask):
768-
def get_send_file_options(self, filename):
769-
options = super(MyFlask, self).get_send_file_options(filename)
770-
if filename.lower().endswith('.js'):
771-
options['cache_timeout'] = 60
772-
options['conditional'] = True
773-
return options
782+
def get_send_file_max_age(self, name):
783+
if name.lower().endswith('.js'):
784+
return 60
785+
return flask.Flask.get_send_file_max_age(self, name)
774786
775787
.. versionadded:: 0.9
776788
"""
777-
options = {}
778-
options['cache_timeout'] = current_app.config['SEND_FILE_MAX_AGE_DEFAULT']
779-
return options
789+
return current_app.config['SEND_FILE_MAX_AGE_DEFAULT']
780790

781791
def send_static_file(self, filename):
782792
"""Function used internally to send static files from the static
@@ -786,8 +796,11 @@ def send_static_file(self, filename):
786796
"""
787797
if not self.has_static_folder:
788798
raise RuntimeError('No static folder for this object')
799+
# Ensure get_send_file_max_age is called in all cases.
800+
# Here, we ensure get_send_file_max_age is called for Blueprints.
801+
cache_timeout = self.get_send_file_max_age(filename)
789802
return send_from_directory(self.static_folder, filename,
790-
**self.get_send_file_options(filename))
803+
cache_timeout=cache_timeout)
791804

792805
def open_resource(self, resource, mode='rb'):
793806
"""Opens a resource from the application's resource folder. To see

flask/testsuite/blueprints.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,29 @@ def test_templates_and_static(self):
386386
with flask.Flask(__name__).test_request_context():
387387
self.assert_equal(flask.render_template('nested/nested.txt'), 'I\'m nested')
388388

389+
def test_default_static_cache_timeout(self):
390+
app = flask.Flask(__name__)
391+
class MyBlueprint(flask.Blueprint):
392+
def get_send_file_max_age(self, filename):
393+
return 100
394+
395+
blueprint = MyBlueprint('blueprint', __name__, static_folder='static')
396+
app.register_blueprint(blueprint)
397+
398+
# try/finally, in case other tests use this app for Blueprint tests.
399+
max_age_default = app.config['SEND_FILE_MAX_AGE_DEFAULT']
400+
try:
401+
with app.test_request_context():
402+
unexpected_max_age = 3600
403+
if app.config['SEND_FILE_MAX_AGE_DEFAULT'] == unexpected_max_age:
404+
unexpected_max_age = 7200
405+
app.config['SEND_FILE_MAX_AGE_DEFAULT'] = unexpected_max_age
406+
rv = blueprint.send_static_file('index.html')
407+
cc = parse_cache_control_header(rv.headers['Cache-Control'])
408+
self.assert_equal(cc.max_age, 100)
409+
finally:
410+
app.config['SEND_FILE_MAX_AGE_DEFAULT'] = max_age_default
411+
389412
def test_templates_list(self):
390413
from blueprintapp import app
391414
templates = sorted(app.jinja_env.list_templates())

flask/testsuite/helpers.py

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -237,28 +237,37 @@ def test_static_file(self):
237237
app = flask.Flask(__name__)
238238
# default cache timeout is 12 hours
239239
with app.test_request_context():
240+
# Test with static file handler.
240241
rv = app.send_static_file('index.html')
241242
cc = parse_cache_control_header(rv.headers['Cache-Control'])
242243
self.assert_equal(cc.max_age, 12 * 60 * 60)
244+
# Test again with direct use of send_file utility.
245+
rv = flask.send_file('static/index.html')
246+
cc = parse_cache_control_header(rv.headers['Cache-Control'])
247+
self.assert_equal(cc.max_age, 12 * 60 * 60)
243248
app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 3600
244249
with app.test_request_context():
250+
# Test with static file handler.
245251
rv = app.send_static_file('index.html')
246252
cc = parse_cache_control_header(rv.headers['Cache-Control'])
247253
self.assert_equal(cc.max_age, 3600)
248-
# override get_send_file_options with some new values and check them
254+
# Test again with direct use of send_file utility.
255+
rv = flask.send_file('static/index.html')
256+
cc = parse_cache_control_header(rv.headers['Cache-Control'])
257+
self.assert_equal(cc.max_age, 3600)
249258
class StaticFileApp(flask.Flask):
250-
def get_send_file_options(self, filename):
251-
opts = super(StaticFileApp, self).get_send_file_options(filename)
252-
opts['cache_timeout'] = 10
253-
# this test catches explicit inclusion of the conditional
254-
# keyword arg in the guts
255-
opts['conditional'] = True
256-
return opts
259+
def get_send_file_max_age(self, filename):
260+
return 10
257261
app = StaticFileApp(__name__)
258262
with app.test_request_context():
263+
# Test with static file handler.
259264
rv = app.send_static_file('index.html')
260265
cc = parse_cache_control_header(rv.headers['Cache-Control'])
261266
self.assert_equal(cc.max_age, 10)
267+
# Test again with direct use of send_file utility.
268+
rv = flask.send_file('static/index.html')
269+
cc = parse_cache_control_header(rv.headers['Cache-Control'])
270+
self.assert_equal(cc.max_age, 10)
262271

263272

264273
class LoggingTestCase(FlaskTestCase):

0 commit comments

Comments
 (0)