forked from orbit-oss/flask
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.
This commit is contained in:
parent
146088d580
commit
d94efc6db6
6 changed files with 55 additions and 14 deletions
12
CHANGES
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
|
- View functions can now return a tuple with the first instance being an
|
||||||
instance of :class:`flask.Response`. This allows for returning
|
instance of :class:`flask.Response`. This allows for returning
|
||||||
``jsonify(error="error msg"), 400`` from a view function.
|
``jsonify(error="error msg"), 400`` from a view function.
|
||||||
- :class:`flask.Flask` now provides a `get_static_file_options` hook for
|
- :class:`flask.Flask` now provides a `get_send_file_options` hook for
|
||||||
subclasses to override behavior of serving static files through Flask,
|
subclasses to override behavior of serving static files from Flask when using
|
||||||
optionally by filename, which for example allows changing cache controls by
|
:meth:`flask.Flask.send_static_file` based on keywords in
|
||||||
file extension.
|
: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
|
Version 0.8.1
|
||||||
|
|
|
||||||
|
|
@ -107,6 +107,13 @@ The following configuration values are used internally by Flask:
|
||||||
reject incoming requests with a
|
reject incoming requests with a
|
||||||
content length greater than this by
|
content length greater than this by
|
||||||
returning a 413 status code.
|
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
|
``TRAP_HTTP_EXCEPTIONS`` If this is set to ``True`` Flask will
|
||||||
not execute the error handlers of HTTP
|
not execute the error handlers of HTTP
|
||||||
exceptions but instead treat the
|
exceptions but instead treat the
|
||||||
|
|
@ -267,7 +274,7 @@ configuration::
|
||||||
|
|
||||||
class ProductionConfig(Config):
|
class ProductionConfig(Config):
|
||||||
DATABASE_URI = 'mysql://user@localhost/foo'
|
DATABASE_URI = 'mysql://user@localhost/foo'
|
||||||
|
|
||||||
class DevelopmentConfig(Config):
|
class DevelopmentConfig(Config):
|
||||||
DEBUG = True
|
DEBUG = True
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -249,6 +249,7 @@ class Flask(_PackageBoundObject):
|
||||||
'SESSION_COOKIE_HTTPONLY': True,
|
'SESSION_COOKIE_HTTPONLY': True,
|
||||||
'SESSION_COOKIE_SECURE': False,
|
'SESSION_COOKIE_SECURE': False,
|
||||||
'MAX_CONTENT_LENGTH': None,
|
'MAX_CONTENT_LENGTH': None,
|
||||||
|
'SEND_FILE_MAX_AGE_DEFAULT': 12 * 60 * 60, # 12 hours
|
||||||
'TRAP_BAD_REQUEST_ERRORS': False,
|
'TRAP_BAD_REQUEST_ERRORS': False,
|
||||||
'TRAP_HTTP_EXCEPTIONS': False
|
'TRAP_HTTP_EXCEPTIONS': False
|
||||||
})
|
})
|
||||||
|
|
@ -1020,6 +1021,12 @@ class Flask(_PackageBoundObject):
|
||||||
self.error_handler_spec.setdefault(key, {}).setdefault(None, []) \
|
self.error_handler_spec.setdefault(key, {}).setdefault(None, []) \
|
||||||
.append((code_or_exception, f))
|
.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
|
@setupmethod
|
||||||
def template_filter(self, name=None):
|
def template_filter(self, name=None):
|
||||||
"""A decorator that is used to register custom template filter.
|
"""A decorator that is used to register custom template filter.
|
||||||
|
|
|
||||||
|
|
@ -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
|
guessing requires a `filename` or an `attachment_filename` to be
|
||||||
provided.
|
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
|
Please never pass filenames to this function from user sources without
|
||||||
checking them first. Something like this is usually sufficient to
|
checking them first. Something like this is usually sufficient to
|
||||||
avoid security problems::
|
avoid security problems::
|
||||||
|
|
@ -652,7 +656,7 @@ class _PackageBoundObject(object):
|
||||||
return FileSystemLoader(os.path.join(self.root_path,
|
return FileSystemLoader(os.path.join(self.root_path,
|
||||||
self.template_folder))
|
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`.
|
"""Provides keyword arguments to send to :func:`send_from_directory`.
|
||||||
|
|
||||||
This allows subclasses to change the behavior when sending files based
|
This allows subclasses to change the behavior when sending files based
|
||||||
|
|
@ -660,14 +664,14 @@ class _PackageBoundObject(object):
|
||||||
to 60 seconds (note the options are keywords for :func:`send_file`)::
|
to 60 seconds (note the options are keywords for :func:`send_file`)::
|
||||||
|
|
||||||
class MyFlask(flask.Flask):
|
class MyFlask(flask.Flask):
|
||||||
def get_static_file_options(self, filename):
|
def get_send_file_options(self, filename):
|
||||||
options = super(MyFlask, self).get_static_file_options(filename)
|
options = super(MyFlask, self).get_send_file_options(filename)
|
||||||
if filename.lower().endswith('.js'):
|
if filename.lower().endswith('.js'):
|
||||||
options['cache_timeout'] = 60
|
options['cache_timeout'] = 60
|
||||||
options['conditional'] = True
|
options['conditional'] = True
|
||||||
return options
|
return options
|
||||||
|
|
||||||
.. versionaded:: 0.9
|
.. versionadded:: 0.9
|
||||||
"""
|
"""
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
|
@ -680,7 +684,7 @@ class _PackageBoundObject(object):
|
||||||
if not self.has_static_folder:
|
if not self.has_static_folder:
|
||||||
raise RuntimeError('No static folder for this object')
|
raise RuntimeError('No static folder for this object')
|
||||||
return send_from_directory(self.static_folder, filename,
|
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'):
|
def open_resource(self, resource, mode='rb'):
|
||||||
"""Opens a resource from the application's resource folder. To see
|
"""Opens a resource from the application's resource folder. To see
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import unittest
|
||||||
import warnings
|
import warnings
|
||||||
from flask.testsuite import FlaskTestCase, emits_module_deprecation_warning
|
from flask.testsuite import FlaskTestCase, emits_module_deprecation_warning
|
||||||
from werkzeug.exceptions import NotFound
|
from werkzeug.exceptions import NotFound
|
||||||
|
from werkzeug.http import parse_cache_control_header
|
||||||
from jinja2 import TemplateNotFound
|
from jinja2 import TemplateNotFound
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -357,6 +358,19 @@ class BlueprintTestCase(FlaskTestCase):
|
||||||
rv = c.get('/admin/static/css/test.css')
|
rv = c.get('/admin/static/css/test.css')
|
||||||
self.assert_equal(rv.data.strip(), '/* nested file */')
|
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():
|
with app.test_request_context():
|
||||||
self.assert_equal(flask.url_for('admin.static', filename='test.txt'),
|
self.assert_equal(flask.url_for('admin.static', filename='test.txt'),
|
||||||
'/admin/static/test.txt')
|
'/admin/static/test.txt')
|
||||||
|
|
|
||||||
|
|
@ -206,15 +206,20 @@ class SendfileTestCase(FlaskTestCase):
|
||||||
|
|
||||||
def test_static_file(self):
|
def test_static_file(self):
|
||||||
app = flask.Flask(__name__)
|
app = flask.Flask(__name__)
|
||||||
# default cache timeout is 12 hours (hard-coded)
|
# default cache timeout is 12 hours
|
||||||
with app.test_request_context():
|
with app.test_request_context():
|
||||||
rv = app.send_static_file('index.html')
|
rv = app.send_static_file('index.html')
|
||||||
cc = parse_cache_control_header(rv.headers['Cache-Control'])
|
cc = parse_cache_control_header(rv.headers['Cache-Control'])
|
||||||
self.assert_equal(cc.max_age, 12 * 60 * 60)
|
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):
|
class StaticFileApp(flask.Flask):
|
||||||
def get_static_file_options(self, filename):
|
def get_send_file_options(self, filename):
|
||||||
opts = super(StaticFileApp, self).get_static_file_options(filename)
|
opts = super(StaticFileApp, self).get_send_file_options(filename)
|
||||||
opts['cache_timeout'] = 10
|
opts['cache_timeout'] = 10
|
||||||
# this test catches explicit inclusion of the conditional
|
# this test catches explicit inclusion of the conditional
|
||||||
# keyword arg in the guts
|
# keyword arg in the guts
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue