from tornado.template import DictLoader
from tornado.testing import LogTrapTestCase, AsyncHTTPTestCase
from tornado.util import b, bytes_type
-from tornado.web import RequestHandler, _O, authenticated, Application, asynchronous, url, HTTPError
+from tornado.web import RequestHandler, _O, authenticated, Application, asynchronous, url, HTTPError, StaticFileHandler
import binascii
import logging
def get(self, path):
self.write(self.static_url(path))
- return Application([('/static_url/(.*)', StaticUrlHandler)],
+ class AbsoluteStaticUrlHandler(RequestHandler):
+ include_host = True
+ def get(self, path):
+ self.write(self.static_url(path))
+
+ return Application([('/static_url/(.*)', StaticUrlHandler),
+ ('/abs_static_url/(.*)', AbsoluteStaticUrlHandler)],
static_path=os.path.join(os.path.dirname(__file__), 'static'))
def test_static_files(self):
def test_static_url(self):
response = self.fetch("/static_url/robots.txt")
self.assertEqual(response.body, b("/static/robots.txt?v=f71d2"))
+
+ def test_absolute_static_url(self):
+ response = self.fetch("/abs_static_url/robots.txt")
+ self.assertEqual(response.body,
+ utf8(self.get_url("/") + "static/robots.txt?v=f71d2"))
+
+class CustomStaticFileTest(AsyncHTTPTestCase, LogTrapTestCase):
+ def get_app(self):
+ class MyStaticFileHandler(StaticFileHandler):
+ def get(self, path):
+ assert path == "foo.txt"
+ self.write("bar")
+
+ @classmethod
+ def make_static_url(cls, settings, path):
+ return "/static/%s?v=42" % path
+
+ class StaticUrlHandler(RequestHandler):
+ def get(self, path):
+ self.write(self.static_url(path))
+
+ return Application([("/static_url/(.*)", StaticUrlHandler)],
+ static_path="dummy",
+ static_handler_class=MyStaticFileHandler)
+
+ def test_serve(self):
+ response = self.fetch("/static/foo.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"))
path names.
"""
self.require_setting("static_path", "static_url")
- if not hasattr(RequestHandler, "_static_hashes"):
- RequestHandler._static_hashes = {}
- hashes = RequestHandler._static_hashes
- abs_path = os.path.join(self.application.settings["static_path"],
- path)
- if abs_path not in hashes:
- try:
- f = open(abs_path, "rb")
- hashes[abs_path] = hashlib.md5(f.read()).hexdigest()
- f.close()
- except Exception:
- logging.error("Could not open static file %r", path)
- hashes[abs_path] = None
- base = self.request.protocol + "://" + self.request.host \
- if getattr(self, "include_host", False) else ""
- static_url_prefix = self.settings.get('static_url_prefix', '/static/')
- if hashes.get(abs_path):
- return base + static_url_prefix + path + "?v=" + hashes[abs_path][:5]
+ static_handler_class = self.settings.get(
+ "static_handler_class", StaticFileHandler)
+ if getattr(self, "include_host", False):
+ base = self.request.protocol + "://" + self.request.host
else:
- return base + static_url_prefix + path
+ base = ""
+ return base + static_handler_class.make_static_url(self.settings, path)
def async_callback(self, callback, *args, **kwargs):
"""Obsolete - catches exceptions from the wrapped function.
Each tuple can contain an optional third element, which should be a
dictionary if it is present. That dictionary is passed as keyword
arguments to the contructor of the handler. This pattern is used
- for the StaticFileHandler below::
+ for the StaticFileHandler below (note that a StaticFileHandler
+ can be installed automatically with the static_path setting described
+ below)::
application = web.Application([
(r"/static/(.*)", web.StaticFileHandler, {"path": "/var/www"}),
keyword argument. We will serve those files from the /static/ URI
(this is configurable with the static_url_prefix setting),
and we will serve /favicon.ico and /robots.txt from the same directory.
+ A custom subclass of StaticFileHandler can be specified with the
+ static_handler_class setting.
.. attribute:: settings
handlers = list(handlers or [])
static_url_prefix = settings.get("static_url_prefix",
"/static/")
+ static_handler_class = settings.get("static_handler_class",
+ StaticFileHandler)
handlers = [
- (re.escape(static_url_prefix) + r"(.*)", StaticFileHandler,
+ (re.escape(static_url_prefix) + r"(.*)", static_handler_class,
dict(path=path)),
- (r"/(favicon\.ico)", StaticFileHandler, dict(path=path)),
- (r"/(robots\.txt)", StaticFileHandler, dict(path=path)),
+ (r"/(favicon\.ico)", static_handler_class, dict(path=path)),
+ (r"/(robots\.txt)", static_handler_class, dict(path=path)),
] + handlers
if handlers: self.add_handlers(".*$", handlers)
"""
CACHE_MAX_AGE = 86400*365*10 #10 years
+ _static_hashes = {}
+
def initialize(self, path, default_filename=None):
self.root = os.path.abspath(path) + os.path.sep
self.default_filename = default_filename
"""
return self.CACHE_MAX_AGE if "v" in self.request.arguments else 0
+ @classmethod
+ def make_static_url(cls, settings, path):
+ """Constructs a versioned url for the given path.
+
+ This method may be overridden in subclasses (but note that it is
+ a class method rather than an instance method).
+
+ ``settings`` is the `Application.settings` dictionary. ``path``
+ is the static path being requested. The url returned should be
+ relative to the current host.
+ """
+ hashes = cls._static_hashes
+ abs_path = os.path.join(settings["static_path"], path)
+ if abs_path not in hashes:
+ try:
+ f = open(abs_path, "rb")
+ hashes[abs_path] = hashlib.md5(f.read()).hexdigest()
+ f.close()
+ except Exception:
+ logging.error("Could not open static file %r", path)
+ hashes[abs_path] = None
+ static_url_prefix = settings.get('static_url_prefix', '/static/')
+ if hashes.get(abs_path):
+ return static_url_prefix + path + "?v=" + hashes[abs_path][:5]
+ else:
+ return static_url_prefix + path
+
class FallbackHandler(RequestHandler):
"""A RequestHandler that wraps another HTTP server callback.