include tornado/test/gettext_translations/fr_FR/LC_MESSAGES/tornado_test.po
include tornado/test/options_test.cfg
include tornado/test/static/robots.txt
+include tornado/test/static/dir/index.html
include tornado/test/templates/utf8.html
include tornado/test/test.crt
include tornado/test/test.key
"gettext_translations/fr_FR/LC_MESSAGES/tornado_test.po",
"options_test.cfg",
"static/robots.txt",
+ "static/dir/index.html",
"templates/utf8.html",
"test.crt",
"test.key",
--- /dev/null
+this is the index
'Range': 'asdf'})
self.assertEqual(response.code, 416)
+ def test_static_head(self):
+ response = self.fetch('/static/robots.txt', method='HEAD')
+ self.assertEqual(response.code, 200)
+ # No body was returned, but we did get the right content length.
+ self.assertEqual(response.body, b'')
+ self.assertEqual(response.headers['Content-Length'], '26')
+ self.assertEqual(utf8(response.headers['Etag']),
+ b'"' + self.robots_txt_hash + b'"')
+
+ def test_static_head_range(self):
+ response = self.fetch('/static/robots.txt', method='HEAD',
+ headers={'Range': 'bytes=1-4'})
+ self.assertEqual(response.code, 206)
+ self.assertEqual(response.body, b'')
+ self.assertEqual(response.headers['Content-Length'], '4')
+ self.assertEqual(utf8(response.headers['Etag']),
+ b'"' + self.robots_txt_hash + b'"')
+
+ def test_static_range_if_none_match(self):
+ response = self.fetch('/static/robots.txt', headers={
+ 'Range': 'bytes=1-4',
+ 'If-None-Match': b'"' + self.robots_txt_hash + b'"'})
+ self.assertEqual(response.code, 304)
+ self.assertEqual(response.body, b'')
+ self.assertTrue('Content-Length' not in response.headers)
+ self.assertEqual(utf8(response.headers['Etag']),
+ b'"' + self.robots_txt_hash + b'"')
+
+ def test_static_404(self):
+ response = self.fetch('/static/blarg')
+ self.assertEqual(response.code, 404)
+
+
+@wsgi_safe
+class StaticDefaultFilenameTest(WebTestCase):
+ def get_app_kwargs(self):
+ return dict(static_path=os.path.join(os.path.dirname(__file__),
+ 'static'),
+ static_handler_args=dict(default_filename='index.html'))
+
+ def get_handlers(self):
+ return []
+
+ def test_static_default_filename(self):
+ response = self.fetch('/static/dir/', follow_redirects=False)
+ self.assertEqual(response.code, 200)
+ self.assertEqual(b'this is the index\n', response.body)
+
+ def test_static_default_redirect(self):
+ response = self.fetch('/static/dir', follow_redirects=False)
+ self.assertEqual(response.code, 301)
+ self.assertTrue(response.headers['Location'].endswith('/static/dir/'))
+
+
@wsgi_safe
class CustomStaticFileTest(WebTestCase):
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()
versions, and sends the correct ``Etag`` for a partial response
(i.e. the same ``Etag`` as the full file).
"""
- version_hash = self.get_version(self.settings, self.path_args[0])
+ version_hash = self._get_cached_version(self.absolute_path)
if not version_hash:
return None
return '"%s"' %(version_hash, )
# 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 + "/")
+ self.redirect(self.request.path + "/", permanent=True)
return
absolute_path = os.path.join(absolute_path, self.default_filename)
if not os.path.exists(absolute_path):
with open(abspath, "rb") as file:
return file.read()
+ @classmethod
+ def get_content_version(cls, abspath):
+ """Returns a version string for the resource at the given path.
+
+ This class method may be overridden by subclasses. The
+ default implementation is a hash of the file's contents.
+ """
+ data = cls.get_content(abspath)
+ return hashlib.md5(data).hexdigest()
+
def get_modified_time(self):
"""Returns the time that ``self.absolute_path`` was last modified.
def get_version(cls, settings, path):
"""Generate the version string to be used in static URLs.
- This method may be overridden in subclasses (but note that it
- is a class method rather than a static method). The default
- implementation uses a hash of the file's contents.
-
``settings`` is the `Application.settings` dictionary and ``path``
is the relative location of the requested asset on the filesystem.
The returned value should be a string, or ``None`` if no version
could be determined.
+
+ This method was previously recommended for subclasses to override;
+ `get_content_version` is now preferred as it allows the base
+ class to handle caching of the result.
"""
abs_path = cls.get_absolute_path(settings, path)
+ return cls._get_cached_version(abs_path)
+
+ @classmethod
+ def _get_cached_version(cls, abs_path):
with cls._lock:
hashes = cls._static_hashes
if abs_path not in hashes:
try:
- data = cls.get_content(abs_path)
- hashes[abs_path] = hashlib.md5(data).hexdigest()
+ hashes[abs_path] = cls.get_content_version(abs_path)
except Exception:
- gen_log.error("Could not open static file %r", path)
+ gen_log.error("Could not open static file %r", abs_path)
hashes[abs_path] = None
hsh = hashes.get(abs_path)
if hsh: