##// END OF EJS Templates
Merge pull request #4450 from ivanov/remove-old-tornado-monkeypatch...
Min RK -
r13340:b462fc9b merge
parent child Browse files
Show More
@@ -1,771 +1,747 b''
1 1 # coding: utf-8
2 2 """A tornado based IPython notebook server.
3 3
4 4 Authors:
5 5
6 6 * Brian Granger
7 7 """
8 8 #-----------------------------------------------------------------------------
9 9 # Copyright (C) 2013 The IPython Development Team
10 10 #
11 11 # Distributed under the terms of the BSD License. The full license is in
12 12 # the file COPYING, distributed as part of this software.
13 13 #-----------------------------------------------------------------------------
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Imports
17 17 #-----------------------------------------------------------------------------
18 18
19 19 # stdlib
20 20 import errno
21 21 import logging
22 22 import os
23 23 import random
24 24 import select
25 25 import signal
26 26 import socket
27 27 import sys
28 28 import threading
29 29 import time
30 30 import webbrowser
31 31
32 32
33 33 # Third party
34 34 # check for pyzmq 2.1.11
35 35 from IPython.utils.zmqrelated import check_for_zmq
36 36 check_for_zmq('2.1.11', 'IPython.html')
37 37
38 38 from jinja2 import Environment, FileSystemLoader
39 39
40 40 # Install the pyzmq ioloop. This has to be done before anything else from
41 41 # tornado is imported.
42 42 from zmq.eventloop import ioloop
43 43 ioloop.install()
44 44
45 45 # check for tornado 3.1.0
46 46 msg = "The IPython Notebook requires tornado >= 3.1.0"
47 47 try:
48 48 import tornado
49 49 except ImportError:
50 50 raise ImportError(msg)
51 51 try:
52 52 version_info = tornado.version_info
53 53 except AttributeError:
54 54 raise ImportError(msg + ", but you have < 1.1.0")
55 55 if version_info < (3,1,0):
56 56 raise ImportError(msg + ", but you have %s" % tornado.version)
57 57
58 58 from tornado import httpserver
59 59 from tornado import web
60 60
61 61 # Our own libraries
62 62 from IPython.html import DEFAULT_STATIC_FILES_PATH
63 63
64 64 from .services.kernels.kernelmanager import MappingKernelManager
65 65 from .services.notebooks.nbmanager import NotebookManager
66 66 from .services.notebooks.filenbmanager import FileNotebookManager
67 67 from .services.clusters.clustermanager import ClusterManager
68 68 from .services.sessions.sessionmanager import SessionManager
69 69
70 70 from .base.handlers import AuthenticatedFileHandler, FileFindHandler
71 71
72 72 from IPython.config.application import catch_config_error, boolean_flag
73 73 from IPython.core.application import BaseIPythonApplication
74 74 from IPython.consoleapp import IPythonConsoleApp
75 75 from IPython.kernel import swallow_argv
76 76 from IPython.kernel.zmq.session import default_secure
77 77 from IPython.kernel.zmq.kernelapp import (
78 78 kernel_flags,
79 79 kernel_aliases,
80 80 )
81 81 from IPython.utils.importstring import import_item
82 82 from IPython.utils.localinterfaces import localhost
83 83 from IPython.utils import submodule
84 84 from IPython.utils.traitlets import (
85 85 Dict, Unicode, Integer, List, Bool, Bytes,
86 86 DottedObjectName
87 87 )
88 88 from IPython.utils import py3compat
89 89 from IPython.utils.path import filefind, get_ipython_dir
90 90
91 91 from .utils import url_path_join
92 92
93 93 #-----------------------------------------------------------------------------
94 94 # Module globals
95 95 #-----------------------------------------------------------------------------
96 96
97 97 _examples = """
98 98 ipython notebook # start the notebook
99 99 ipython notebook --profile=sympy # use the sympy profile
100 100 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
101 101 """
102 102
103 103 #-----------------------------------------------------------------------------
104 104 # Helper functions
105 105 #-----------------------------------------------------------------------------
106 106
107 107 def random_ports(port, n):
108 108 """Generate a list of n random ports near the given port.
109 109
110 110 The first 5 ports will be sequential, and the remaining n-5 will be
111 111 randomly selected in the range [port-2*n, port+2*n].
112 112 """
113 113 for i in range(min(5, n)):
114 114 yield port + i
115 115 for i in range(n-5):
116 116 yield max(1, port + random.randint(-2*n, 2*n))
117 117
118 118 def load_handlers(name):
119 119 """Load the (URL pattern, handler) tuples for each component."""
120 120 name = 'IPython.html.' + name
121 121 mod = __import__(name, fromlist=['default_handlers'])
122 122 return mod.default_handlers
123 123
124 124 #-----------------------------------------------------------------------------
125 125 # The Tornado web application
126 126 #-----------------------------------------------------------------------------
127 127
128 128 class NotebookWebApplication(web.Application):
129 129
130 130 def __init__(self, ipython_app, kernel_manager, notebook_manager,
131 131 cluster_manager, session_manager, log, base_project_url,
132 132 settings_overrides):
133 133
134 134 settings = self.init_settings(
135 135 ipython_app, kernel_manager, notebook_manager, cluster_manager,
136 136 session_manager, log, base_project_url, settings_overrides)
137 137 handlers = self.init_handlers(settings)
138 138
139 139 super(NotebookWebApplication, self).__init__(handlers, **settings)
140 140
141 141 def init_settings(self, ipython_app, kernel_manager, notebook_manager,
142 142 cluster_manager, session_manager, log, base_project_url,
143 143 settings_overrides):
144 144 # Python < 2.6.5 doesn't accept unicode keys in f(**kwargs), and
145 145 # base_project_url will always be unicode, which will in turn
146 146 # make the patterns unicode, and ultimately result in unicode
147 147 # keys in kwargs to handler._execute(**kwargs) in tornado.
148 148 # This enforces that base_project_url be ascii in that situation.
149 149 #
150 150 # Note that the URLs these patterns check against are escaped,
151 151 # and thus guaranteed to be ASCII: 'hΓ©llo' is really 'h%C3%A9llo'.
152 152 base_project_url = py3compat.unicode_to_str(base_project_url, 'ascii')
153 153 template_path = settings_overrides.get("template_path", os.path.join(os.path.dirname(__file__), "templates"))
154 154 settings = dict(
155 155 # basics
156 156 base_project_url=base_project_url,
157 157 base_kernel_url=ipython_app.base_kernel_url,
158 158 template_path=template_path,
159 159 static_path=ipython_app.static_file_path,
160 160 static_handler_class = FileFindHandler,
161 161 static_url_prefix = url_path_join(base_project_url,'/static/'),
162 162
163 163 # authentication
164 164 cookie_secret=ipython_app.cookie_secret,
165 165 login_url=url_path_join(base_project_url,'/login'),
166 166 password=ipython_app.password,
167 167
168 168 # managers
169 169 kernel_manager=kernel_manager,
170 170 notebook_manager=notebook_manager,
171 171 cluster_manager=cluster_manager,
172 172 session_manager=session_manager,
173 173
174 174 # IPython stuff
175 175 nbextensions_path = ipython_app.nbextensions_path,
176 176 mathjax_url=ipython_app.mathjax_url,
177 177 config=ipython_app.config,
178 178 use_less=ipython_app.use_less,
179 179 jinja2_env=Environment(loader=FileSystemLoader(template_path)),
180 180 )
181 181
182 182 # allow custom overrides for the tornado web app.
183 183 settings.update(settings_overrides)
184 184 return settings
185 185
186 186 def init_handlers(self, settings):
187 187 # Load the (URL pattern, handler) tuples for each component.
188 188 handlers = []
189 189 handlers.extend(load_handlers('base.handlers'))
190 190 handlers.extend(load_handlers('tree.handlers'))
191 191 handlers.extend(load_handlers('auth.login'))
192 192 handlers.extend(load_handlers('auth.logout'))
193 193 handlers.extend(load_handlers('notebook.handlers'))
194 194 handlers.extend(load_handlers('services.kernels.handlers'))
195 195 handlers.extend(load_handlers('services.notebooks.handlers'))
196 196 handlers.extend(load_handlers('services.clusters.handlers'))
197 197 handlers.extend(load_handlers('services.sessions.handlers'))
198 198 handlers.extend([
199 199 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : settings['notebook_manager'].notebook_dir}),
200 200 (r"/nbextensions/(.*)", FileFindHandler, {'path' : settings['nbextensions_path']}),
201 201 ])
202 202 # prepend base_project_url onto the patterns that we match
203 203 new_handlers = []
204 204 for handler in handlers:
205 205 pattern = url_path_join(settings['base_project_url'], handler[0])
206 206 new_handler = tuple([pattern] + list(handler[1:]))
207 207 new_handlers.append(new_handler)
208 208 return new_handlers
209 209
210 210
211 211
212 212 #-----------------------------------------------------------------------------
213 213 # Aliases and Flags
214 214 #-----------------------------------------------------------------------------
215 215
216 216 flags = dict(kernel_flags)
217 217 flags['no-browser']=(
218 218 {'NotebookApp' : {'open_browser' : False}},
219 219 "Don't open the notebook in a browser after startup."
220 220 )
221 221 flags['no-mathjax']=(
222 222 {'NotebookApp' : {'enable_mathjax' : False}},
223 223 """Disable MathJax
224 224
225 225 MathJax is the javascript library IPython uses to render math/LaTeX. It is
226 226 very large, so you may want to disable it if you have a slow internet
227 227 connection, or for offline use of the notebook.
228 228
229 229 When disabled, equations etc. will appear as their untransformed TeX source.
230 230 """
231 231 )
232 232
233 233 # Add notebook manager flags
234 234 flags.update(boolean_flag('script', 'FileNotebookManager.save_script',
235 235 'Auto-save a .py script everytime the .ipynb notebook is saved',
236 236 'Do not auto-save .py scripts for every notebook'))
237 237
238 238 # the flags that are specific to the frontend
239 239 # these must be scrubbed before being passed to the kernel,
240 240 # or it will raise an error on unrecognized flags
241 241 notebook_flags = ['no-browser', 'no-mathjax', 'script', 'no-script']
242 242
243 243 aliases = dict(kernel_aliases)
244 244
245 245 aliases.update({
246 246 'ip': 'NotebookApp.ip',
247 247 'port': 'NotebookApp.port',
248 248 'port-retries': 'NotebookApp.port_retries',
249 249 'transport': 'KernelManager.transport',
250 250 'keyfile': 'NotebookApp.keyfile',
251 251 'certfile': 'NotebookApp.certfile',
252 252 'notebook-dir': 'NotebookManager.notebook_dir',
253 253 'browser': 'NotebookApp.browser',
254 254 })
255 255
256 256 # remove ipkernel flags that are singletons, and don't make sense in
257 257 # multi-kernel evironment:
258 258 aliases.pop('f', None)
259 259
260 260 notebook_aliases = [u'port', u'port-retries', u'ip', u'keyfile', u'certfile',
261 261 u'notebook-dir', u'profile', u'profile-dir']
262 262
263 263 #-----------------------------------------------------------------------------
264 264 # NotebookApp
265 265 #-----------------------------------------------------------------------------
266 266
267 267 class NotebookApp(BaseIPythonApplication):
268 268
269 269 name = 'ipython-notebook'
270 270
271 271 description = """
272 272 The IPython HTML Notebook.
273 273
274 274 This launches a Tornado based HTML Notebook Server that serves up an
275 275 HTML5/Javascript Notebook client.
276 276 """
277 277 examples = _examples
278 278
279 279 classes = IPythonConsoleApp.classes + [MappingKernelManager, NotebookManager,
280 280 FileNotebookManager]
281 281 flags = Dict(flags)
282 282 aliases = Dict(aliases)
283 283
284 284 kernel_argv = List(Unicode)
285 285
286 286 def _log_level_default(self):
287 287 return logging.INFO
288 288
289 289 def _log_format_default(self):
290 290 """override default log format to include time"""
291 291 return u"%(asctime)s.%(msecs).03d [%(name)s]%(highlevel)s %(message)s"
292 292
293 293 # create requested profiles by default, if they don't exist:
294 294 auto_create = Bool(True)
295 295
296 296 # file to be opened in the notebook server
297 297 file_to_run = Unicode('')
298 298
299 299 # Network related information.
300 300
301 301 ip = Unicode(config=True,
302 302 help="The IP address the notebook server will listen on."
303 303 )
304 304 def _ip_default(self):
305 305 return localhost()
306 306
307 307 def _ip_changed(self, name, old, new):
308 308 if new == u'*': self.ip = u''
309 309
310 310 port = Integer(8888, config=True,
311 311 help="The port the notebook server will listen on."
312 312 )
313 313 port_retries = Integer(50, config=True,
314 314 help="The number of additional ports to try if the specified port is not available."
315 315 )
316 316
317 317 certfile = Unicode(u'', config=True,
318 318 help="""The full path to an SSL/TLS certificate file."""
319 319 )
320 320
321 321 keyfile = Unicode(u'', config=True,
322 322 help="""The full path to a private key file for usage with SSL/TLS."""
323 323 )
324 324
325 325 cookie_secret = Bytes(b'', config=True,
326 326 help="""The random bytes used to secure cookies.
327 327 By default this is a new random number every time you start the Notebook.
328 328 Set it to a value in a config file to enable logins to persist across server sessions.
329 329
330 330 Note: Cookie secrets should be kept private, do not share config files with
331 331 cookie_secret stored in plaintext (you can read the value from a file).
332 332 """
333 333 )
334 334 def _cookie_secret_default(self):
335 335 return os.urandom(1024)
336 336
337 337 password = Unicode(u'', config=True,
338 338 help="""Hashed password to use for web authentication.
339 339
340 340 To generate, type in a python/IPython shell:
341 341
342 342 from IPython.lib import passwd; passwd()
343 343
344 344 The string should be of the form type:salt:hashed-password.
345 345 """
346 346 )
347 347
348 348 open_browser = Bool(True, config=True,
349 349 help="""Whether to open in a browser after starting.
350 350 The specific browser used is platform dependent and
351 351 determined by the python standard library `webbrowser`
352 352 module, unless it is overridden using the --browser
353 353 (NotebookApp.browser) configuration option.
354 354 """)
355 355
356 356 browser = Unicode(u'', config=True,
357 357 help="""Specify what command to use to invoke a web
358 358 browser when opening the notebook. If not specified, the
359 359 default browser will be determined by the `webbrowser`
360 360 standard library module, which allows setting of the
361 361 BROWSER environment variable to override it.
362 362 """)
363 363
364 364 use_less = Bool(False, config=True,
365 365 help="""Wether to use Browser Side less-css parsing
366 366 instead of compiled css version in templates that allows
367 367 it. This is mainly convenient when working on the less
368 368 file to avoid a build step, or if user want to overwrite
369 369 some of the less variables without having to recompile
370 370 everything.
371 371
372 372 You will need to install the less.js component in the static directory
373 373 either in the source tree or in your profile folder.
374 374 """)
375 375
376 376 webapp_settings = Dict(config=True,
377 377 help="Supply overrides for the tornado.web.Application that the "
378 378 "IPython notebook uses.")
379 379
380 380 enable_mathjax = Bool(True, config=True,
381 381 help="""Whether to enable MathJax for typesetting math/TeX
382 382
383 383 MathJax is the javascript library IPython uses to render math/LaTeX. It is
384 384 very large, so you may want to disable it if you have a slow internet
385 385 connection, or for offline use of the notebook.
386 386
387 387 When disabled, equations etc. will appear as their untransformed TeX source.
388 388 """
389 389 )
390 390 def _enable_mathjax_changed(self, name, old, new):
391 391 """set mathjax url to empty if mathjax is disabled"""
392 392 if not new:
393 393 self.mathjax_url = u''
394 394
395 395 base_project_url = Unicode('/', config=True,
396 396 help='''The base URL for the notebook server.
397 397
398 398 Leading and trailing slashes can be omitted,
399 399 and will automatically be added.
400 400 ''')
401 401 def _base_project_url_changed(self, name, old, new):
402 402 if not new.startswith('/'):
403 403 self.base_project_url = '/'+new
404 404 elif not new.endswith('/'):
405 405 self.base_project_url = new+'/'
406 406
407 407 base_kernel_url = Unicode('/', config=True,
408 408 help='''The base URL for the kernel server
409 409
410 410 Leading and trailing slashes can be omitted,
411 411 and will automatically be added.
412 412 ''')
413 413 def _base_kernel_url_changed(self, name, old, new):
414 414 if not new.startswith('/'):
415 415 self.base_kernel_url = '/'+new
416 416 elif not new.endswith('/'):
417 417 self.base_kernel_url = new+'/'
418 418
419 419 websocket_url = Unicode("", config=True,
420 420 help="""The base URL for the websocket server,
421 421 if it differs from the HTTP server (hint: it almost certainly doesn't).
422 422
423 423 Should be in the form of an HTTP origin: ws[s]://hostname[:port]
424 424 """
425 425 )
426 426
427 427 extra_static_paths = List(Unicode, config=True,
428 428 help="""Extra paths to search for serving static files.
429 429
430 430 This allows adding javascript/css to be available from the notebook server machine,
431 431 or overriding individual files in the IPython"""
432 432 )
433 433 def _extra_static_paths_default(self):
434 434 return [os.path.join(self.profile_dir.location, 'static')]
435 435
436 436 @property
437 437 def static_file_path(self):
438 438 """return extra paths + the default location"""
439 439 return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH]
440 440
441 441 nbextensions_path = List(Unicode, config=True,
442 442 help="""paths for Javascript extensions. By default, this is just IPYTHONDIR/nbextensions"""
443 443 )
444 444 def _nbextensions_path_default(self):
445 445 return [os.path.join(get_ipython_dir(), 'nbextensions')]
446 446
447 447 mathjax_url = Unicode("", config=True,
448 448 help="""The url for MathJax.js."""
449 449 )
450 450 def _mathjax_url_default(self):
451 451 if not self.enable_mathjax:
452 452 return u''
453 453 static_url_prefix = self.webapp_settings.get("static_url_prefix",
454 454 url_path_join(self.base_project_url, "static")
455 455 )
456 456
457 457 # try local mathjax, either in nbextensions/mathjax or static/mathjax
458 458 for (url_prefix, search_path) in [
459 459 (url_path_join(self.base_project_url, "nbextensions"), self.nbextensions_path),
460 460 (static_url_prefix, self.static_file_path),
461 461 ]:
462 462 self.log.debug("searching for local mathjax in %s", search_path)
463 463 try:
464 464 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), search_path)
465 465 except IOError:
466 466 continue
467 467 else:
468 468 url = url_path_join(url_prefix, u"mathjax/MathJax.js")
469 469 self.log.info("Serving local MathJax from %s at %s", mathjax, url)
470 470 return url
471 471
472 472 # no local mathjax, serve from CDN
473 473 if self.certfile:
474 474 # HTTPS: load from Rackspace CDN, because SSL certificate requires it
475 475 host = u"https://c328740.ssl.cf1.rackcdn.com"
476 476 else:
477 477 host = u"http://cdn.mathjax.org"
478 478
479 479 url = host + u"/mathjax/latest/MathJax.js"
480 480 self.log.info("Using MathJax from CDN: %s", url)
481 481 return url
482 482
483 483 def _mathjax_url_changed(self, name, old, new):
484 484 if new and not self.enable_mathjax:
485 485 # enable_mathjax=False overrides mathjax_url
486 486 self.mathjax_url = u''
487 487 else:
488 488 self.log.info("Using MathJax: %s", new)
489 489
490 490 notebook_manager_class = DottedObjectName('IPython.html.services.notebooks.filenbmanager.FileNotebookManager',
491 491 config=True,
492 492 help='The notebook manager class to use.')
493 493
494 494 trust_xheaders = Bool(False, config=True,
495 495 help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
496 496 "sent by the upstream reverse proxy. Neccesary if the proxy handles SSL")
497 497 )
498 498
499 499 def parse_command_line(self, argv=None):
500 500 super(NotebookApp, self).parse_command_line(argv)
501 501
502 502 if self.extra_args:
503 503 arg0 = self.extra_args[0]
504 504 f = os.path.abspath(arg0)
505 505 self.argv.remove(arg0)
506 506 if not os.path.exists(f):
507 507 self.log.critical("No such file or directory: %s", f)
508 508 self.exit(1)
509 509 if os.path.isdir(f):
510 510 self.config.FileNotebookManager.notebook_dir = f
511 511 elif os.path.isfile(f):
512 512 self.file_to_run = f
513 513
514 514 def init_kernel_argv(self):
515 515 """construct the kernel arguments"""
516 516 # Scrub frontend-specific flags
517 517 self.kernel_argv = swallow_argv(self.argv, notebook_aliases, notebook_flags)
518 518 # Kernel should inherit default config file from frontend
519 519 self.kernel_argv.append("--IPKernelApp.parent_appname='%s'" % self.name)
520 520 # Kernel should get *absolute* path to profile directory
521 521 self.kernel_argv.extend(["--profile-dir", self.profile_dir.location])
522 522
523 523 def init_configurables(self):
524 524 # force Session default to be secure
525 525 default_secure(self.config)
526 526 self.kernel_manager = MappingKernelManager(
527 527 parent=self, log=self.log, kernel_argv=self.kernel_argv,
528 528 connection_dir = self.profile_dir.security_dir,
529 529 )
530 530 kls = import_item(self.notebook_manager_class)
531 531 self.notebook_manager = kls(parent=self, log=self.log)
532 532 self.session_manager = SessionManager(parent=self, log=self.log)
533 533 self.cluster_manager = ClusterManager(parent=self, log=self.log)
534 534 self.cluster_manager.update_profiles()
535 535
536 536 def init_logging(self):
537 537 # This prevents double log messages because tornado use a root logger that
538 538 # self.log is a child of. The logging module dipatches log messages to a log
539 539 # and all of its ancenstors until propagate is set to False.
540 540 self.log.propagate = False
541 541
542 542 # hook up tornado 3's loggers to our app handlers
543 543 for name in ('access', 'application', 'general'):
544 544 logger = logging.getLogger('tornado.%s' % name)
545 545 logger.parent = self.log
546 546 logger.setLevel(self.log.level)
547 547
548 548 def init_webapp(self):
549 549 """initialize tornado webapp and httpserver"""
550 550 self.web_app = NotebookWebApplication(
551 551 self, self.kernel_manager, self.notebook_manager,
552 552 self.cluster_manager, self.session_manager,
553 553 self.log, self.base_project_url, self.webapp_settings
554 554 )
555 555 if self.certfile:
556 556 ssl_options = dict(certfile=self.certfile)
557 557 if self.keyfile:
558 558 ssl_options['keyfile'] = self.keyfile
559 559 else:
560 560 ssl_options = None
561 561 self.web_app.password = self.password
562 562 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
563 563 xheaders=self.trust_xheaders)
564 564 if not self.ip:
565 565 warning = "WARNING: The notebook server is listening on all IP addresses"
566 566 if ssl_options is None:
567 567 self.log.critical(warning + " and not using encryption. This "
568 568 "is not recommended.")
569 569 if not self.password:
570 570 self.log.critical(warning + " and not using authentication. "
571 571 "This is highly insecure and not recommended.")
572 572 success = None
573 573 for port in random_ports(self.port, self.port_retries+1):
574 574 try:
575 575 self.http_server.listen(port, self.ip)
576 576 except socket.error as e:
577 # XXX: remove the e.errno == -9 block when we require
578 # tornado >= 3.0
579 if e.errno == -9 and tornado.version_info[0] < 3:
580 # The flags passed to socket.getaddrinfo from
581 # tornado.netutils.bind_sockets can cause "gaierror:
582 # [Errno -9] Address family for hostname not supported"
583 # when the interface is not associated, for example.
584 # Changing the flags to exclude socket.AI_ADDRCONFIG does
585 # not cause this error, but the only way to do this is to
586 # monkeypatch socket to remove the AI_ADDRCONFIG attribute
587 saved_AI_ADDRCONFIG = socket.AI_ADDRCONFIG
588 self.log.warn('Monkeypatching socket to fix tornado bug')
589 del(socket.AI_ADDRCONFIG)
590 try:
591 # retry the tornado call without AI_ADDRCONFIG flags
592 self.http_server.listen(port, self.ip)
593 except socket.error as e2:
594 e = e2
595 else:
596 self.port = port
597 success = True
598 break
599 # restore the monekypatch
600 socket.AI_ADDRCONFIG = saved_AI_ADDRCONFIG
601 577 if e.errno == errno.EADDRINUSE:
602 578 self.log.info('The port %i is already in use, trying another random port.' % port)
603 579 continue
604 580 elif e.errno in (errno.EACCES, getattr(errno, 'WSAEACCES', errno.EACCES)):
605 581 self.log.warn("Permission to listen on port %i denied" % port)
606 582 continue
607 583 else:
608 584 raise
609 585 else:
610 586 self.port = port
611 587 success = True
612 588 break
613 589 if not success:
614 590 self.log.critical('ERROR: the notebook server could not be started because '
615 591 'no available port could be found.')
616 592 self.exit(1)
617 593
618 594 def init_signal(self):
619 595 if not sys.platform.startswith('win'):
620 596 signal.signal(signal.SIGINT, self._handle_sigint)
621 597 signal.signal(signal.SIGTERM, self._signal_stop)
622 598 if hasattr(signal, 'SIGUSR1'):
623 599 # Windows doesn't support SIGUSR1
624 600 signal.signal(signal.SIGUSR1, self._signal_info)
625 601 if hasattr(signal, 'SIGINFO'):
626 602 # only on BSD-based systems
627 603 signal.signal(signal.SIGINFO, self._signal_info)
628 604
629 605 def _handle_sigint(self, sig, frame):
630 606 """SIGINT handler spawns confirmation dialog"""
631 607 # register more forceful signal handler for ^C^C case
632 608 signal.signal(signal.SIGINT, self._signal_stop)
633 609 # request confirmation dialog in bg thread, to avoid
634 610 # blocking the App
635 611 thread = threading.Thread(target=self._confirm_exit)
636 612 thread.daemon = True
637 613 thread.start()
638 614
639 615 def _restore_sigint_handler(self):
640 616 """callback for restoring original SIGINT handler"""
641 617 signal.signal(signal.SIGINT, self._handle_sigint)
642 618
643 619 def _confirm_exit(self):
644 620 """confirm shutdown on ^C
645 621
646 622 A second ^C, or answering 'y' within 5s will cause shutdown,
647 623 otherwise original SIGINT handler will be restored.
648 624
649 625 This doesn't work on Windows.
650 626 """
651 627 # FIXME: remove this delay when pyzmq dependency is >= 2.1.11
652 628 time.sleep(0.1)
653 629 info = self.log.info
654 630 info('interrupted')
655 631 print self.notebook_info()
656 632 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
657 633 sys.stdout.flush()
658 634 r,w,x = select.select([sys.stdin], [], [], 5)
659 635 if r:
660 636 line = sys.stdin.readline()
661 637 if line.lower().startswith('y'):
662 638 self.log.critical("Shutdown confirmed")
663 639 ioloop.IOLoop.instance().stop()
664 640 return
665 641 else:
666 642 print "No answer for 5s:",
667 643 print "resuming operation..."
668 644 # no answer, or answer is no:
669 645 # set it back to original SIGINT handler
670 646 # use IOLoop.add_callback because signal.signal must be called
671 647 # from main thread
672 648 ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
673 649
674 650 def _signal_stop(self, sig, frame):
675 651 self.log.critical("received signal %s, stopping", sig)
676 652 ioloop.IOLoop.instance().stop()
677 653
678 654 def _signal_info(self, sig, frame):
679 655 print self.notebook_info()
680 656
681 657 def init_components(self):
682 658 """Check the components submodule, and warn if it's unclean"""
683 659 status = submodule.check_submodule_status()
684 660 if status == 'missing':
685 661 self.log.warn("components submodule missing, running `git submodule update`")
686 662 submodule.update_submodules(submodule.ipython_parent())
687 663 elif status == 'unclean':
688 664 self.log.warn("components submodule unclean, you may see 404s on static/components")
689 665 self.log.warn("run `setup.py submodule` or `git submodule update` to update")
690 666
691 667
692 668 @catch_config_error
693 669 def initialize(self, argv=None):
694 670 super(NotebookApp, self).initialize(argv)
695 671 self.init_logging()
696 672 self.init_kernel_argv()
697 673 self.init_configurables()
698 674 self.init_components()
699 675 self.init_webapp()
700 676 self.init_signal()
701 677
702 678 def cleanup_kernels(self):
703 679 """Shutdown all kernels.
704 680
705 681 The kernels will shutdown themselves when this process no longer exists,
706 682 but explicit shutdown allows the KernelManagers to cleanup the connection files.
707 683 """
708 684 self.log.info('Shutting down kernels')
709 685 self.kernel_manager.shutdown_all()
710 686
711 687 def notebook_info(self):
712 688 "Return the current working directory and the server url information"
713 689 info = self.notebook_manager.info_string() + "\n"
714 690 info += "%d active kernels \n" % len(self.kernel_manager._kernels)
715 691 return info + "The IPython Notebook is running at: %s" % self._url
716 692
717 693 def start(self):
718 694 """ Start the IPython Notebook server app, after initialization
719 695
720 696 This method takes no arguments so all configuration and initialization
721 697 must be done prior to calling this method."""
722 698 ip = self.ip if self.ip else '[all ip addresses on your system]'
723 699 proto = 'https' if self.certfile else 'http'
724 700 info = self.log.info
725 701 self._url = "%s://%s:%i%s" % (proto, ip, self.port,
726 702 self.base_project_url)
727 703 for line in self.notebook_info().split("\n"):
728 704 info(line)
729 705 info("Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).")
730 706
731 707 if self.open_browser or self.file_to_run:
732 708 ip = self.ip or localhost()
733 709 try:
734 710 browser = webbrowser.get(self.browser or None)
735 711 except webbrowser.Error as e:
736 712 self.log.warn('No web browser found: %s.' % e)
737 713 browser = None
738 714
739 715 nbdir = os.path.abspath(self.notebook_manager.notebook_dir)
740 716 f = self.file_to_run
741 717 if f and f.startswith(nbdir):
742 718 f = f[len(nbdir):]
743 719 else:
744 720 self.log.warn(
745 721 "Probably won't be able to open notebook %s "
746 722 "because it is not in notebook_dir %s",
747 723 f, nbdir,
748 724 )
749 725
750 726 if os.path.isfile(self.file_to_run):
751 727 url = url_path_join('notebooks', f)
752 728 else:
753 729 url = url_path_join('tree', f)
754 730 if browser:
755 731 b = lambda : browser.open("%s://%s:%i%s%s" % (proto, ip,
756 732 self.port, self.base_project_url, url), new=2)
757 733 threading.Thread(target=b).start()
758 734 try:
759 735 ioloop.IOLoop.instance().start()
760 736 except KeyboardInterrupt:
761 737 info("Interrupted...")
762 738 finally:
763 739 self.cleanup_kernels()
764 740
765 741
766 742 #-----------------------------------------------------------------------------
767 743 # Main entry point
768 744 #-----------------------------------------------------------------------------
769 745
770 746 launch_new_instance = NotebookApp.launch_instance
771 747
General Comments 0
You need to be logged in to leave comments. Login now