mirror of https://github.com/pallets/flask.git
Added support for send_file
This commit is contained in:
parent
4156bd412f
commit
2d87e9bc37
1
CHANGES
1
CHANGES
|
@ -15,6 +15,7 @@ Version 0.2
|
|||
view function.
|
||||
- server listens on 127.0.0.1 by default now to fix issues with chrome.
|
||||
- added external URL support.
|
||||
- added support for :func:`~flask.send_file`
|
||||
|
||||
Version 0.1
|
||||
-----------
|
||||
|
|
|
@ -213,6 +213,8 @@ Useful Functions and Classes
|
|||
|
||||
.. autofunction:: redirect
|
||||
|
||||
.. autofunction:: send_file
|
||||
|
||||
.. autofunction:: escape
|
||||
|
||||
.. autoclass:: Markup
|
||||
|
|
75
flask.py
75
flask.py
|
@ -12,12 +12,13 @@
|
|||
from __future__ import with_statement
|
||||
import os
|
||||
import sys
|
||||
import mimetypes
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
from jinja2 import Environment, PackageLoader, FileSystemLoader
|
||||
from werkzeug import Request as RequestBase, Response as ResponseBase, \
|
||||
LocalStack, LocalProxy, create_environ, SharedDataMiddleware, \
|
||||
ImmutableDict, cached_property
|
||||
ImmutableDict, cached_property, wrap_file, Headers
|
||||
from werkzeug.routing import Map, Rule
|
||||
from werkzeug.exceptions import HTTPException
|
||||
from werkzeug.contrib.securecookie import SecureCookie
|
||||
|
@ -235,6 +236,71 @@ def jsonify(*args, **kwargs):
|
|||
indent=None if request.is_xhr else 2), mimetype='application/json')
|
||||
|
||||
|
||||
def send_file(filename_or_fp, mimetype=None, as_attachment=False,
|
||||
attachment_filename=None):
|
||||
"""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
|
||||
you can set the application's :attr:`~Flask.use_x_sendfile` attribute
|
||||
to ``True`` to directly emit an `X-Sendfile` header. This however
|
||||
requires support of the underlying webserver for `X-Sendfile`.
|
||||
|
||||
By default it will try to guess the mimetype for you, but you can
|
||||
also explicitly provide one. For extra security you probably want
|
||||
to sent certain files as attachment (HTML for instance).
|
||||
|
||||
.. versionadded:: 0.2
|
||||
|
||||
: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.
|
||||
Alternatively a file object might be provided
|
||||
in which case `X-Sendfile` might not work and
|
||||
fall back to the traditional method.
|
||||
:param mimetype: the mimetype of the file if provided, otherwise
|
||||
auto detection happens.
|
||||
:param as_attachment: set to `True` if you want to send this file with
|
||||
a ``Content-Disposition: attachment`` header.
|
||||
:param attachment_filename: the filename for the attachment if it
|
||||
differs from the file's filename.
|
||||
"""
|
||||
if isinstance(filename_or_fp, basestring):
|
||||
filename = filename_or_fp
|
||||
file = None
|
||||
else:
|
||||
file = filename_or_fp
|
||||
filename = getattr(file, 'name', None)
|
||||
if filename is not None:
|
||||
filename = os.path.join(current_app.root_path, filename)
|
||||
if mimetype is None and (filename or attachment_filename):
|
||||
mimetype = mimetypes.guess_type(filename or attachment_filename)[0]
|
||||
if mimetype is None:
|
||||
mimetype = 'application/octet-stream'
|
||||
|
||||
headers = Headers()
|
||||
if as_attachment:
|
||||
if attachment_filename is None:
|
||||
if filename is None:
|
||||
raise TypeError('filename unavailable, required for '
|
||||
'sending as attachment')
|
||||
attachment_filename = os.path.basename(filename)
|
||||
headers.add('Content-Disposition', 'attachment',
|
||||
filename=attachment_filename)
|
||||
|
||||
if current_app.use_x_sendfile and filename:
|
||||
if file is not None:
|
||||
file.close()
|
||||
headers['X-Sendfile'] = filename
|
||||
data = None
|
||||
else:
|
||||
if file is None:
|
||||
file = open(filename, 'rb')
|
||||
data = wrap_file(request.environ, file)
|
||||
|
||||
return Response(data, mimetype=mimetype, headers=headers,
|
||||
direct_passthrough=True)
|
||||
|
||||
|
||||
def render_template(template_name, **context):
|
||||
"""Renders a template from the template folder with the given
|
||||
context.
|
||||
|
@ -344,6 +410,13 @@ class Flask(object):
|
|||
#: permanent session survive for roughly one month.
|
||||
permanent_session_lifetime = timedelta(days=31)
|
||||
|
||||
#: Enable this if you want to use the X-Sendfile feature. Keep in
|
||||
#: mind that the server has to support this. This only affects files
|
||||
#: sent with the :func:`send_file` method.
|
||||
#:
|
||||
#: .. versionadded:: 0.2
|
||||
use_x_sendfile = False
|
||||
|
||||
#: options that are passed directly to the Jinja2 environment
|
||||
jinja_options = ImmutableDict(
|
||||
autoescape=True,
|
||||
|
|
|
@ -17,7 +17,8 @@ import flask
|
|||
import unittest
|
||||
import tempfile
|
||||
from datetime import datetime
|
||||
from werkzeug import parse_date
|
||||
from werkzeug import parse_date, parse_options_header
|
||||
from cStringIO import StringIO
|
||||
|
||||
|
||||
example_path = os.path.join(os.path.dirname(__file__), '..', 'examples')
|
||||
|
@ -382,6 +383,87 @@ class TemplatingTestCase(unittest.TestCase):
|
|||
assert rv.data == 'dcba'
|
||||
|
||||
|
||||
class SendfileTestCase(unittest.TestCase):
|
||||
|
||||
def test_send_file_regular(self):
|
||||
app = flask.Flask(__name__)
|
||||
with app.test_request_context():
|
||||
rv = flask.send_file('static/index.html')
|
||||
assert rv.direct_passthrough
|
||||
assert rv.mimetype == 'text/html'
|
||||
with app.open_resource('static/index.html') as f:
|
||||
assert rv.data == f.read()
|
||||
|
||||
def test_send_file_xsendfile(self):
|
||||
app = flask.Flask(__name__)
|
||||
app.use_x_sendfile = True
|
||||
with app.test_request_context():
|
||||
rv = flask.send_file('static/index.html')
|
||||
assert rv.direct_passthrough
|
||||
assert 'x-sendfile' in rv.headers
|
||||
assert rv.headers['x-sendfile'] == \
|
||||
os.path.join(app.root_path, 'static/index.html')
|
||||
assert rv.mimetype == 'text/html'
|
||||
|
||||
def test_send_file_object(self):
|
||||
app = flask.Flask(__name__)
|
||||
with app.test_request_context():
|
||||
f = open(os.path.join(app.root_path, 'static/index.html'))
|
||||
rv = flask.send_file(f)
|
||||
with app.open_resource('static/index.html') as f:
|
||||
assert rv.data == f.read()
|
||||
assert rv.mimetype == 'text/html'
|
||||
|
||||
app.use_x_sendfile = True
|
||||
with app.test_request_context():
|
||||
f = open(os.path.join(app.root_path, 'static/index.html'))
|
||||
rv = flask.send_file(f)
|
||||
assert rv.mimetype == 'text/html'
|
||||
assert 'x-sendfile' in rv.headers
|
||||
assert rv.headers['x-sendfile'] == \
|
||||
os.path.join(app.root_path, 'static/index.html')
|
||||
|
||||
app.use_x_sendfile = False
|
||||
with app.test_request_context():
|
||||
f = StringIO('Test')
|
||||
rv = flask.send_file(f)
|
||||
assert rv.data == 'Test'
|
||||
assert rv.mimetype == 'application/octet-stream'
|
||||
f = StringIO('Test')
|
||||
rv = flask.send_file(f, mimetype='text/plain')
|
||||
assert rv.data == 'Test'
|
||||
assert rv.mimetype == 'text/plain'
|
||||
|
||||
app.use_x_sendfile = True
|
||||
with app.test_request_context():
|
||||
f = StringIO('Test')
|
||||
rv = flask.send_file(f)
|
||||
assert 'x-sendfile' not in rv.headers
|
||||
|
||||
def test_attachment(self):
|
||||
app = flask.Flask(__name__)
|
||||
with app.test_request_context():
|
||||
f = open(os.path.join(app.root_path, 'static/index.html'))
|
||||
rv = flask.send_file(f, as_attachment=True)
|
||||
value, options = parse_options_header(rv.headers['Content-Disposition'])
|
||||
assert value == 'attachment'
|
||||
|
||||
with app.test_request_context():
|
||||
assert options['filename'] == 'index.html'
|
||||
rv = flask.send_file('static/index.html', as_attachment=True)
|
||||
value, options = parse_options_header(rv.headers['Content-Disposition'])
|
||||
assert value == 'attachment'
|
||||
assert options['filename'] == 'index.html'
|
||||
|
||||
with app.test_request_context():
|
||||
rv = flask.send_file(StringIO('Test'), as_attachment=True,
|
||||
attachment_filename='index.txt')
|
||||
assert rv.mimetype == 'text/plain'
|
||||
value, options = parse_options_header(rv.headers['Content-Disposition'])
|
||||
assert value == 'attachment'
|
||||
assert options['filename'] == 'index.txt'
|
||||
|
||||
|
||||
def suite():
|
||||
from minitwit_tests import MiniTwitTestCase
|
||||
from flaskr_tests import FlaskrTestCase
|
||||
|
@ -389,6 +471,7 @@ def suite():
|
|||
suite.addTest(unittest.makeSuite(ContextTestCase))
|
||||
suite.addTest(unittest.makeSuite(BasicFunctionalityTestCase))
|
||||
suite.addTest(unittest.makeSuite(TemplatingTestCase))
|
||||
suite.addTest(unittest.makeSuite(SendfileTestCase))
|
||||
if flask.json_available:
|
||||
suite.addTest(unittest.makeSuite(JSONTestCase))
|
||||
suite.addTest(unittest.makeSuite(MiniTwitTestCase))
|
||||
|
|
Loading…
Reference in New Issue