Skip to content
Browse files

Expose send_file max-age as config value, #433.

Need to add the same hook in a Blueprint, but this is the first such
case where we need app.config in the Blueprint.
  • Loading branch information...
1 parent 146088d commit d94efc6db63516b7f72e58c34ae33700f3d9c4fb @rduplain rduplain committed
Showing with 55 additions and 14 deletions.
  1. +8 −4 CHANGES
  2. +8 −1 docs/config.rst
  3. +7 −0 flask/app.py
  4. +9 −5 flask/helpers.py
  5. +14 −0 flask/testsuite/blueprints.py
  6. +9 −4 flask/testsuite/helpers.py
View
12 CHANGES
@@ -48,10 +48,14 @@ Relase date to be decided, codename to be chosen.
- View functions can now return a tuple with the first instance being an
instance of :class:`flask.Response`. This allows for returning
``jsonify(error="error msg"), 400`` from a view function.
-- :class:`flask.Flask` now provides a `get_static_file_options` hook for
- subclasses to override behavior of serving static files through Flask,
- optionally by filename, which for example allows changing cache controls by
- file extension.
+- :class:`flask.Flask` now provides a `get_send_file_options` hook for
+ subclasses to override behavior of serving static files from Flask when using
+ :meth:`flask.Flask.send_static_file` based on keywords in
+ :func:`flask.helpers.send_file`. This hook is provided a filename, which for
+ example allows changing cache controls by file extension. The default
+ max-age for `send_static_file` can be configured through a new
+ ``SEND_FILE_MAX_AGE_DEFAULT`` configuration variable, regardless of whether
+ the `get_send_file_options` hook is used.
Version 0.8.1
View
9 docs/config.rst
@@ -107,6 +107,13 @@ The following configuration values are used internally by Flask:
reject incoming requests with a
content length greater than this by
returning a 413 status code.
+``SEND_FILE_MAX_AGE_DEFAULT``: Default cache control max age to use with
+ :meth:`flask.Flask.send_static_file`, in
+ seconds. Override this value on a per-file
+ basis using the
+ :meth:`flask.Flask.get_send_file_options` and
+ :meth:`flask.Blueprint.get_send_file_options`
+ hooks. Defaults to 43200 (12 hours).
``TRAP_HTTP_EXCEPTIONS`` If this is set to ``True`` Flask will
not execute the error handlers of HTTP
exceptions but instead treat the
@@ -267,7 +274,7 @@ configuration::
class ProductionConfig(Config):
DATABASE_URI = 'mysql://user@localhost/foo'
-
+
class DevelopmentConfig(Config):
DEBUG = True
View
7 flask/app.py
@@ -249,6 +249,7 @@ class Flask(_PackageBoundObject):
'SESSION_COOKIE_HTTPONLY': True,
'SESSION_COOKIE_SECURE': False,
'MAX_CONTENT_LENGTH': None,
+ 'SEND_FILE_MAX_AGE_DEFAULT': 12 * 60 * 60, # 12 hours
'TRAP_BAD_REQUEST_ERRORS': False,
'TRAP_HTTP_EXCEPTIONS': False
})
@@ -1020,6 +1021,12 @@ def _register_error_handler(self, key, code_or_exception, f):
self.error_handler_spec.setdefault(key, {}).setdefault(None, []) \
.append((code_or_exception, f))
+ def get_send_file_options(self, filename):
+ # Override: Hooks in SEND_FILE_MAX_AGE_DEFAULT config.
+ options = super(Flask, self).get_send_file_options(filename)
+ options['cache_timeout'] = self.config['SEND_FILE_MAX_AGE_DEFAULT']
+ return options
+
@setupmethod
def template_filter(self, name=None):
"""A decorator that is used to register custom template filter.
View
14 flask/helpers.py
@@ -319,6 +319,10 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False,
guessing requires a `filename` or an `attachment_filename` to be
provided.
+ Note `get_send_file_options` in :class:`flask.Flask` hooks the
+ ``SEND_FILE_MAX_AGE_DEFAULT`` configuration variable to set the default
+ cache_timeout.
+
Please never pass filenames to this function from user sources without
checking them first. Something like this is usually sufficient to
avoid security problems::
@@ -652,7 +656,7 @@ def jinja_loader(self):
return FileSystemLoader(os.path.join(self.root_path,
self.template_folder))
- def get_static_file_options(self, filename):
+ def get_send_file_options(self, filename):
"""Provides keyword arguments to send to :func:`send_from_directory`.
This allows subclasses to change the behavior when sending files based
@@ -660,14 +664,14 @@ def get_static_file_options(self, filename):
to 60 seconds (note the options are keywords for :func:`send_file`)::
class MyFlask(flask.Flask):
- def get_static_file_options(self, filename):
- options = super(MyFlask, self).get_static_file_options(filename)
+ def get_send_file_options(self, filename):
+ options = super(MyFlask, self).get_send_file_options(filename)
if filename.lower().endswith('.js'):
options['cache_timeout'] = 60
options['conditional'] = True
return options
- .. versionaded:: 0.9
+ .. versionadded:: 0.9
"""
return {}
@@ -680,7 +684,7 @@ def send_static_file(self, filename):
if not self.has_static_folder:
raise RuntimeError('No static folder for this object')
return send_from_directory(self.static_folder, filename,
- **self.get_static_file_options(filename))
+ **self.get_send_file_options(filename))
def open_resource(self, resource, mode='rb'):
"""Opens a resource from the application's resource folder. To see
View
14 flask/testsuite/blueprints.py
@@ -16,6 +16,7 @@
import warnings
from flask.testsuite import FlaskTestCase, emits_module_deprecation_warning
from werkzeug.exceptions import NotFound
+from werkzeug.http import parse_cache_control_header
from jinja2 import TemplateNotFound
@@ -357,6 +358,19 @@ def test_templates_and_static(self):
rv = c.get('/admin/static/css/test.css')
self.assert_equal(rv.data.strip(), '/* nested file */')
+ # try/finally, in case other tests use this app for Blueprint tests.
+ max_age_default = app.config['SEND_FILE_MAX_AGE_DEFAULT']
+ try:
+ expected_max_age = 3600
+ if app.config['SEND_FILE_MAX_AGE_DEFAULT'] == expected_max_age:
+ expected_max_age = 7200
+ app.config['SEND_FILE_MAX_AGE_DEFAULT'] = expected_max_age
+ rv = c.get('/admin/static/css/test.css')
+ cc = parse_cache_control_header(rv.headers['Cache-Control'])
+ self.assert_equal(cc.max_age, expected_max_age)
+ finally:
+ app.config['SEND_FILE_MAX_AGE_DEFAULT'] = max_age_default
+
with app.test_request_context():
self.assert_equal(flask.url_for('admin.static', filename='test.txt'),
'/admin/static/test.txt')
View
13 flask/testsuite/helpers.py
@@ -206,15 +206,20 @@ def test_attachment(self):
def test_static_file(self):
app = flask.Flask(__name__)
- # default cache timeout is 12 hours (hard-coded)
+ # default cache timeout is 12 hours
with app.test_request_context():
rv = app.send_static_file('index.html')
cc = parse_cache_control_header(rv.headers['Cache-Control'])
self.assert_equal(cc.max_age, 12 * 60 * 60)
- # override get_static_file_options with some new values and check them
+ app.config['SEND_FILE_MAX_AGE_DEFAULT'] = 3600
+ with app.test_request_context():
+ rv = app.send_static_file('index.html')
+ cc = parse_cache_control_header(rv.headers['Cache-Control'])
+ self.assert_equal(cc.max_age, 3600)
+ # override get_send_file_options with some new values and check them
class StaticFileApp(flask.Flask):
- def get_static_file_options(self, filename):
- opts = super(StaticFileApp, self).get_static_file_options(filename)
+ def get_send_file_options(self, filename):
+ opts = super(StaticFileApp, self).get_send_file_options(filename)
opts['cache_timeout'] = 10
# this test catches explicit inclusion of the conditional
# keyword arg in the guts

0 comments on commit d94efc6

Please sign in to comment.
Something went wrong with that request. Please try again.