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.template import DictLoader
|
||||||
from tornado.testing import LogTrapTestCase, AsyncHTTPTestCase
|
from tornado.testing import LogTrapTestCase, AsyncHTTPTestCase
|
||||||
from tornado.util import b, bytes_type
|
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 binascii
|
||||||
import logging
|
import logging
|
||||||
|
@ -519,7 +519,13 @@ class StaticFileTest(AsyncHTTPTestCase, LogTrapTestCase):
|
||||||
def get(self, path):
|
def get(self, path):
|
||||||
self.write(self.static_url(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'))
|
static_path=os.path.join(os.path.dirname(__file__), 'static'))
|
||||||
|
|
||||||
def test_static_files(self):
|
def test_static_files(self):
|
||||||
|
@ -532,3 +538,35 @@ class StaticFileTest(AsyncHTTPTestCase, LogTrapTestCase):
|
||||||
def test_static_url(self):
|
def test_static_url(self):
|
||||||
response = self.fetch("/static_url/robots.txt")
|
response = self.fetch("/static_url/robots.txt")
|
||||||
self.assertEqual(response.body, b("/static/robots.txt?v=f71d2"))
|
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.
|
path names.
|
||||||
"""
|
"""
|
||||||
self.require_setting("static_path", "static_url")
|
self.require_setting("static_path", "static_url")
|
||||||
if not hasattr(RequestHandler, "_static_hashes"):
|
static_handler_class = self.settings.get(
|
||||||
RequestHandler._static_hashes = {}
|
"static_handler_class", StaticFileHandler)
|
||||||
hashes = RequestHandler._static_hashes
|
if getattr(self, "include_host", False):
|
||||||
abs_path = os.path.join(self.application.settings["static_path"],
|
base = self.request.protocol + "://" + self.request.host
|
||||||
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]
|
|
||||||
else:
|
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):
|
def async_callback(self, callback, *args, **kwargs):
|
||||||
"""Obsolete - catches exceptions from the wrapped function.
|
"""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
|
Each tuple can contain an optional third element, which should be a
|
||||||
dictionary if it is present. That dictionary is passed as keyword
|
dictionary if it is present. That dictionary is passed as keyword
|
||||||
arguments to the contructor of the handler. This pattern is used
|
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([
|
application = web.Application([
|
||||||
(r"/static/(.*)", web.StaticFileHandler, {"path": "/var/www"}),
|
(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
|
keyword argument. We will serve those files from the /static/ URI
|
||||||
(this is configurable with the static_url_prefix setting),
|
(this is configurable with the static_url_prefix setting),
|
||||||
and we will serve /favicon.ico and /robots.txt from the same directory.
|
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
|
.. attribute:: settings
|
||||||
|
|
||||||
|
@ -1206,11 +1197,13 @@ class Application(object):
|
||||||
handlers = list(handlers or [])
|
handlers = list(handlers or [])
|
||||||
static_url_prefix = settings.get("static_url_prefix",
|
static_url_prefix = settings.get("static_url_prefix",
|
||||||
"/static/")
|
"/static/")
|
||||||
|
static_handler_class = settings.get("static_handler_class",
|
||||||
|
StaticFileHandler)
|
||||||
handlers = [
|
handlers = [
|
||||||
(re.escape(static_url_prefix) + r"(.*)", StaticFileHandler,
|
(re.escape(static_url_prefix) + r"(.*)", static_handler_class,
|
||||||
dict(path=path)),
|
dict(path=path)),
|
||||||
(r"/(favicon\.ico)", StaticFileHandler, dict(path=path)),
|
(r"/(favicon\.ico)", static_handler_class, dict(path=path)),
|
||||||
(r"/(robots\.txt)", StaticFileHandler, dict(path=path)),
|
(r"/(robots\.txt)", static_handler_class, dict(path=path)),
|
||||||
] + handlers
|
] + handlers
|
||||||
if handlers: self.add_handlers(".*$", handlers)
|
if handlers: self.add_handlers(".*$", handlers)
|
||||||
|
|
||||||
|
@ -1469,6 +1462,8 @@ class StaticFileHandler(RequestHandler):
|
||||||
"""
|
"""
|
||||||
CACHE_MAX_AGE = 86400*365*10 #10 years
|
CACHE_MAX_AGE = 86400*365*10 #10 years
|
||||||
|
|
||||||
|
_static_hashes = {}
|
||||||
|
|
||||||
def initialize(self, path, default_filename=None):
|
def initialize(self, path, default_filename=None):
|
||||||
self.root = os.path.abspath(path) + os.path.sep
|
self.root = os.path.abspath(path) + os.path.sep
|
||||||
self.default_filename = default_filename
|
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
|
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):
|
class FallbackHandler(RequestHandler):
|
||||||
"""A RequestHandler that wraps another HTTP server callback.
|
"""A RequestHandler that wraps another HTTP server callback.
|
||||||
|
|
|
@ -71,6 +71,9 @@ New features
|
||||||
appear multiple times in the response.
|
appear multiple times in the response.
|
||||||
* `IOLoop.add_timeout` now accepts `datetime.timedelta` objects in addition
|
* `IOLoop.add_timeout` now accepts `datetime.timedelta` objects in addition
|
||||||
to absolute timestamps.
|
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
|
Bug fixes
|
||||||
~~~~~~~~~
|
~~~~~~~~~
|
||||||
|
|
Loading…
Reference in New Issue