mirror of
https://github.com/jupyter/notebook.git
synced 2024-12-27 04:20:22 +08:00
add FileFindHandler for serving static files from a search path
This commit is contained in:
parent
3730b710e6
commit
a627f3d955
@ -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
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user