Created
April 5, 2011 02:49
-
-
Save didip/902931 to your computer and use it in GitHub Desktop.
Tornado StaticFileHandler
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
class StaticFileHandler(RequestHandler): | |
"""A simple handler that can serve static content from a directory. | |
To map a path to this handler for a static data directory /var/www, | |
you would add a line to your application like: | |
application = web.Application([ | |
(r"/static/(.*)", web.StaticFileHandler, {"path": "/var/www"}), | |
]) | |
The local root directory of the content should be passed as the "path" | |
argument to the handler. | |
To support aggressive browser caching, if the argument "v" is given | |
with the path, we set an infinite HTTP expiration header. So, if you | |
want browsers to cache a file indefinitely, send them to, e.g., | |
/static/images/myimage.png?v=xxx. | |
""" | |
def initialize(self, path, default_filename=None): | |
self.root = os.path.abspath(path) + os.path.sep | |
self.default_filename = default_filename | |
def head(self, path): | |
self.get(path, include_body=False) | |
def get(self, path, include_body=True): | |
if os.path.sep != "/": | |
path = path.replace("/", os.path.sep) | |
abspath = os.path.abspath(os.path.join(self.root, path)) | |
# os.path.abspath strips a trailing / | |
# it needs to be temporarily added back for requests to root/ | |
if not (abspath + os.path.sep).startswith(self.root): | |
raise HTTPError(403, "%s is not in root static directory", path) | |
if os.path.isdir(abspath) and self.default_filename is not None: | |
# need to look at the request.path here for when path is empty | |
# but there is some prefix to the path that was already | |
# trimmed by the routing | |
if not self.request.path.endswith("/"): | |
self.redirect(self.request.path + "/") | |
return | |
abspath = os.path.join(abspath, self.default_filename) | |
if not os.path.exists(abspath): | |
raise HTTPError(404) | |
if not os.path.isfile(abspath): | |
raise HTTPError(403, "%s is not a file", path) | |
stat_result = os.stat(abspath) | |
modified = datetime.datetime.fromtimestamp(stat_result[stat.ST_MTIME]) | |
self.set_header("Last-Modified", modified) | |
if "v" in self.request.arguments: | |
self.set_header("Expires", datetime.datetime.utcnow() + \ | |
datetime.timedelta(days=365*10)) | |
self.set_header("Cache-Control", "max-age=" + str(86400*365*10)) | |
else: | |
self.set_header("Cache-Control", "public") | |
mime_type, encoding = mimetypes.guess_type(abspath) | |
if mime_type: | |
self.set_header("Content-Type", mime_type) | |
self.set_extra_headers(path) | |
# Check the If-Modified-Since, and don't send the result if the | |
# content has not been modified | |
ims_value = self.request.headers.get("If-Modified-Since") | |
if ims_value is not None: | |
date_tuple = email.utils.parsedate(ims_value) | |
if_since = datetime.datetime.fromtimestamp(time.mktime(date_tuple)) | |
if if_since >= modified: | |
self.set_status(304) | |
return | |
if not include_body: | |
return | |
file = open(abspath, "rb") | |
try: | |
self.write(file.read()) | |
finally: | |
file.close() | |
def set_extra_headers(self, path): | |
"""For subclass to add extra headers to the response""" | |
pass |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment