##// END OF EJS Templates
bump minimum tornado version to 3.1.0...
MinRK -
Show More
@@ -17,21 +17,15 b' Authors:'
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19
19
20 import datetime
21 import email.utils
22 import functools
20 import functools
23 import hashlib
24 import json
21 import json
25 import logging
22 import logging
26 import mimetypes
27 import os
23 import os
28 import stat
24 import stat
29 import sys
25 import sys
30 import threading
31 import traceback
26 import traceback
32
27
33 from tornado import web
28 from tornado import web
34 from tornado import websocket
35
29
36 try:
30 try:
37 from tornado.log import app_log
31 from tornado.log import app_log
@@ -39,66 +33,13 b' except ImportError:'
39 app_log = logging.getLogger()
33 app_log = logging.getLogger()
40
34
41 from IPython.config import Application
35 from IPython.config import Application
42 from IPython.external.decorator import decorator
43 from IPython.utils.path import filefind
36 from IPython.utils.path import filefind
44 from IPython.utils.jsonutil import date_default
45
37
46 # UF_HIDDEN is a stat flag not defined in the stat module.
38 # UF_HIDDEN is a stat flag not defined in the stat module.
47 # It is used by BSD to indicate hidden files.
39 # It is used by BSD to indicate hidden files.
48 UF_HIDDEN = getattr(stat, 'UF_HIDDEN', 32768)
40 UF_HIDDEN = getattr(stat, 'UF_HIDDEN', 32768)
49
41
50 #-----------------------------------------------------------------------------
42 #-----------------------------------------------------------------------------
51 # Monkeypatch for Tornado <= 2.1.1 - Remove when no longer necessary!
52 #-----------------------------------------------------------------------------
53
54 # Google Chrome, as of release 16, changed its websocket protocol number. The
55 # parts tornado cares about haven't really changed, so it's OK to continue
56 # accepting Chrome connections, but as of Tornado 2.1.1 (the currently released
57 # version as of Oct 30/2011) the version check fails, see the issue report:
58
59 # https://github.com/facebook/tornado/issues/385
60
61 # This issue has been fixed in Tornado post 2.1.1:
62
63 # https://github.com/facebook/tornado/commit/84d7b458f956727c3b0d6710
64
65 # Here we manually apply the same patch as above so that users of IPython can
66 # continue to work with an officially released Tornado. We make the
67 # monkeypatch version check as narrow as possible to limit its effects; once
68 # Tornado 2.1.1 is no longer found in the wild we'll delete this code.
69
70 import tornado
71
72 if tornado.version_info <= (2,1,1):
73
74 def _execute(self, transforms, *args, **kwargs):
75 from tornado.websocket import WebSocketProtocol8, WebSocketProtocol76
76
77 self.open_args = args
78 self.open_kwargs = kwargs
79
80 # The difference between version 8 and 13 is that in 8 the
81 # client sends a "Sec-Websocket-Origin" header and in 13 it's
82 # simply "Origin".
83 if self.request.headers.get("Sec-WebSocket-Version") in ("7", "8", "13"):
84 self.ws_connection = WebSocketProtocol8(self)
85 self.ws_connection.accept_connection()
86
87 elif self.request.headers.get("Sec-WebSocket-Version"):
88 self.stream.write(tornado.escape.utf8(
89 "HTTP/1.1 426 Upgrade Required\r\n"
90 "Sec-WebSocket-Version: 8\r\n\r\n"))
91 self.stream.close()
92
93 else:
94 self.ws_connection = WebSocketProtocol76(self)
95 self.ws_connection.accept_connection()
96
97 websocket.WebSocketHandler._execute = _execute
98 del _execute
99
100
101 #-----------------------------------------------------------------------------
102 # Top-level handlers
43 # Top-level handlers
103 #-----------------------------------------------------------------------------
44 #-----------------------------------------------------------------------------
104
45
@@ -359,20 +300,20 b' HTTPError = web.HTTPError'
359 class FileFindHandler(web.StaticFileHandler):
300 class FileFindHandler(web.StaticFileHandler):
360 """subclass of StaticFileHandler for serving files from a search path"""
301 """subclass of StaticFileHandler for serving files from a search path"""
361
302
303 # cache search results, don't search for files more than once
362 _static_paths = {}
304 _static_paths = {}
363 # _lock is needed for tornado < 2.2.0 compat
364 _lock = threading.Lock() # protects _static_hashes
365
305
366 def initialize(self, path, default_filename=None):
306 def initialize(self, path, default_filename=None):
367 if isinstance(path, basestring):
307 if isinstance(path, basestring):
368 path = [path]
308 path = [path]
369 self.roots = tuple(
309
310 self.root = tuple(
370 os.path.abspath(os.path.expanduser(p)) + os.sep for p in path
311 os.path.abspath(os.path.expanduser(p)) + os.sep for p in path
371 )
312 )
372 self.default_filename = default_filename
313 self.default_filename = default_filename
373
314
374 @classmethod
315 @classmethod
375 def locate_file(cls, path, roots):
316 def get_absolute_path(cls, roots, path):
376 """locate a file to serve on our static file search path"""
317 """locate a file to serve on our static file search path"""
377 with cls._lock:
318 with cls._lock:
378 if path in cls._static_paths:
319 if path in cls._static_paths:
@@ -382,131 +323,18 b' class FileFindHandler(web.StaticFileHandler):'
382 except IOError:
323 except IOError:
383 # empty string should always give exists=False
324 # empty string should always give exists=False
384 return ''
325 return ''
385
326
386 # os.path.abspath strips a trailing /
387 # it needs to be temporarily added back for requests to root/
388 if not (abspath + os.sep).startswith(roots):
389 raise HTTPError(403, "%s is not in root static directory", path)
390
391 cls._static_paths[path] = abspath
327 cls._static_paths[path] = abspath
392 return abspath
328 return abspath
393
329
394 def get(self, path, include_body=True):
330 def validate_absolute_path(self, root, absolute_path):
395 path = self.parse_url_path(path)
331 """check if the file should be served (raises 404, 403, etc.)"""
396
332 for root in self.root:
397 # begin subclass override
333 if (absolute_path + os.sep).startswith(root):
398 abspath = self.locate_file(path, self.roots)
334 break
399 # end subclass override
400
401 if os.path.isdir(abspath) and self.default_filename is not None:
402 # need to look at the request.path here for when path is empty
403 # but there is some prefix to the path that was already
404 # trimmed by the routing
405 if not self.request.path.endswith("/"):
406 self.redirect(self.request.path + "/")
407 return
408 abspath = os.path.join(abspath, self.default_filename)
409 if not os.path.exists(abspath):
410 raise HTTPError(404)
411 if not os.path.isfile(abspath):
412 raise HTTPError(403, "%s is not a file", path)
413
414 stat_result = os.stat(abspath)
415 modified = datetime.datetime.utcfromtimestamp(stat_result[stat.ST_MTIME])
416
417 self.set_header("Last-Modified", modified)
418
419 mime_type, encoding = mimetypes.guess_type(abspath)
420 if mime_type:
421 self.set_header("Content-Type", mime_type)
422
423 cache_time = self.get_cache_time(path, modified, mime_type)
424
425 if cache_time > 0:
426 self.set_header("Expires", datetime.datetime.utcnow() + \
427 datetime.timedelta(seconds=cache_time))
428 self.set_header("Cache-Control", "max-age=" + str(cache_time))
429 else:
430 self.set_header("Cache-Control", "public")
431
432 self.set_extra_headers(path)
433
434 # Check the If-Modified-Since, and don't send the result if the
435 # content has not been modified
436 ims_value = self.request.headers.get("If-Modified-Since")
437 if ims_value is not None:
438 date_tuple = email.utils.parsedate(ims_value)
439 if_since = datetime.datetime(*date_tuple[:6])
440 if if_since >= modified:
441 self.set_status(304)
442 return
443
335
444 with open(abspath, "rb") as file:
336 return super(FileFindHandler, self).validate_absolute_path(root, absolute_path)
445 data = file.read()
446 hasher = hashlib.sha1()
447 hasher.update(data)
448 self.set_header("Etag", '"%s"' % hasher.hexdigest())
449 if include_body:
450 self.write(data)
451 else:
452 assert self.request.method == "HEAD"
453 self.set_header("Content-Length", len(data))
454
455 @classmethod
456 def get_version(cls, settings, path):
457 """Generate the version string to be used in static URLs.
458
459 This method may be overridden in subclasses (but note that it
460 is a class method rather than a static method). The default
461 implementation uses a hash of the file's contents.
462
337
463 ``settings`` is the `Application.settings` dictionary and ``path``
464 is the relative location of the requested asset on the filesystem.
465 The returned value should be a string, or ``None`` if no version
466 could be determined.
467 """
468 # begin subclass override:
469 static_paths = settings['static_path']
470 if isinstance(static_paths, basestring):
471 static_paths = [static_paths]
472 roots = tuple(
473 os.path.abspath(os.path.expanduser(p)) + os.sep for p in static_paths
474 )
475
476 try:
477 abs_path = filefind(path, roots)
478 except IOError:
479 app_log.error("Could not find static file %r", path)
480 return None
481
482 # end subclass override
483
484 with cls._lock:
485 hashes = cls._static_hashes
486 if abs_path not in hashes:
487 try:
488 f = open(abs_path, "rb")
489 hashes[abs_path] = hashlib.md5(f.read()).hexdigest()
490 f.close()
491 except Exception:
492 app_log.error("Could not open static file %r", path)
493 hashes[abs_path] = None
494 hsh = hashes.get(abs_path)
495 if hsh:
496 return hsh[:5]
497 return None
498
499
500 def parse_url_path(self, url_path):
501 """Converts a static URL path into a filesystem path.
502
503 ``url_path`` is the path component of the URL with
504 ``static_url_prefix`` removed. The return value should be
505 filesystem path relative to ``static_path``.
506 """
507 if os.sep != "/":
508 url_path = url_path.replace("/", os.sep)
509 return url_path
510
338
511 class TrailingSlashHandler(web.RequestHandler):
339 class TrailingSlashHandler(web.RequestHandler):
512 """Simple redirect handler that strips trailing slashes
340 """Simple redirect handler that strips trailing slashes
@@ -42,8 +42,8 b' from jinja2 import Environment, FileSystemLoader'
42 from zmq.eventloop import ioloop
42 from zmq.eventloop import ioloop
43 ioloop.install()
43 ioloop.install()
44
44
45 # check for tornado 2.1.0
45 # check for tornado 3.1.0
46 msg = "The IPython Notebook requires tornado >= 2.1.0"
46 msg = "The IPython Notebook requires tornado >= 3.1.0"
47 try:
47 try:
48 import tornado
48 import tornado
49 except ImportError:
49 except ImportError:
@@ -52,7 +52,7 b' try:'
52 version_info = tornado.version_info
52 version_info = tornado.version_info
53 except AttributeError:
53 except AttributeError:
54 raise ImportError(msg + ", but you have < 1.1.0")
54 raise ImportError(msg + ", but you have < 1.1.0")
55 if version_info < (2,1,0):
55 if version_info < (3,1,0):
56 raise ImportError(msg + ", but you have %s" % tornado.version)
56 raise ImportError(msg + ", but you have %s" % tornado.version)
57
57
58 from tornado import httpserver
58 from tornado import httpserver
@@ -273,7 +273,7 b" if 'setuptools' in sys.modules:"
273 zmq = 'pyzmq>=2.1.11',
273 zmq = 'pyzmq>=2.1.11',
274 doc = 'Sphinx>=0.3',
274 doc = 'Sphinx>=0.3',
275 test = 'nose>=0.10.1',
275 test = 'nose>=0.10.1',
276 notebook = ['tornado>=2.0', 'pyzmq>=2.1.11', 'jinja2'],
276 notebook = ['tornado>=3.1', 'pyzmq>=2.1.11', 'jinja2'],
277 nbconvert = ['pygments', 'jinja2', 'Sphinx>=0.3']
277 nbconvert = ['pygments', 'jinja2', 'Sphinx>=0.3']
278 )
278 )
279 everything = set()
279 everything = set()
@@ -137,8 +137,12 b' def check_for_tornado():'
137 print_status('tornado', "no (required for notebook)")
137 print_status('tornado', "no (required for notebook)")
138 return False
138 return False
139 else:
139 else:
140 print_status('tornado', tornado.version)
140 if getattr(tornado, 'version_info', (0,)) < (3,1):
141 return True
141 print_status('tornado', "no (have %s, but require >= 3.1.0)" % tornado.version)
142 return False
143 else:
144 print_status('tornado', tornado.version)
145 return True
142
146
143 def check_for_readline():
147 def check_for_readline():
144 from distutils.version import LooseVersion
148 from distutils.version import LooseVersion
General Comments 0
You need to be logged in to leave comments. Login now