mirror of
https://github.com/jupyter/notebook.git
synced 2025-01-12 11:45:38 +08:00
Merge pull request #4428 from minrk/tornado-3.1
bump minimum tornado version to 3.1.0
This commit is contained in:
commit
daf15735bd
@ -17,21 +17,15 @@ Authors:
|
|||||||
#-----------------------------------------------------------------------------
|
#-----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
import datetime
|
|
||||||
import email.utils
|
|
||||||
import functools
|
import functools
|
||||||
import hashlib
|
|
||||||
import json
|
import json
|
||||||
import logging
|
import logging
|
||||||
import mimetypes
|
|
||||||
import os
|
import os
|
||||||
import stat
|
import stat
|
||||||
import sys
|
import sys
|
||||||
import threading
|
|
||||||
import traceback
|
import traceback
|
||||||
|
|
||||||
from tornado import web
|
from tornado import web
|
||||||
from tornado import websocket
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from tornado.log import app_log
|
from tornado.log import app_log
|
||||||
@ -39,65 +33,12 @@ except ImportError:
|
|||||||
app_log = logging.getLogger()
|
app_log = logging.getLogger()
|
||||||
|
|
||||||
from IPython.config import Application
|
from IPython.config import Application
|
||||||
from IPython.external.decorator import decorator
|
|
||||||
from IPython.utils.path import filefind
|
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.
|
# UF_HIDDEN is a stat flag not defined in the stat module.
|
||||||
# It is used by BSD to indicate hidden files.
|
# It is used by BSD to indicate hidden files.
|
||||||
UF_HIDDEN = getattr(stat, 'UF_HIDDEN', 32768)
|
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
|
# Top-level handlers
|
||||||
#-----------------------------------------------------------------------------
|
#-----------------------------------------------------------------------------
|
||||||
@ -359,20 +300,20 @@ HTTPError = web.HTTPError
|
|||||||
class FileFindHandler(web.StaticFileHandler):
|
class FileFindHandler(web.StaticFileHandler):
|
||||||
"""subclass of StaticFileHandler for serving files from a search path"""
|
"""subclass of StaticFileHandler for serving files from a search path"""
|
||||||
|
|
||||||
|
# cache search results, don't search for files more than once
|
||||||
_static_paths = {}
|
_static_paths = {}
|
||||||
# _lock is needed for tornado < 2.2.0 compat
|
|
||||||
_lock = threading.Lock() # protects _static_hashes
|
|
||||||
|
|
||||||
def initialize(self, path, default_filename=None):
|
def initialize(self, path, default_filename=None):
|
||||||
if isinstance(path, basestring):
|
if isinstance(path, basestring):
|
||||||
path = [path]
|
path = [path]
|
||||||
self.roots = tuple(
|
|
||||||
|
self.root = tuple(
|
||||||
os.path.abspath(os.path.expanduser(p)) + os.sep for p in path
|
os.path.abspath(os.path.expanduser(p)) + os.sep for p in path
|
||||||
)
|
)
|
||||||
self.default_filename = default_filename
|
self.default_filename = default_filename
|
||||||
|
|
||||||
@classmethod
|
@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"""
|
"""locate a file to serve on our static file search path"""
|
||||||
with cls._lock:
|
with cls._lock:
|
||||||
if path in cls._static_paths:
|
if path in cls._static_paths:
|
||||||
@ -383,130 +324,17 @@ class FileFindHandler(web.StaticFileHandler):
|
|||||||
# empty string should always give exists=False
|
# empty string should always give exists=False
|
||||||
return ''
|
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
|
cls._static_paths[path] = abspath
|
||||||
return abspath
|
return abspath
|
||||||
|
|
||||||
def get(self, path, include_body=True):
|
def validate_absolute_path(self, root, absolute_path):
|
||||||
path = self.parse_url_path(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
|
||||||
|
|
||||||
# begin subclass override
|
return super(FileFindHandler, self).validate_absolute_path(root, absolute_path)
|
||||||
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
|
|
||||||
|
|
||||||
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_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):
|
class TrailingSlashHandler(web.RequestHandler):
|
||||||
"""Simple redirect handler that strips trailing slashes
|
"""Simple redirect handler that strips trailing slashes
|
||||||
|
@ -42,8 +42,8 @@ from jinja2 import Environment, FileSystemLoader
|
|||||||
from zmq.eventloop import ioloop
|
from zmq.eventloop import ioloop
|
||||||
ioloop.install()
|
ioloop.install()
|
||||||
|
|
||||||
# check for tornado 2.1.0
|
# check for tornado 3.1.0
|
||||||
msg = "The IPython Notebook requires tornado >= 2.1.0"
|
msg = "The IPython Notebook requires tornado >= 3.1.0"
|
||||||
try:
|
try:
|
||||||
import tornado
|
import tornado
|
||||||
except ImportError:
|
except ImportError:
|
||||||
@ -52,7 +52,7 @@ try:
|
|||||||
version_info = tornado.version_info
|
version_info = tornado.version_info
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
raise ImportError(msg + ", but you have < 1.1.0")
|
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)
|
raise ImportError(msg + ", but you have %s" % tornado.version)
|
||||||
|
|
||||||
from tornado import httpserver
|
from tornado import httpserver
|
||||||
@ -542,9 +542,8 @@ class NotebookApp(BaseIPythonApplication):
|
|||||||
# hook up tornado 3's loggers to our app handlers
|
# hook up tornado 3's loggers to our app handlers
|
||||||
for name in ('access', 'application', 'general'):
|
for name in ('access', 'application', 'general'):
|
||||||
logger = logging.getLogger('tornado.%s' % name)
|
logger = logging.getLogger('tornado.%s' % name)
|
||||||
logger.propagate = False
|
logger.parent = self.log
|
||||||
logger.setLevel(self.log.level)
|
logger.setLevel(self.log.level)
|
||||||
logger.handlers = self.log.handlers
|
|
||||||
|
|
||||||
def init_webapp(self):
|
def init_webapp(self):
|
||||||
"""initialize tornado webapp and httpserver"""
|
"""initialize tornado webapp and httpserver"""
|
||||||
@ -692,8 +691,8 @@ class NotebookApp(BaseIPythonApplication):
|
|||||||
|
|
||||||
@catch_config_error
|
@catch_config_error
|
||||||
def initialize(self, argv=None):
|
def initialize(self, argv=None):
|
||||||
self.init_logging()
|
|
||||||
super(NotebookApp, self).initialize(argv)
|
super(NotebookApp, self).initialize(argv)
|
||||||
|
self.init_logging()
|
||||||
self.init_kernel_argv()
|
self.init_kernel_argv()
|
||||||
self.init_configurables()
|
self.init_configurables()
|
||||||
self.init_components()
|
self.init_components()
|
||||||
|
2
setup.py
2
setup.py
@ -273,7 +273,7 @@ if 'setuptools' in sys.modules:
|
|||||||
zmq = 'pyzmq>=2.1.11',
|
zmq = 'pyzmq>=2.1.11',
|
||||||
doc = 'Sphinx>=0.3',
|
doc = 'Sphinx>=0.3',
|
||||||
test = 'nose>=0.10.1',
|
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']
|
nbconvert = ['pygments', 'jinja2', 'Sphinx>=0.3']
|
||||||
)
|
)
|
||||||
everything = set()
|
everything = set()
|
||||||
|
Loading…
Reference in New Issue
Block a user