add FileFindHandler for serving static files from a search path

This commit is contained in:
MinRK 2012-07-20 11:59:52 -05:00
parent 3730b710e6
commit a627f3d955

View File

@ -16,8 +16,14 @@ Authors:
# Imports
#-----------------------------------------------------------------------------
import logging
import Cookie
import datetime
import email.utils
import hashlib
import logging
import mimetypes
import os
import stat
import time
import uuid
@ -31,6 +37,7 @@ from IPython.external.decorator import decorator
from IPython.zmq.session import Session
from IPython.lib.security import passwd_check
from IPython.utils.jsonutil import date_default
from IPython.utils.path import filefind
try:
from docutils.core import publish_string
@ -736,3 +743,138 @@ class RSTHandler(AuthenticatedHandler):
self.finish(html)
class FileFindHandler(web.StaticFileHandler):
"""subclass of StaticFileHandler for serving files from a search path"""
_static_paths = {}
def initialize(self, path, default_filename=None):
self.roots = tuple(
os.path.abspath(os.path.expanduser(p)) + os.path.sep for p in path
)
self.default_filename = default_filename
@classmethod
def locate_file(cls, path, roots):
"""locate a file to serve on our static file search path"""
with cls._lock:
if path in cls._static_paths:
return cls._static_paths[path]
try:
abspath = os.path.abspath(filefind(path, roots))
except IOError:
# empty string should always give exists=False
return ''
# os.path.abspath strips a trailing /
# it needs to be temporarily added back for requests to root/
if not (abspath + os.path.sep).startswith(roots):
raise web.HTTPError(403, "%s is not in root static directory", path)
cls._static_paths[path] = abspath
return abspath
def get(self, path, include_body=True):
path = self.parse_url_path(path)
abspath = self.locate_file(path, self.roots)
# from here on, this method is unchanged from the parent:
# other than using web.HTTPError instead of just HTTPError
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 web.HTTPError(404)
if not os.path.isfile(abspath):
raise web.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)
mime_type, encoding = mimetypes.guess_type(abspath)
if mime_type:
self.set_header("Content-Type", mime_type)
cache_time = self.get_cache_time(path, modified, mime_type)
if cache_time > 0:
self.set_header("Expires", datetime.datetime.utcnow() + \
datetime.timedelta(seconds=cache_time))
self.set_header("Cache-Control", "max-age=" + str(cache_time))
else:
self.set_header("Cache-Control", "public")
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
with open(abspath, "rb") as file:
data = file.read()
hasher = hashlib.sha1()
hasher.update(data)
self.set_header("Etag", '"%s"' % hasher.hexdigest())
if include_body:
self.write(data)
else:
assert self.request.method == "HEAD"
self.set_header("Content-Length", len(data))
@classmethod
def get_version(cls, settings, path):
"""Generate the version string to be used in static URLs.
This method may be overridden in subclasses (but note that it
is a class method rather than a static method). The default
implementation uses a hash of the file's contents.
``settings`` is the `Application.settings` dictionary and ``path``
is the relative location of the requested asset on the filesystem.
The returned value should be a string, or ``None`` if no version
could be determined.
"""
# begin subclass override:
static_path = settings['static_path']
roots = tuple(
os.path.abspath(os.path.expanduser(p)) + os.path.sep for p in static_path
)
try:
abs_path = filefind(path, roots)
except Exception:
logging.error("Could not find static file %r", path)
return None
# end subclass override
with cls._lock:
hashes = cls._static_hashes
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
hsh = hashes.get(abs_path)
if hsh:
return hsh[:5]
return None