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:
parent
c14931142a
commit
00bb10c4e1
|
@ -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"))
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
~~~~~~~~~
|
||||
|
|
Loading…
Reference in New Issue