From 2468216f5918c16c055756f52bf95ac7b166c92e Mon Sep 17 00:00:00 2001 From: Birk Nilson Date: Fri, 2 Dec 2011 12:28:03 +0000 Subject: [PATCH 1/3] Separate generation of static URLs and versioning in order ease customization of how versioning is handled in subclasses. --- tornado/web.py | 23 +++++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/tornado/web.py b/tornado/web.py index c7801a4f..906b0128 100644 --- a/tornado/web.py +++ b/tornado/web.py @@ -1528,6 +1528,21 @@ class StaticFileHandler(RequestHandler): is the static path being requested. The url returned should be relative to the current host. """ + static_url_prefix = settings.get('static_url_prefix', '/static/') + version_hash = cls.get_version(settings, path) + if version_hash: + return static_url_prefix + path + "?v=" + version_hash + return static_url_prefix + path + + @classmethod + def get_version(cls, settings, path): + """Generate the version string to be appended as a query string + to the static URL - allowing aggressive caching. + + ``settings`` is the `Application.settings` dictionary and ```path`` + is the relative location of the requested asset on the filesystem. + """ + hsh = None abs_path = os.path.join(settings["static_path"], path) with cls._lock: hashes = cls._static_hashes @@ -1539,12 +1554,8 @@ class StaticFileHandler(RequestHandler): except Exception: logging.error("Could not open static file %r", path) hashes[abs_path] = None - hsh = hashes.get(abs_path) - static_url_prefix = settings.get('static_url_prefix', '/static/') - if hsh: - return static_url_prefix + path + "?v=" + hsh[:5] - else: - return static_url_prefix + path + hsh = hashes.get(abs_path)[:5] + return hsh class FallbackHandler(RequestHandler): From 782c4deffff72a8d9bcec20ae16128380ce0ace4 Mon Sep 17 00:00:00 2001 From: Birk Nilson Date: Fri, 2 Dec 2011 14:25:45 +0000 Subject: [PATCH 2/3] Fixed issue when slicing static_url version string when none exists. --- tornado/web.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/tornado/web.py b/tornado/web.py index 906b0128..d6a82d56 100644 --- a/tornado/web.py +++ b/tornado/web.py @@ -1542,7 +1542,6 @@ class StaticFileHandler(RequestHandler): ``settings`` is the `Application.settings` dictionary and ```path`` is the relative location of the requested asset on the filesystem. """ - hsh = None abs_path = os.path.join(settings["static_path"], path) with cls._lock: hashes = cls._static_hashes @@ -1554,8 +1553,10 @@ class StaticFileHandler(RequestHandler): except Exception: logging.error("Could not open static file %r", path) hashes[abs_path] = None - hsh = hashes.get(abs_path)[:5] - return hsh + hsh = hashes.get(abs_path) + if hsh: + return hsh[:5] + return None class FallbackHandler(RequestHandler): From 59812edca7fec856a737d99363274ff650356988 Mon Sep 17 00:00:00 2001 From: Birk Nilson Date: Fri, 2 Dec 2011 14:28:31 +0000 Subject: [PATCH 3/3] Add ability to parse static path before using it as though it was a relative filesystem path. Thereby enabling developers to add the versioning string as a component in the path rather than a query string. Which is required when working with CloudFront for instance. --- tornado/test/web_test.py | 18 +++++++++++++++--- tornado/web.py | 5 +++++ 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/tornado/test/web_test.py b/tornado/test/web_test.py index c5ae99a3..bef6f529 100644 --- a/tornado/test/web_test.py +++ b/tornado/test/web_test.py @@ -552,12 +552,24 @@ class CustomStaticFileTest(AsyncHTTPTestCase, LogTrapTestCase): def get_app(self): class MyStaticFileHandler(StaticFileHandler): def get(self, path): + path = self.parse_url_path(path) assert path == "foo.txt" self.write("bar") @classmethod def make_static_url(cls, settings, path): - return "/static/%s?v=42" % path + version_hash = cls.get_version(settings, path) + extension_index = path.rindex('.') + before_version = path[:extension_index] + after_version = path[(extension_index + 1):] + return '/static/%s.%s.%s' % (before_version, 42, after_version) + + @classmethod + def parse_url_path(cls, url_path): + extension_index = url_path.rindex('.') + version_index = url_path.rindex('.', 0, extension_index) + return '%s%s' % (url_path[:version_index], + url_path[extension_index:]) class StaticUrlHandler(RequestHandler): def get(self, path): @@ -568,9 +580,9 @@ class CustomStaticFileTest(AsyncHTTPTestCase, LogTrapTestCase): static_handler_class=MyStaticFileHandler) def test_serve(self): - response = self.fetch("/static/foo.txt") + response = self.fetch("/static/foo.42.txt") self.assertEqual(response.body, b("bar")) def test_static_url(self): response = self.fetch("/static_url/foo.txt") - self.assertEqual(response.body, b("/static/foo.txt?v=42")) + self.assertEqual(response.body, b("/static/foo.42.txt")) diff --git a/tornado/web.py b/tornado/web.py index d6a82d56..5a25acdf 100644 --- a/tornado/web.py +++ b/tornado/web.py @@ -1446,6 +1446,7 @@ class StaticFileHandler(RequestHandler): def get(self, path, include_body=True): if os.path.sep != "/": path = path.replace("/", os.path.sep) + 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/ @@ -1558,6 +1559,10 @@ class StaticFileHandler(RequestHandler): return hsh[:5] return None + @classmethod + def parse_url_path(cls, url_path): + return url_path + class FallbackHandler(RequestHandler): """A RequestHandler that wraps another HTTP server callback.