From: Ben Darnell Date: Sun, 19 May 2013 03:59:00 +0000 (-0400) Subject: Merge remote-tracking branch 'birknilson/refactoring/reduce_statichandler_complexity... X-Git-Tag: v3.1.0~67 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=f0e371591ebdb881cce11ff1b75f88471cb0a023;p=thirdparty%2Ftornado.git Merge remote-tracking branch 'birknilson/refactoring/reduce_statichandler_complexity' into merge Conflicts: tornado/web.py --- f0e371591ebdb881cce11ff1b75f88471cb0a023 diff --cc tornado/web.py index 690d2ecc4,20c382d5a..fde2428d7 --- a/tornado/web.py +++ b/tornado/web.py @@@ -1729,6 -1489,43 +1729,68 @@@ class StaticFileHandler(RequestHandler) def get(self, path, include_body=True): path = self.parse_url_path(path) + if not self.set_headers(path): + return + + body = self.get_content(path) + if not body: + return + - hasher = hashlib.sha1() - hasher.update(body) - self.set_header("Etag", '"%s"' % hasher.hexdigest()) - if include_body: - self.write(body) - else: - assert self.request.method == "HEAD" - self.set_header("Content-Length", len(body)) ++ self.set_etag_header() ++ if self.check_etag_header(): ++ self.set_status(304) ++ return ++ ++ self.set_header("Accept-Ranges", "bytes") ++ 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: ++ # 416: Range Not Satisfiable ++ self.set_status(416) ++ self.set_header("Content-Type", "text/plain") ++ self.write(utf8("The provided Range header is not valid: %r\n" ++ "Note: multiple ranges are not supported." ++ %(range_header, ))) ++ return ++ ++ with open(self.get_absolute_path(path), "rb") as file: ++ data = file.read() ++ if request_range: ++ # 206: Partial Content ++ self.set_status(206) ++ content_range = httputil.get_content_range(data, request_range) ++ self.set_header("Content-Range", content_range) ++ data = data[request_range] ++ if include_body: ++ self.write(data) ++ else: ++ assert self.request.method == "HEAD" ++ self.set_header("Content-Length", len(data)) ++ + + def on_finish(self): + # Cleanup the cached computation of the absolute path associated with + # the requested resource. This is crucial in order to ensure accurate + # responses in upcoming requests which would otherwise utilize the same + # absolute path as the initial one - which would be chaotic. + self._abspath = None + + def get_absolute_path(self, path): + """Retrieve the absolute path on the filesystem where the resource + corresponding to the given URL ``path`` can be found. + + This method also handles the validation of the given path and ensures + resources outside of the static directory cannot be accessed. + """ + # In case the ``_abspath`` attribute exists and contains a value + # other than None the abspath has already been computed and verified. + # It can be returned instantly in order to avoid recomputation. + abspath = getattr(self, '_abspath', None) + if abspath is not None: + return abspath + 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/ @@@ -1747,8 -1544,17 +1809,17 @@@ if not os.path.isfile(abspath): raise HTTPError(403, "%s is not a file", path) + self._abspath = abspath + return abspath + + def set_headers(self, path): + """Set the response headers in order to ensure that client browsers + will cache the requested resource and not proceed to retrieve its content + in the case of a 304 response. + """ + abspath = self.get_absolute_path(path) stat_result = os.stat(abspath) - modified = datetime.datetime.fromtimestamp(stat_result[stat.ST_MTIME]) + modified = datetime.datetime.utcfromtimestamp(stat_result[stat.ST_MTIME]) self.set_header("Last-Modified", modified) @@@ -1770,43 -1578,21 +1841,20 @@@ ims_value = self.request.headers.get("If-Modified-Since") if ims_value is not None: date_tuple = email.utils.parsedate(ims_value) - if_since = datetime.datetime.fromtimestamp(time.mktime(date_tuple)) + if_since = datetime.datetime(*date_tuple[:6]) if if_since >= modified: self.set_status(304) - return - - self.set_etag_header() - if self.check_etag_header(): - self.set_status(304) - return - - self.set_header("Accept-Ranges", "bytes") - 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: - # 416: Range Not Satisfiable - self.set_status(416) - self.set_header("Content-Type", "text/plain") - self.write(utf8("The provided Range header is not valid: %r\n" - "Note: multiple ranges are not supported." - %(range_header, ))) - return + return False - + return True + def get_content(self, path): + """Retrieve the content of the requested resource which is located + at the given absolute ``path``. + """ + abspath = self.get_absolute_path(path) with open(abspath, "rb") as file: - data = file.read() - if request_range: - # 206: Partial Content - self.set_status(206) - content_range = httputil.get_content_range(data, request_range) - self.set_header("Content-Range", content_range) - data = data[request_range] - if include_body: - self.write(data) - else: - assert self.request.method == "HEAD" - self.set_header("Content-Length", len(data)) + return file.read() + return None def set_extra_headers(self, path): """For subclass to add extra headers to the response"""