forked from orbit-oss/flask
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
|
- Correctly invoke response handlers for both regular request dispatching as
|
||||||
well as error handlers.
|
well as error handlers.
|
||||||
- Disable logger propagation by default for the app logger.
|
- Disable logger propagation by default for the app logger.
|
||||||
|
- Add support for range requests in ``send_file``.
|
||||||
|
|
||||||
Version 0.11.2
|
Version 0.11.2
|
||||||
--------------
|
--------------
|
||||||
|
|
|
||||||
|
|
@ -25,8 +25,9 @@ try:
|
||||||
except ImportError:
|
except ImportError:
|
||||||
from urlparse import quote as url_quote
|
from urlparse import quote as url_quote
|
||||||
|
|
||||||
from werkzeug.datastructures import Headers
|
from werkzeug.datastructures import Headers, Range
|
||||||
from werkzeug.exceptions import BadRequest, NotFound
|
from werkzeug.exceptions import BadRequest, NotFound, \
|
||||||
|
RequestedRangeNotSatisfiable
|
||||||
|
|
||||||
# this was moved in 0.7
|
# this was moved in 0.7
|
||||||
try:
|
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
|
ETags will also be attached automatically if a `filename` is provided. You
|
||||||
can turn this off by setting `add_etags=False`.
|
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;
|
Please never pass filenames to this function from user sources;
|
||||||
you should use :func:`send_from_directory` instead.
|
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.
|
If a file was passed, this overrides its mtime.
|
||||||
"""
|
"""
|
||||||
mtime = None
|
mtime = None
|
||||||
|
fsize = None
|
||||||
if isinstance(filename_or_fp, string_types):
|
if isinstance(filename_or_fp, string_types):
|
||||||
filename = filename_or_fp
|
filename = filename_or_fp
|
||||||
if not os.path.isabs(filename):
|
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:
|
if file is not None:
|
||||||
file.close()
|
file.close()
|
||||||
headers['X-Sendfile'] = filename
|
headers['X-Sendfile'] = filename
|
||||||
headers['Content-Length'] = os.path.getsize(filename)
|
fsize = os.path.getsize(filename)
|
||||||
|
headers['Content-Length'] = fsize
|
||||||
data = None
|
data = None
|
||||||
else:
|
else:
|
||||||
if file is None:
|
if file is None:
|
||||||
file = open(filename, 'rb')
|
file = open(filename, 'rb')
|
||||||
mtime = os.path.getmtime(filename)
|
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)
|
data = wrap_file(request.environ, file)
|
||||||
|
|
||||||
rv = current_app.response_class(data, mimetype=mimetype, headers=headers,
|
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 '
|
warn('Access %s failed, maybe it does not exist, so ignore etags in '
|
||||||
'headers' % filename, stacklevel=2)
|
'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)
|
rv = rv.make_conditional(request)
|
||||||
# make sure we don't send x-sendfile for servers that
|
# make sure we don't send x-sendfile for servers that
|
||||||
# ignore the 304 status code for x-sendfile.
|
# ignore the 304 status code for x-sendfile.
|
||||||
if rv.status_code == 304:
|
if rv.status_code == 304:
|
||||||
rv.headers.pop('x-sendfile', None)
|
rv.headers.pop('x-sendfile', None)
|
||||||
return rv
|
return rv
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,10 @@ import pytest
|
||||||
import os
|
import os
|
||||||
import uuid
|
import uuid
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
import flask
|
import flask
|
||||||
from logging import StreamHandler
|
from logging import StreamHandler
|
||||||
|
from werkzeug.datastructures import Range
|
||||||
from werkzeug.exceptions import BadRequest, NotFound
|
from werkzeug.exceptions import BadRequest, NotFound
|
||||||
from werkzeug.http import parse_cache_control_header, parse_options_header
|
from werkzeug.http import parse_cache_control_header, parse_options_header
|
||||||
from werkzeug.http import http_date
|
from werkzeug.http import http_date
|
||||||
|
|
@ -462,6 +464,69 @@ class TestSendfile(object):
|
||||||
assert 'x-sendfile' not in rv.headers
|
assert 'x-sendfile' not in rv.headers
|
||||||
rv.close()
|
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):
|
def test_attachment(self):
|
||||||
app = flask.Flask(__name__)
|
app = flask.Flask(__name__)
|
||||||
with app.test_request_context():
|
with app.test_request_context():
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue