##// END OF EJS Templates
Renaming api handlers and moving FileFindHandler into base handlr.
Brian E. Granger -
Show More
@@ -16,6 +16,16 b' Authors:'
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19
20 import datetime
21 import email.utils
22 import hashlib
23 import logging
24 import mimetypes
25 import os
26 import stat
27 import threading
28
19 import logging
29 import logging
20
30
21 from tornado import web
31 from tornado import web
@@ -28,6 +38,7 b' except ImportError:'
28
38
29 from IPython.config import Application
39 from IPython.config import Application
30 from IPython.external.decorator import decorator
40 from IPython.external.decorator import decorator
41 from IPython.utils.path import filefind
31
42
32 #-----------------------------------------------------------------------------
43 #-----------------------------------------------------------------------------
33 # Monkeypatch for Tornado <= 2.1.1 - Remove when no longer necessary!
44 # Monkeypatch for Tornado <= 2.1.1 - Remove when no longer necessary!
@@ -277,6 +288,165 b' class AuthenticatedFileHandler(IPythonHandler, web.StaticFileHandler):'
277
288
278
289
279 #-----------------------------------------------------------------------------
290 #-----------------------------------------------------------------------------
291 # File handler
292 #-----------------------------------------------------------------------------
293
294 # to minimize subclass changes:
295 HTTPError = web.HTTPError
296
297 class FileFindHandler(web.StaticFileHandler):
298 """subclass of StaticFileHandler for serving files from a search path"""
299
300 _static_paths = {}
301 # _lock is needed for tornado < 2.2.0 compat
302 _lock = threading.Lock() # protects _static_hashes
303
304 def initialize(self, path, default_filename=None):
305 if isinstance(path, basestring):
306 path = [path]
307 self.roots = tuple(
308 os.path.abspath(os.path.expanduser(p)) + os.path.sep for p in path
309 )
310 self.default_filename = default_filename
311
312 @classmethod
313 def locate_file(cls, path, roots):
314 """locate a file to serve on our static file search path"""
315 with cls._lock:
316 if path in cls._static_paths:
317 return cls._static_paths[path]
318 try:
319 abspath = os.path.abspath(filefind(path, roots))
320 except IOError:
321 # empty string should always give exists=False
322 return ''
323
324 # os.path.abspath strips a trailing /
325 # it needs to be temporarily added back for requests to root/
326 if not (abspath + os.path.sep).startswith(roots):
327 raise HTTPError(403, "%s is not in root static directory", path)
328
329 cls._static_paths[path] = abspath
330 return abspath
331
332 def get(self, path, include_body=True):
333 path = self.parse_url_path(path)
334
335 # begin subclass override
336 abspath = self.locate_file(path, self.roots)
337 # end subclass override
338
339 if os.path.isdir(abspath) and self.default_filename is not None:
340 # need to look at the request.path here for when path is empty
341 # but there is some prefix to the path that was already
342 # trimmed by the routing
343 if not self.request.path.endswith("/"):
344 self.redirect(self.request.path + "/")
345 return
346 abspath = os.path.join(abspath, self.default_filename)
347 if not os.path.exists(abspath):
348 raise HTTPError(404)
349 if not os.path.isfile(abspath):
350 raise HTTPError(403, "%s is not a file", path)
351
352 stat_result = os.stat(abspath)
353 modified = datetime.datetime.utcfromtimestamp(stat_result[stat.ST_MTIME])
354
355 self.set_header("Last-Modified", modified)
356
357 mime_type, encoding = mimetypes.guess_type(abspath)
358 if mime_type:
359 self.set_header("Content-Type", mime_type)
360
361 cache_time = self.get_cache_time(path, modified, mime_type)
362
363 if cache_time > 0:
364 self.set_header("Expires", datetime.datetime.utcnow() + \
365 datetime.timedelta(seconds=cache_time))
366 self.set_header("Cache-Control", "max-age=" + str(cache_time))
367 else:
368 self.set_header("Cache-Control", "public")
369
370 self.set_extra_headers(path)
371
372 # Check the If-Modified-Since, and don't send the result if the
373 # content has not been modified
374 ims_value = self.request.headers.get("If-Modified-Since")
375 if ims_value is not None:
376 date_tuple = email.utils.parsedate(ims_value)
377 if_since = datetime.datetime(*date_tuple[:6])
378 if if_since >= modified:
379 self.set_status(304)
380 return
381
382 with open(abspath, "rb") as file:
383 data = file.read()
384 hasher = hashlib.sha1()
385 hasher.update(data)
386 self.set_header("Etag", '"%s"' % hasher.hexdigest())
387 if include_body:
388 self.write(data)
389 else:
390 assert self.request.method == "HEAD"
391 self.set_header("Content-Length", len(data))
392
393 @classmethod
394 def get_version(cls, settings, path):
395 """Generate the version string to be used in static URLs.
396
397 This method may be overridden in subclasses (but note that it
398 is a class method rather than a static method). The default
399 implementation uses a hash of the file's contents.
400
401 ``settings`` is the `Application.settings` dictionary and ``path``
402 is the relative location of the requested asset on the filesystem.
403 The returned value should be a string, or ``None`` if no version
404 could be determined.
405 """
406 # begin subclass override:
407 static_paths = settings['static_path']
408 if isinstance(static_paths, basestring):
409 static_paths = [static_paths]
410 roots = tuple(
411 os.path.abspath(os.path.expanduser(p)) + os.path.sep for p in static_paths
412 )
413
414 try:
415 abs_path = filefind(path, roots)
416 except IOError:
417 app_log.error("Could not find static file %r", path)
418 return None
419
420 # end subclass override
421
422 with cls._lock:
423 hashes = cls._static_hashes
424 if abs_path not in hashes:
425 try:
426 f = open(abs_path, "rb")
427 hashes[abs_path] = hashlib.md5(f.read()).hexdigest()
428 f.close()
429 except Exception:
430 app_log.error("Could not open static file %r", path)
431 hashes[abs_path] = None
432 hsh = hashes.get(abs_path)
433 if hsh:
434 return hsh[:5]
435 return None
436
437
438 def parse_url_path(self, url_path):
439 """Converts a static URL path into a filesystem path.
440
441 ``url_path`` is the path component of the URL with
442 ``static_url_prefix`` removed. The return value should be
443 filesystem path relative to ``static_path``.
444 """
445 if os.path.sep != "/":
446 url_path = url_path.replace("/", os.path.sep)
447 return url_path
448
449 #-----------------------------------------------------------------------------
280 # URL to handler mappings
450 # URL to handler mappings
281 #-----------------------------------------------------------------------------
451 #-----------------------------------------------------------------------------
282
452
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/clusters/handlers.py to IPython/frontend/html/notebook/clusters/apihandlers.py
NO CONTENT: file renamed from IPython/frontend/html/notebook/clusters/handlers.py to IPython/frontend/html/notebook/clusters/apihandlers.py
1 NO CONTENT: file renamed from IPython/frontend/html/notebook/kernels/handlers.py to IPython/frontend/html/notebook/kernels/apihandlers.py
NO CONTENT: file renamed from IPython/frontend/html/notebook/kernels/handlers.py to IPython/frontend/html/notebook/kernels/apihandlers.py
@@ -68,8 +68,7 b' from .notebooks.nbmanager import NotebookManager'
68 from .notebooks.filenbmanager import FileNotebookManager
68 from .notebooks.filenbmanager import FileNotebookManager
69 from .clusters.clustermanager import ClusterManager
69 from .clusters.clustermanager import ClusterManager
70
70
71 from .base.handlers import AuthenticatedFileHandler
71 from .base.handlers import AuthenticatedFileHandler, FileFindHandler
72 from .base.files import FileFindHandler
73
72
74 from IPython.config.application import catch_config_error, boolean_flag
73 from IPython.config.application import catch_config_error, boolean_flag
75 from IPython.core.application import BaseIPythonApplication
74 from IPython.core.application import BaseIPythonApplication
@@ -142,9 +141,9 b' class NotebookWebApplication(web.Application):'
142 handlers.extend(load_handlers('auth.login'))
141 handlers.extend(load_handlers('auth.login'))
143 handlers.extend(load_handlers('auth.logout'))
142 handlers.extend(load_handlers('auth.logout'))
144 handlers.extend(load_handlers('notebooks.handlers'))
143 handlers.extend(load_handlers('notebooks.handlers'))
145 handlers.extend(load_handlers('kernels.handlers'))
144 handlers.extend(load_handlers('kernels.apihandlers'))
146 handlers.extend(load_handlers('notebooks.apihandlers'))
145 handlers.extend(load_handlers('notebooks.apihandlers'))
147 handlers.extend(load_handlers('clusters.handlers'))
146 handlers.extend(load_handlers('clusters.apihandlers'))
148 handlers.extend([
147 handlers.extend([
149 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : notebook_manager.notebook_dir}),
148 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : notebook_manager.notebook_dir}),
150 ])
149 ])
1 NO CONTENT: file was removed
NO CONTENT: file was removed
General Comments 0
You need to be logged in to leave comments. Login now