Add start and end parameters to get_content() instead of reading everything.
This commit is contained in:
parent
e59aa2f438
commit
2e73d4b635
|
@ -238,19 +238,21 @@ class HTTPFile(ObjectDict):
|
||||||
def _parse_request_range(range_header):
|
def _parse_request_range(range_header):
|
||||||
"""Parses a Range header.
|
"""Parses a Range header.
|
||||||
|
|
||||||
Returns either ``None`` or an instance of ``slice``:
|
Returns either ``None`` or tuple ``(start, end)``.
|
||||||
|
Note that while the HTTP headers use inclusive byte positions,
|
||||||
|
this method returns indexes suitable for use in slices.
|
||||||
|
|
||||||
>>> rh = _parse_request_range("bytes=1-2")
|
>>> start, end = _parse_request_range("bytes=1-2")
|
||||||
>>> rh
|
>>> start, end
|
||||||
slice(1, 3, None)
|
(1, 3)
|
||||||
>>> [0, 1, 2, 3, 4][rh]
|
>>> [0, 1, 2, 3, 4][start:end]
|
||||||
[1, 2]
|
[1, 2]
|
||||||
>>> _parse_request_range("bytes=6-")
|
>>> _parse_request_range("bytes=6-")
|
||||||
slice(6, None, None)
|
(6, None)
|
||||||
>>> _parse_request_range("bytes=-6")
|
>>> _parse_request_range("bytes=-6")
|
||||||
slice(-6, None, None)
|
(-6, None)
|
||||||
>>> _parse_request_range("bytes=")
|
>>> _parse_request_range("bytes=")
|
||||||
slice(None, None, None)
|
(None, None)
|
||||||
>>> _parse_request_range("foo=42")
|
>>> _parse_request_range("foo=42")
|
||||||
>>> _parse_request_range("bytes=1-2,6-10")
|
>>> _parse_request_range("bytes=1-2,6-10")
|
||||||
|
|
||||||
|
@ -276,26 +278,21 @@ def _parse_request_range(range_header):
|
||||||
end = None
|
end = None
|
||||||
else:
|
else:
|
||||||
end += 1
|
end += 1
|
||||||
return slice(start, end)
|
return (start, end)
|
||||||
|
|
||||||
def _get_content_range(data, request_range):
|
def _get_content_range(start, end, total):
|
||||||
"""Returns a suitable Content-Range header:
|
"""Returns a suitable Content-Range header:
|
||||||
|
|
||||||
>>> print(_get_content_range("abcd", slice(None, 1)))
|
>>> print(_get_content_range(None, 1, 4))
|
||||||
0-0/4
|
0-0/4
|
||||||
>>> print(_get_content_range("abcd", slice(1, 3)))
|
>>> print(_get_content_range(1, 3, 4))
|
||||||
1-2/4
|
1-2/4
|
||||||
>>> print(_get_content_range("abcd", slice(None, None)))
|
>>> print(_get_content_range(None, None, 4))
|
||||||
0-3/4
|
0-3/4
|
||||||
"""
|
"""
|
||||||
|
|
||||||
data_len = len(data)
|
|
||||||
start, stop = request_range.start, request_range.stop
|
|
||||||
start = start or 0
|
start = start or 0
|
||||||
if start < 0:
|
end = (end or total) - 1
|
||||||
start = data_len + start
|
return "%s-%s/%s" % (start, end, total)
|
||||||
stop = (stop or data_len) - 1
|
|
||||||
return "%s-%s/%s" %(start, stop, data_len)
|
|
||||||
|
|
||||||
def _int_or_none(val):
|
def _int_or_none(val):
|
||||||
val = val.strip()
|
val = val.strip()
|
||||||
|
|
|
@ -989,7 +989,8 @@ class CustomStaticFileTest(WebTestCase):
|
||||||
return absolute_path
|
return absolute_path
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_content(self, path):
|
def get_content(self, path, start=None, end=None):
|
||||||
|
assert start is None and end is None
|
||||||
if path == 'CustomStaticFileTest:foo.txt':
|
if path == 'CustomStaticFileTest:foo.txt':
|
||||||
return b'bar'
|
return b'bar'
|
||||||
raise Exception("unexpected path %r" % path)
|
raise Exception("unexpected path %r" % path)
|
||||||
|
|
|
@ -1799,12 +1799,17 @@ class StaticFileHandler(RequestHandler):
|
||||||
% range_header)
|
% range_header)
|
||||||
return
|
return
|
||||||
|
|
||||||
data = self.get_content(self.absolute_path)
|
|
||||||
if request_range:
|
if request_range:
|
||||||
|
start, end = request_range
|
||||||
|
size = self.get_content_size()
|
||||||
|
if start < 0:
|
||||||
|
start += size
|
||||||
self.set_status(206) # Partial Content
|
self.set_status(206) # Partial Content
|
||||||
content_range = httputil._get_content_range(data, request_range)
|
self.set_header("Content-Range",
|
||||||
self.set_header("Content-Range", content_range)
|
httputil._get_content_range(start, end, size))
|
||||||
data = data[request_range]
|
else:
|
||||||
|
start = end = None
|
||||||
|
data = self.get_content(self.absolute_path, start, end)
|
||||||
if include_body:
|
if include_body:
|
||||||
self.write(data)
|
self.write(data)
|
||||||
else:
|
else:
|
||||||
|
@ -1909,7 +1914,7 @@ class StaticFileHandler(RequestHandler):
|
||||||
return absolute_path
|
return absolute_path
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_content(cls, abspath):
|
def get_content(cls, abspath, start=None, end=None):
|
||||||
"""Retrieve the content of the requested resource which is located
|
"""Retrieve the content of the requested resource which is located
|
||||||
at the given absolute path.
|
at the given absolute path.
|
||||||
|
|
||||||
|
@ -1919,7 +1924,13 @@ class StaticFileHandler(RequestHandler):
|
||||||
``abspath`` is able to stand on its own as a cache key.
|
``abspath`` is able to stand on its own as a cache key.
|
||||||
"""
|
"""
|
||||||
with open(abspath, "rb") as file:
|
with open(abspath, "rb") as file:
|
||||||
return file.read()
|
if start is not None:
|
||||||
|
file.seek(start)
|
||||||
|
if end is not None:
|
||||||
|
remaining = end - (start or 0)
|
||||||
|
return file.read(remaining)
|
||||||
|
else:
|
||||||
|
return file.read()
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_content_version(cls, abspath):
|
def get_content_version(cls, abspath):
|
||||||
|
@ -1931,13 +1942,27 @@ class StaticFileHandler(RequestHandler):
|
||||||
data = cls.get_content(abspath)
|
data = cls.get_content(abspath)
|
||||||
return hashlib.md5(data).hexdigest()
|
return hashlib.md5(data).hexdigest()
|
||||||
|
|
||||||
|
def _stat(self):
|
||||||
|
if not hasattr(self, '_stat_result'):
|
||||||
|
self._stat_result = os.stat(self.absolute_path)
|
||||||
|
return self._stat_result
|
||||||
|
|
||||||
|
def get_content_size(self):
|
||||||
|
"""Retrieve the total size of the resource at the given path.
|
||||||
|
|
||||||
|
This method may be overridden by subclasses. It will only
|
||||||
|
be called if a partial result is requested from `get_content`
|
||||||
|
"""
|
||||||
|
stat_result = self._stat()
|
||||||
|
return stat_result[stat.ST_SIZE]
|
||||||
|
|
||||||
def get_modified_time(self):
|
def get_modified_time(self):
|
||||||
"""Returns the time that ``self.absolute_path`` was last modified.
|
"""Returns the time that ``self.absolute_path`` was last modified.
|
||||||
|
|
||||||
May be overridden in subclasses. Should return a `~datetime.datetime`
|
May be overridden in subclasses. Should return a `~datetime.datetime`
|
||||||
object or None.
|
object or None.
|
||||||
"""
|
"""
|
||||||
stat_result = os.stat(self.absolute_path)
|
stat_result = self._stat()
|
||||||
modified = datetime.datetime.utcfromtimestamp(stat_result[stat.ST_MTIME])
|
modified = datetime.datetime.utcfromtimestamp(stat_result[stat.ST_MTIME])
|
||||||
return modified
|
return modified
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue