Add static_handler_class application setting, and move most of static_url

into the handler class where it can be overridden.
This commit is contained in:
Ben Darnell 2011-08-14 14:38:42 -07:00
parent c14931142a
commit 00bb10c4e1
3 changed files with 88 additions and 25 deletions

View File

@ -3,7 +3,7 @@ from tornado.iostream import IOStream
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
@ -519,7 +519,13 @@ class StaticFileTest(AsyncHTTPTestCase, LogTrapTestCase):
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):
@ -532,3 +538,35 @@ class StaticFileTest(AsyncHTTPTestCase, LogTrapTestCase):
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"))

View File

@ -914,26 +914,13 @@ class RequestHandler(object):
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.
@ -1156,7 +1143,9 @@ class Application(object):
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"}),
@ -1173,6 +1162,8 @@ class Application(object):
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
@ -1206,11 +1197,13 @@ class Application(object):
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)
@ -1469,6 +1462,8 @@ class StaticFileHandler(RequestHandler):
"""
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
@ -1550,6 +1545,33 @@ class StaticFileHandler(RequestHandler):
"""
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.

View File

@ -71,6 +71,9 @@ New features
appear multiple times in the response.
* `IOLoop.add_timeout` now accepts `datetime.timedelta` objects in addition
to absolute timestamps.
* It is now possible to use a custom subclass of ``StaticFileHandler``
with the ``static_handler_class`` application setting, and this subclass
can override the behavior of the ``static_url`` method.
Bug fixes
~~~~~~~~~