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