Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support partial request for media resource #1050

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions docs/features.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1768,6 +1768,28 @@ configuration. Enable ``AUTO_COLLAPSE_MULTI_KEYS`` and ``AUTO_CREATE_LISTS``
to make this possible. This allows to send multiple values for one key in
``multipart/form-data`` requests and in this way upload a list of files.

.. _partial_request:

Partial request for media resource
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
The partial request provides an ability to download a part of a file.
You can also pause and resume when you are downloading without restarting
your download. To use it, make sure you have ``Range`` in your request header.

.. code-block:: console

$ curl http://localhost/media/yourfilename -i -H "Range: bytes=0-10"
HTTP/1.1 206 PARTIAL CONTENT
Date: Sun, 20 Aug 2017 14:26:42 GMT
Content-Type: audio/mp4
Content-Length: 11
Connection: keep-alive
Content-Range: bytes 0-10/23671
Last-Modified: Sat, 19 Aug 2017 03:25:36 GMT
Accept-Ranges: bytes

ftypmp4%

.. _geojson_feature:

GeoJSON
Expand Down
94 changes: 72 additions & 22 deletions eve/endpoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
:copyright: (c) 2017 by Nicola Iarocci.
:license: BSD, see LICENSE for more details.
"""
import re

from bson import tz_util
from flask import abort, request, current_app as app, Response

Expand Down Expand Up @@ -174,28 +176,76 @@ def media_endpoint(_id):

.. versionadded:: 0.6
"""
file_ = app.media.get(_id)
if file_ is None:
return abort(404)

if_modified_since = weak_date(request.headers.get('If-Modified-Since'))
if if_modified_since is not None:
if if_modified_since.tzinfo is None:
if_modified_since = if_modified_since.replace(
tzinfo=tz_util.utc)

if if_modified_since > file_.upload_date:
return Response(status=304)

headers = {
'Last-Modified': date_to_rfc1123(file_.upload_date),
'Content-Length': file_.length,
}

response = Response(file_, headers=headers, mimetype=file_.content_type,
direct_passthrough=True)

return response
range_header = request.headers.get('Range', None)
if not range_header:
file_ = app.media.get(_id)
if file_ is None:
return abort(404)

if_modified_since = weak_date(request.headers.get('If-Modified-Since'))
if if_modified_since is not None:
if if_modified_since.tzinfo is None:
if_modified_since = if_modified_since.replace(
tzinfo=tz_util.utc)

if if_modified_since > file_.upload_date:
return Response(status=304)

headers = {
'Last-Modified': date_to_rfc1123(file_.upload_date),
'Content-Length': file_.length,
'Accept-Ranges': 'bytes',
}

response = Response(
file_,
status=200,
headers=headers,
mimetype=file_.content_type,
direct_passthrough=True
)

return response
else:
file_ = app.media.get(_id)
size = file_.length
byte1, byte2 = 0, None

m = re.search('(\d+)-(\d*)', range_header)
g = m.groups()

if g[0]:
byte1 = int(g[0])
if g[1]:
byte2 = int(g[1])

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

g[1] might be empty string, which will break int(''):

>>> int('')
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
ValueError: invalid literal for int() with base 10: ''

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@nicolaiarocci Yes, I will update the test as soon as possible.

@peterdemin Thank you for correcting me.


length = size - byte1
if byte2 is not None:
length = byte2 - byte1 + 1

data = None
file_.seek(byte1)
data = file_.read(length)

headers = {
'Last-Modified': date_to_rfc1123(file_.upload_date),
'Content-Length': file_.length,
'Accept-Ranges': 'bytes',
'Content-Range': 'bytes {0}-{1}/{2}'.format(
byte1,
byte1 + length - 1,
size
),
}

response = Response(
data,
206,
headers=headers,
mimetype=file_.content_type,
direct_passthrough=True)

return response


@requires_auth('resource')
Expand Down