]> git.ipfire.org Git - thirdparty/tornado.git/commitdiff
Add static_handler_class application setting, and move most of static_url
authorBen Darnell <ben@bendarnell.com>
Sun, 14 Aug 2011 21:38:42 +0000 (14:38 -0700)
committerBen Darnell <ben@bendarnell.com>
Sun, 14 Aug 2011 21:38:42 +0000 (14:38 -0700)
into the handler class where it can be overridden.

tornado/test/web_test.py
tornado/web.py
website/sphinx/releases/next.rst

index 08f4712de74552728101fbc73174ed22039a8b6f..6c60ab415cbf331cebe38fdbf7f28494387b58a2 100644 (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"))
index eb34415890d0f3454dff0ae1ad27cf43d1228b87..384b26eec409b4b1f2b6efd1b81e3b4fdf1f4912 100644 (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.
index 5df4f3bab0f3c79c81ef950834e5d155e2ae7986..ef2e8c4fd998ec3bf2f23b0e5f0aaadd73bf5db3 100644 (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
 ~~~~~~~~~