Merge pull request #4428 from minrk/tornado-3.1

bump minimum tornado version to 3.1.0
This commit is contained in:
Thomas Kluyver 2013-10-23 14:36:25 -07:00
commit daf15735bd
3 changed files with 17 additions and 190 deletions

View File

@ -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)
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
# 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)
return super(FileFindHandler, self).validate_absolute_path(root, absolute_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):
"""Simple redirect handler that strips trailing slashes

View File

@ -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()

View File

@ -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()