##// END OF EJS Templates
add FileFindHandler for serving static files from a search path
MinRK -
Show More
@@ -16,8 +16,14 b' Authors:'
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 import logging
20 import Cookie
19 import Cookie
20 import datetime
21 import email.utils
22 import hashlib
23 import logging
24 import mimetypes
25 import os
26 import stat
21 import time
27 import time
22 import uuid
28 import uuid
23
29
@@ -31,6 +37,7 b' from IPython.external.decorator import decorator'
31 from IPython.zmq.session import Session
37 from IPython.zmq.session import Session
32 from IPython.lib.security import passwd_check
38 from IPython.lib.security import passwd_check
33 from IPython.utils.jsonutil import date_default
39 from IPython.utils.jsonutil import date_default
40 from IPython.utils.path import filefind
34
41
35 try:
42 try:
36 from docutils.core import publish_string
43 from docutils.core import publish_string
@@ -736,3 +743,138 b' class RSTHandler(AuthenticatedHandler):'
736 self.finish(html)
743 self.finish(html)
737
744
738
745
746 class FileFindHandler(web.StaticFileHandler):
747 """subclass of StaticFileHandler for serving files from a search path"""
748
749 _static_paths = {}
750
751 def initialize(self, path, default_filename=None):
752 self.roots = tuple(
753 os.path.abspath(os.path.expanduser(p)) + os.path.sep for p in path
754 )
755 self.default_filename = default_filename
756
757 @classmethod
758 def locate_file(cls, path, roots):
759 """locate a file to serve on our static file search path"""
760 with cls._lock:
761 if path in cls._static_paths:
762 return cls._static_paths[path]
763 try:
764 abspath = os.path.abspath(filefind(path, roots))
765 except IOError:
766 # empty string should always give exists=False
767 return ''
768
769 # os.path.abspath strips a trailing /
770 # it needs to be temporarily added back for requests to root/
771 if not (abspath + os.path.sep).startswith(roots):
772 raise web.HTTPError(403, "%s is not in root static directory", path)
773
774 cls._static_paths[path] = abspath
775 return abspath
776
777 def get(self, path, include_body=True):
778 path = self.parse_url_path(path)
779 abspath = self.locate_file(path, self.roots)
780
781 # from here on, this method is unchanged from the parent:
782 # other than using web.HTTPError instead of just HTTPError
783
784 if os.path.isdir(abspath) and self.default_filename is not None:
785 # need to look at the request.path here for when path is empty
786 # but there is some prefix to the path that was already
787 # trimmed by the routing
788 if not self.request.path.endswith("/"):
789 self.redirect(self.request.path + "/")
790 return
791 abspath = os.path.join(abspath, self.default_filename)
792 if not os.path.exists(abspath):
793 raise web.HTTPError(404)
794 if not os.path.isfile(abspath):
795 raise web.HTTPError(403, "%s is not a file", path)
796
797 stat_result = os.stat(abspath)
798 modified = datetime.datetime.fromtimestamp(stat_result[stat.ST_MTIME])
799
800 self.set_header("Last-Modified", modified)
801
802 mime_type, encoding = mimetypes.guess_type(abspath)
803 if mime_type:
804 self.set_header("Content-Type", mime_type)
805
806 cache_time = self.get_cache_time(path, modified, mime_type)
807
808 if cache_time > 0:
809 self.set_header("Expires", datetime.datetime.utcnow() + \
810 datetime.timedelta(seconds=cache_time))
811 self.set_header("Cache-Control", "max-age=" + str(cache_time))
812 else:
813 self.set_header("Cache-Control", "public")
814
815 self.set_extra_headers(path)
816
817 # Check the If-Modified-Since, and don't send the result if the
818 # content has not been modified
819 ims_value = self.request.headers.get("If-Modified-Since")
820 if ims_value is not None:
821 date_tuple = email.utils.parsedate(ims_value)
822 if_since = datetime.datetime.fromtimestamp(time.mktime(date_tuple))
823 if if_since >= modified:
824 self.set_status(304)
825 return
826
827 with open(abspath, "rb") as file:
828 data = file.read()
829 hasher = hashlib.sha1()
830 hasher.update(data)
831 self.set_header("Etag", '"%s"' % hasher.hexdigest())
832 if include_body:
833 self.write(data)
834 else:
835 assert self.request.method == "HEAD"
836 self.set_header("Content-Length", len(data))
837
838 @classmethod
839 def get_version(cls, settings, path):
840 """Generate the version string to be used in static URLs.
841
842 This method may be overridden in subclasses (but note that it
843 is a class method rather than a static method). The default
844 implementation uses a hash of the file's contents.
845
846 ``settings`` is the `Application.settings` dictionary and ``path``
847 is the relative location of the requested asset on the filesystem.
848 The returned value should be a string, or ``None`` if no version
849 could be determined.
850 """
851 # begin subclass override:
852 static_path = settings['static_path']
853 roots = tuple(
854 os.path.abspath(os.path.expanduser(p)) + os.path.sep for p in static_path
855 )
856
857 try:
858 abs_path = filefind(path, roots)
859 except Exception:
860 logging.error("Could not find static file %r", path)
861 return None
862
863 # end subclass override
864
865 with cls._lock:
866 hashes = cls._static_hashes
867 if abs_path not in hashes:
868 try:
869 f = open(abs_path, "rb")
870 hashes[abs_path] = hashlib.md5(f.read()).hexdigest()
871 f.close()
872 except Exception:
873 logging.error("Could not open static file %r", path)
874 hashes[abs_path] = None
875 hsh = hashes.get(abs_path)
876 if hsh:
877 return hsh[:5]
878 return None
879
880
General Comments 0
You need to be logged in to leave comments. Login now