make use of range requests if available in werkzeug (#2031)
* make use of range requests if available in werkzeug * different logic for testing werkzeug functionality
This commit is contained in:
parent
f3d661de66
commit
7186a5aaf5
3 changed files with 93 additions and 9 deletions
1
CHANGES
1
CHANGES
|
|
@ -18,6 +18,7 @@ Version 0.12
|
|||
- Correctly invoke response handlers for both regular request dispatching as
|
||||
well as error handlers.
|
||||
- Disable logger propagation by default for the app logger.
|
||||
- Add support for range requests in ``send_file``.
|
||||
|
||||
Version 0.11.2
|
||||
--------------
|
||||
|
|
|
|||
|
|
@ -25,8 +25,9 @@ try:
|
|||
except ImportError:
|
||||
from urlparse import quote as url_quote
|
||||
|
||||
from werkzeug.datastructures import Headers
|
||||
from werkzeug.exceptions import BadRequest, NotFound
|
||||
from werkzeug.datastructures import Headers, Range
|
||||
from werkzeug.exceptions import BadRequest, NotFound, \
|
||||
RequestedRangeNotSatisfiable
|
||||
|
||||
# this was moved in 0.7
|
||||
try:
|
||||
|
|
@ -446,6 +447,10 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False,
|
|||
ETags will also be attached automatically if a `filename` is provided. You
|
||||
can turn this off by setting `add_etags=False`.
|
||||
|
||||
If `conditional=True` and `filename` is provided, this method will try to
|
||||
upgrade the response stream to support range requests. This will allow
|
||||
the request to be answered with partial content response.
|
||||
|
||||
Please never pass filenames to this function from user sources;
|
||||
you should use :func:`send_from_directory` instead.
|
||||
|
||||
|
|
@ -500,6 +505,7 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False,
|
|||
If a file was passed, this overrides its mtime.
|
||||
"""
|
||||
mtime = None
|
||||
fsize = None
|
||||
if isinstance(filename_or_fp, string_types):
|
||||
filename = filename_or_fp
|
||||
if not os.path.isabs(filename):
|
||||
|
|
@ -535,13 +541,15 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False,
|
|||
if file is not None:
|
||||
file.close()
|
||||
headers['X-Sendfile'] = filename
|
||||
headers['Content-Length'] = os.path.getsize(filename)
|
||||
fsize = os.path.getsize(filename)
|
||||
headers['Content-Length'] = fsize
|
||||
data = None
|
||||
else:
|
||||
if file is None:
|
||||
file = open(filename, 'rb')
|
||||
mtime = os.path.getmtime(filename)
|
||||
headers['Content-Length'] = os.path.getsize(filename)
|
||||
fsize = os.path.getsize(filename)
|
||||
headers['Content-Length'] = fsize
|
||||
data = wrap_file(request.environ, file)
|
||||
|
||||
rv = current_app.response_class(data, mimetype=mimetype, headers=headers,
|
||||
|
|
@ -575,12 +583,22 @@ def send_file(filename_or_fp, mimetype=None, as_attachment=False,
|
|||
warn('Access %s failed, maybe it does not exist, so ignore etags in '
|
||||
'headers' % filename, stacklevel=2)
|
||||
|
||||
if conditional:
|
||||
if conditional:
|
||||
if callable(getattr(Range, 'to_content_range_header', None)):
|
||||
# Werkzeug supports Range Requests
|
||||
# Remove this test when support for Werkzeug <0.12 is dropped
|
||||
try:
|
||||
rv = rv.make_conditional(request, accept_ranges=True,
|
||||
complete_length=fsize)
|
||||
except RequestedRangeNotSatisfiable:
|
||||
file.close()
|
||||
raise
|
||||
else:
|
||||
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)
|
||||
# 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
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -14,8 +14,10 @@ import pytest
|
|||
import os
|
||||
import uuid
|
||||
import datetime
|
||||
|
||||
import flask
|
||||
from logging import StreamHandler
|
||||
from werkzeug.datastructures import Range
|
||||
from werkzeug.exceptions import BadRequest, NotFound
|
||||
from werkzeug.http import parse_cache_control_header, parse_options_header
|
||||
from werkzeug.http import http_date
|
||||
|
|
@ -462,6 +464,69 @@ class TestSendfile(object):
|
|||
assert 'x-sendfile' not in rv.headers
|
||||
rv.close()
|
||||
|
||||
@pytest.mark.skipif(
|
||||
not callable(getattr(Range, 'to_content_range_header', None)),
|
||||
reason="not implement within werkzeug"
|
||||
)
|
||||
def test_send_file_range_request(self):
|
||||
app = flask.Flask(__name__)
|
||||
|
||||
@app.route('/')
|
||||
def index():
|
||||
return flask.send_file('static/index.html', conditional=True)
|
||||
|
||||
c = app.test_client()
|
||||
|
||||
rv = c.get('/', headers={'Range': 'bytes=4-15'})
|
||||
assert rv.status_code == 206
|
||||
with app.open_resource('static/index.html') as f:
|
||||
assert rv.data == f.read()[4:16]
|
||||
rv.close()
|
||||
|
||||
rv = c.get('/', headers={'Range': 'bytes=4-'})
|
||||
assert rv.status_code == 206
|
||||
with app.open_resource('static/index.html') as f:
|
||||
assert rv.data == f.read()[4:]
|
||||
rv.close()
|
||||
|
||||
rv = c.get('/', headers={'Range': 'bytes=4-1000'})
|
||||
assert rv.status_code == 206
|
||||
with app.open_resource('static/index.html') as f:
|
||||
assert rv.data == f.read()[4:]
|
||||
rv.close()
|
||||
|
||||
rv = c.get('/', headers={'Range': 'bytes=-10'})
|
||||
assert rv.status_code == 206
|
||||
with app.open_resource('static/index.html') as f:
|
||||
assert rv.data == f.read()[-10:]
|
||||
rv.close()
|
||||
|
||||
rv = c.get('/', headers={'Range': 'bytes=1000-'})
|
||||
assert rv.status_code == 416
|
||||
rv.close()
|
||||
|
||||
rv = c.get('/', headers={'Range': 'bytes=-'})
|
||||
assert rv.status_code == 416
|
||||
rv.close()
|
||||
|
||||
rv = c.get('/', headers={'Range': 'somethingsomething'})
|
||||
assert rv.status_code == 416
|
||||
rv.close()
|
||||
|
||||
last_modified = datetime.datetime.fromtimestamp(os.path.getmtime(
|
||||
os.path.join(app.root_path, 'static/index.html'))).replace(
|
||||
microsecond=0)
|
||||
|
||||
rv = c.get('/', headers={'Range': 'bytes=4-15',
|
||||
'If-Range': http_date(last_modified)})
|
||||
assert rv.status_code == 206
|
||||
rv.close()
|
||||
|
||||
rv = c.get('/', headers={'Range': 'bytes=4-15', 'If-Range': http_date(
|
||||
datetime.datetime(1999, 1, 1))})
|
||||
assert rv.status_code == 200
|
||||
rv.close()
|
||||
|
||||
def test_attachment(self):
|
||||
app = flask.Flask(__name__)
|
||||
with app.test_request_context():
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue