diff --git a/CHANGES b/CHANGES index 795c2eb8..1440781e 100644 --- a/CHANGES +++ b/CHANGES @@ -17,6 +17,8 @@ Codename to be decided, release date to be announced. ``autoescape`` tag. - refactored Flask internally. It now consists of more than a single file. +- :func:`flask.send_file` now emits etags and has the ability to + do conditional responses builtin. Version 0.4 ----------- diff --git a/flask/app.py b/flask/app.py index e7ed5bcb..062d4289 100644 --- a/flask/app.py +++ b/flask/app.py @@ -328,7 +328,7 @@ class Flask(_PackageBoundObject): return PackageLoader(self.import_name) def init_jinja_globals(self): - """Callde directly after the environment was created to inject + """Called directly after the environment was created to inject some defaults (like `url_for`, `get_flashed_messages` and the `tojson` filter. diff --git a/flask/helpers.py b/flask/helpers.py index 5a6e3966..a34e055e 100644 --- a/flask/helpers.py +++ b/flask/helpers.py @@ -12,6 +12,8 @@ import os import sys import mimetypes +from time import time +from zlib import adler32 # try to load the best simplejson implementation available. If JSON # is not installed, we add a failing class. @@ -24,7 +26,7 @@ except ImportError: except ImportError: json_available = False -from werkzeug import Headers, wrap_file +from werkzeug import Headers, wrap_file, is_resource_modified from flask.globals import session, _request_ctx_stack, current_app, request from flask.wrappers import Response @@ -199,7 +201,8 @@ def get_flashed_messages(with_categories=False): def send_file(filename_or_fp, mimetype=None, as_attachment=False, - attachment_filename=None): + attachment_filename=None, add_etags=True, + cache_timeout=60 * 60 * 12, conditional=False): """Sends the contents of a file to the client. This will use the most efficient method available and configured. By default it will try to use the WSGI server's file_wrapper support. Alternatively @@ -220,6 +223,10 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, .. versionadded:: 0.2 + .. versionadded:: 0.5 + The `add_etags`, `cache_timeout` and `conditional` parameters were added. + The default behaviour is now to attach etags. + :param filename_or_fp: the filename of the file to send. This is relative to the :attr:`~Flask.root_path` if a relative path is specified. @@ -232,6 +239,9 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, a ``Content-Disposition: attachment`` header. :param attachment_filename: the filename for the attachment if it differs from the file's filename. + :param add_etags: set to `False` to disable attaching of etags. + :param conditional: set to `True` to enable conditional responses. + :param cache_timeout: the timeout in seconds for the headers. """ if isinstance(filename_or_fp, basestring): filename = filename_or_fp @@ -266,8 +276,27 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False, file = open(filename, 'rb') data = wrap_file(request.environ, file) - return Response(data, mimetype=mimetype, headers=headers, - direct_passthrough=True) + rv = Response(data, mimetype=mimetype, headers=headers, + direct_passthrough=True) + + rv.cache_control.public = True + if cache_timeout: + rv.cache_control.max_age = cache_timeout + rv.expires = int(time() + cache_timeout) + + if add_etags and filename is not None: + rv.set_etag('flask-%s-%s-%s' % ( + os.path.getmtime(filename), + os.path.getsize(filename), + adler32(filename) & 0xffffffff + )) + if conditional: + rv = rv.make_conditional(request) + # make sure we don't send x-sendfile for servers that + # ignore the 304 status code for x-sendfile. + if rv.status_code == 304: + rv.headers.pop('x-sendfile', None) + return rv def render_template(template_name, **context):