diff --git a/IPython/html/base/handlers.py b/IPython/html/base/handlers.py
index 9ca0d25ff..42a5826c2 100644
--- a/IPython/html/base/handlers.py
+++ b/IPython/html/base/handlers.py
@@ -17,21 +17,15 @@ Authors:
#-----------------------------------------------------------------------------
-import datetime
-import email.utils
import functools
-import hashlib
import json
import logging
-import mimetypes
import os
import stat
import sys
-import threading
import traceback
from tornado import web
-from tornado import websocket
try:
from tornado.log import app_log
@@ -39,65 +33,12 @@ except ImportError:
app_log = logging.getLogger()
from IPython.config import Application
-from IPython.external.decorator import decorator
from IPython.utils.path import filefind
-from IPython.utils.jsonutil import date_default
# UF_HIDDEN is a stat flag not defined in the stat module.
# It is used by BSD to indicate hidden files.
UF_HIDDEN = getattr(stat, 'UF_HIDDEN', 32768)
-#-----------------------------------------------------------------------------
-# Monkeypatch for Tornado <= 2.1.1 - Remove when no longer necessary!
-#-----------------------------------------------------------------------------
-
-# Google Chrome, as of release 16, changed its websocket protocol number. The
-# parts tornado cares about haven't really changed, so it's OK to continue
-# accepting Chrome connections, but as of Tornado 2.1.1 (the currently released
-# version as of Oct 30/2011) the version check fails, see the issue report:
-
-# https://github.com/facebook/tornado/issues/385
-
-# This issue has been fixed in Tornado post 2.1.1:
-
-# https://github.com/facebook/tornado/commit/84d7b458f956727c3b0d6710
-
-# Here we manually apply the same patch as above so that users of IPython can
-# continue to work with an officially released Tornado. We make the
-# monkeypatch version check as narrow as possible to limit its effects; once
-# Tornado 2.1.1 is no longer found in the wild we'll delete this code.
-
-import tornado
-
-if tornado.version_info <= (2,1,1):
-
- def _execute(self, transforms, *args, **kwargs):
- from tornado.websocket import WebSocketProtocol8, WebSocketProtocol76
-
- self.open_args = args
- self.open_kwargs = kwargs
-
- # The difference between version 8 and 13 is that in 8 the
- # client sends a "Sec-Websocket-Origin" header and in 13 it's
- # simply "Origin".
- if self.request.headers.get("Sec-WebSocket-Version") in ("7", "8", "13"):
- self.ws_connection = WebSocketProtocol8(self)
- self.ws_connection.accept_connection()
-
- elif self.request.headers.get("Sec-WebSocket-Version"):
- self.stream.write(tornado.escape.utf8(
- "HTTP/1.1 426 Upgrade Required\r\n"
- "Sec-WebSocket-Version: 8\r\n\r\n"))
- self.stream.close()
-
- else:
- self.ws_connection = WebSocketProtocol76(self)
- self.ws_connection.accept_connection()
-
- websocket.WebSocketHandler._execute = _execute
- del _execute
-
-
#-----------------------------------------------------------------------------
# Top-level handlers
#-----------------------------------------------------------------------------
@@ -359,20 +300,20 @@ HTTPError = web.HTTPError
class FileFindHandler(web.StaticFileHandler):
"""subclass of StaticFileHandler for serving files from a search path"""
+ # cache search results, don't search for files more than once
_static_paths = {}
- # _lock is needed for tornado < 2.2.0 compat
- _lock = threading.Lock() # protects _static_hashes
def initialize(self, path, default_filename=None):
if isinstance(path, basestring):
path = [path]
- self.roots = tuple(
+
+ self.root = tuple(
os.path.abspath(os.path.expanduser(p)) + os.sep for p in path
)
self.default_filename = default_filename
@classmethod
- def locate_file(cls, path, roots):
+ def get_absolute_path(cls, roots, path):
"""locate a file to serve on our static file search path"""
with cls._lock:
if path in cls._static_paths:
@@ -382,131 +323,18 @@ class FileFindHandler(web.StaticFileHandler):
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.sep).startswith(roots):
- raise 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)
-
- # begin subclass override
- abspath = self.locate_file(path, self.roots)
- # end subclass override
-
- 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.utcfromtimestamp(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(*date_tuple[:6])
- if if_since >= modified:
- self.set_status(304)
- return
+ def validate_absolute_path(self, root, absolute_path):
+ """check if the file should be served (raises 404, 403, etc.)"""
+ for root in self.root:
+ if (absolute_path + os.sep).startswith(root):
+ break
- 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.
+ return super(FileFindHandler, self).validate_absolute_path(root, absolute_path)
- ``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_paths = settings['static_path']
- if isinstance(static_paths, basestring):
- static_paths = [static_paths]
- roots = tuple(
- os.path.abspath(os.path.expanduser(p)) + os.sep for p in static_paths
- )
-
- try:
- abs_path = filefind(path, roots)
- except IOError:
- app_log.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:
- app_log.error("Could not open static file %r", path)
- hashes[abs_path] = None
- hsh = hashes.get(abs_path)
- if hsh:
- return hsh[:5]
- return None
-
-
- def parse_url_path(self, url_path):
- """Converts a static URL path into a filesystem path.
-
- ``url_path`` is the path component of the URL with
- ``static_url_prefix`` removed. The return value should be
- filesystem path relative to ``static_path``.
- """
- if os.sep != "/":
- url_path = url_path.replace("/", os.sep)
- return url_path
class TrailingSlashHandler(web.RequestHandler):
"""Simple redirect handler that strips trailing slashes
diff --git a/IPython/html/notebookapp.py b/IPython/html/notebookapp.py
index e523cf4aa..f86e55897 100644
--- a/IPython/html/notebookapp.py
+++ b/IPython/html/notebookapp.py
@@ -42,8 +42,8 @@ from jinja2 import Environment, FileSystemLoader
from zmq.eventloop import ioloop
ioloop.install()
-# check for tornado 2.1.0
-msg = "The IPython Notebook requires tornado >= 2.1.0"
+# check for tornado 3.1.0
+msg = "The IPython Notebook requires tornado >= 3.1.0"
try:
import tornado
except ImportError:
@@ -52,7 +52,7 @@ try:
version_info = tornado.version_info
except AttributeError:
raise ImportError(msg + ", but you have < 1.1.0")
-if version_info < (2,1,0):
+if version_info < (3,1,0):
raise ImportError(msg + ", but you have %s" % tornado.version)
from tornado import httpserver
@@ -542,9 +542,8 @@ class NotebookApp(BaseIPythonApplication):
# hook up tornado 3's loggers to our app handlers
for name in ('access', 'application', 'general'):
logger = logging.getLogger('tornado.%s' % name)
- logger.propagate = False
+ logger.parent = self.log
logger.setLevel(self.log.level)
- logger.handlers = self.log.handlers
def init_webapp(self):
"""initialize tornado webapp and httpserver"""
@@ -692,8 +691,8 @@ class NotebookApp(BaseIPythonApplication):
@catch_config_error
def initialize(self, argv=None):
- self.init_logging()
super(NotebookApp, self).initialize(argv)
+ self.init_logging()
self.init_kernel_argv()
self.init_configurables()
self.init_components()
diff --git a/setup.py b/setup.py
index 6498bff88..204804785 100755
--- a/setup.py
+++ b/setup.py
@@ -273,7 +273,7 @@ if 'setuptools' in sys.modules:
zmq = 'pyzmq>=2.1.11',
doc = 'Sphinx>=0.3',
test = 'nose>=0.10.1',
- notebook = ['tornado>=2.0', 'pyzmq>=2.1.11', 'jinja2'],
+ notebook = ['tornado>=3.1', 'pyzmq>=2.1.11', 'jinja2'],
nbconvert = ['pygments', 'jinja2', 'Sphinx>=0.3']
)
everything = set()