]> git.ipfire.org Git - thirdparty/tornado.git/commitdiff
Add start and end parameters to get_content() instead of reading everything.
authorBen Darnell <ben@bendarnell.com>
Sun, 19 May 2013 17:57:31 +0000 (13:57 -0400)
committerBen Darnell <ben@bendarnell.com>
Sun, 19 May 2013 17:57:31 +0000 (13:57 -0400)
tornado/httputil.py
tornado/test/web_test.py
tornado/web.py

index 23705defeb2c3feb4df1701a39636fb89dfca349..36d7dc8faed63eb7469fc97c5e0b6cee65b461b3 100644 (file)
@@ -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``:
-
-    >>> rh = _parse_request_range("bytes=1-2")
-    >>> rh
-    slice(1, 3, None)
-    >>> [0, 1, 2, 3, 4][rh]
+    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.
+
+    >>> 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()
index 195269bbb115bce4e5017def5baf2eee55dc36a1..89c549c2d914be2d25c5201fa46a629a67b7fcff 100644 (file)
@@ -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)
index 8ecaaedd77f1616a72b86d74615ea2a3f42ab16e..78d7b0ab6012810c084cafcb7a19e5554dccdea2 100644 (file)
@@ -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