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):
|
||||
"""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")
|
||||
>>> rh
|
||||
slice(1, 3, None)
|
||||
>>> [0, 1, 2, 3, 4][rh]
|
||||
>>> start, end = _parse_request_range("bytes=1-2")
|
||||
>>> start, end
|
||||
(1, 3)
|
||||
>>> [0, 1, 2, 3, 4][start:end]
|
||||
[1, 2]
|
||||
>>> _parse_request_range("bytes=6-")
|
||||
slice(6, None, None)
|
||||
(6, None)
|
||||
>>> _parse_request_range("bytes=-6")
|
||||
slice(-6, None, None)
|
||||
(-6, None)
|
||||
>>> _parse_request_range("bytes=")
|
||||
slice(None, None, None)
|
||||
(None, None)
|
||||
>>> _parse_request_range("foo=42")
|
||||
>>> _parse_request_range("bytes=1-2,6-10")
|
||||
|
||||
|
@ -276,26 +278,21 @@ def _parse_request_range(range_header):
|
|||
end = None
|
||||
else:
|
||||
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:
|
||||
|
||||
>>> print(_get_content_range("abcd", slice(None, 1)))
|
||||
>>> print(_get_content_range(None, 1, 4))
|
||||
0-0/4
|
||||
>>> print(_get_content_range("abcd", slice(1, 3)))
|
||||
>>> print(_get_content_range(1, 3, 4))
|
||||
1-2/4
|
||||
>>> print(_get_content_range("abcd", slice(None, None)))
|
||||
>>> print(_get_content_range(None, None, 4))
|
||||
0-3/4
|
||||
"""
|
||||
|
||||
data_len = len(data)
|
||||
start, stop = request_range.start, request_range.stop
|
||||
start = start or 0
|
||||
if start < 0:
|
||||
start = data_len + start
|
||||
stop = (stop or data_len) - 1
|
||||
return "%s-%s/%s" %(start, stop, data_len)
|
||||
end = (end or total) - 1
|
||||
return "%s-%s/%s" % (start, end, total)
|
||||
|
||||
def _int_or_none(val):
|
||||
val = val.strip()
|
||||
|
|
|
@ -989,7 +989,8 @@ class CustomStaticFileTest(WebTestCase):
|
|||
return absolute_path
|
||||
|
||||
@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':
|
||||
return b'bar'
|
||||
raise Exception("unexpected path %r" % path)
|
||||
|
|
|
@ -1799,12 +1799,17 @@ class StaticFileHandler(RequestHandler):
|
|||
% range_header)
|
||||
return
|
||||
|
||||
data = self.get_content(self.absolute_path)
|
||||
if request_range:
|
||||
start, end = request_range
|
||||
size = self.get_content_size()
|
||||
if start < 0:
|
||||
start += size
|
||||
self.set_status(206) # Partial Content
|
||||
content_range = httputil._get_content_range(data, request_range)
|
||||
self.set_header("Content-Range", content_range)
|
||||
data = data[request_range]
|
||||
self.set_header("Content-Range",
|
||||
httputil._get_content_range(start, end, size))
|
||||
else:
|
||||
start = end = None
|
||||
data = self.get_content(self.absolute_path, start, end)
|
||||
if include_body:
|
||||
self.write(data)
|
||||
else:
|
||||
|
@ -1909,7 +1914,7 @@ class StaticFileHandler(RequestHandler):
|
|||
return absolute_path
|
||||
|
||||
@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
|
||||
at the given absolute path.
|
||||
|
||||
|
@ -1919,7 +1924,13 @@ class StaticFileHandler(RequestHandler):
|
|||
``abspath`` is able to stand on its own as a cache key.
|
||||
"""
|
||||
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
|
||||
def get_content_version(cls, abspath):
|
||||
|
@ -1931,13 +1942,27 @@ class StaticFileHandler(RequestHandler):
|
|||
data = cls.get_content(abspath)
|
||||
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):
|
||||
"""Returns the time that ``self.absolute_path`` was last modified.
|
||||
|
||||
May be overridden in subclasses. Should return a `~datetime.datetime`
|
||||
object or None.
|
||||
"""
|
||||
stat_result = os.stat(self.absolute_path)
|
||||
stat_result = self._stat()
|
||||
modified = datetime.datetime.utcfromtimestamp(stat_result[stat.ST_MTIME])
|
||||
return modified
|
||||
|
||||
|
|
Loading…
Reference in New Issue