diff --git a/tornado/test/web_test.py b/tornado/test/web_test.py index c285ca7b..9f4c860e 100644 --- a/tornado/test/web_test.py +++ b/tornado/test/web_test.py @@ -602,12 +602,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): @@ -618,9 +630,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 3a581ae9..0d50730d 100644 --- a/tornado/web.py +++ b/tornado/web.py @@ -1477,6 +1477,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/ @@ -1562,6 +1563,20 @@ 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. + """ abs_path = os.path.join(settings["static_path"], path) with cls._lock: hashes = cls._static_hashes @@ -1574,11 +1589,13 @@ class StaticFileHandler(RequestHandler): 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 + if hsh: + return hsh[:5] + return None + + @classmethod + def parse_url_path(cls, url_path): + return url_path class FallbackHandler(RequestHandler):