self.get(path, include_body=False)
def get(self, path, include_body=True):
- path = self.parse_url_path(path)
- abspath = os.path.abspath(os.path.join(self.root, path))
- # os.path.abspath strips a trailing /
- # it needs to be temporarily added back for requests to root/
- if not (abspath + os.path.sep).startswith(self.root):
- raise HTTPError(403, "%s is not in root static directory", path)
- if os.path.isdir(abspath) and self.default_filename is not None:
- # need to look at the request.path here for when path is empty
- # but there is some prefix to the path that was already
- # trimmed by the routing
- if not self.request.path.endswith("/"):
- self.redirect(self.request.path + "/")
+ # Set up our path instance variables.
+ self.path = self.parse_url_path(path)
+ del path # make sure we don't refer to path instead of self.path again
+ absolute_path = self.get_absolute_path(self.settings, self.path)
+ self.absolute_path = self.validate_absolute_path(absolute_path)
+ if self.absolute_path is None:
+ return
+
+ self.modified = self.get_modified_time()
+ self.set_headers()
+
+ if self.should_return_304():
+ self.set_status(304)
+ return
+
+ request_range = None
+ range_header = self.request.headers.get("Range")
+ if range_header:
+ request_range = httputil._parse_request_range(range_header)
+ if not request_range:
+ self.set_status(416) # Range Not Satisfiable
+ self.set_header("Content-Type", "text/plain")
+ self.write("The provided Range header is not valid: %r\n"
+ "Note: multiple ranges are not supported."
+ % range_header)
return
- abspath = os.path.join(abspath, self.default_filename)
- if not os.path.exists(abspath):
- raise HTTPError(404)
- if not os.path.isfile(abspath):
- raise HTTPError(403, "%s is not a file", path)
- stat_result = os.stat(abspath)
- modified = datetime.datetime.utcfromtimestamp(stat_result[stat.ST_MTIME])
+ if request_range:
+ start, end = request_range
+ size = self.get_content_size()
+ if start < 0:
+ start += size
- self.set_status(206) # Partial Content
++ if size != (end or size) - (start or 0):
++ self.set_status(206) # Partial Content
+ self.set_header("Content-Range",
+ httputil._get_content_range(start, end, size))
+ else:
+ start = end = None
+ content = self.get_content(self.absolute_path, start, end)
+ if isinstance(content, bytes_type):
+ content = [content]
+ content_length = 0
+ for chunk in content:
+ if include_body:
+ self.write(chunk)
+ else:
+ content_length += len(chunk)
+ if not include_body:
+ assert self.request.method == "HEAD"
+ self.set_header("Content-Length", content_length)
+
+ def compute_etag(self):
+ """Sets the ``Etag`` header based on static url version.
+
+ This allows efficient ``If-None-Match`` checks against cached
+ versions, and sends the correct ``Etag`` for a partial response
+ (i.e. the same ``Etag`` as the full file).
+ """
+ version_hash = self._get_cached_version(self.absolute_path)
+ if not version_hash:
+ return None
+ return '"%s"' %(version_hash, )
- self.set_header("Last-Modified", modified)
+ def set_headers(self):
+ """Sets the content and caching headers on the response."""
+ self.set_header("Accept-Ranges", "bytes")
+ self.set_etag_header()
- mime_type, encoding = mimetypes.guess_type(abspath)
- if mime_type:
- self.set_header("Content-Type", mime_type)
+ if self.modified is not None:
+ self.set_header("Last-Modified", self.modified)
- cache_time = self.get_cache_time(path, modified, mime_type)
+ content_type = self.get_content_type()
+ if content_type:
+ self.set_header("Content-Type", content_type)
+ cache_time = self.get_cache_time(self.path, self.modified, content_type)
if cache_time > 0:
self.set_header("Expires", datetime.datetime.utcnow() +
datetime.timedelta(seconds=cache_time))