##// END OF EJS Templates
include the # of active kernels in server info
Paul Ivanov -
Show More
@@ -1,771 +1,772 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 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.propagate = False
546 546 logger.setLevel(self.log.level)
547 547 logger.handlers = self.log.handlers
548 548
549 549 def init_webapp(self):
550 550 """initialize tornado webapp and httpserver"""
551 551 self.web_app = NotebookWebApplication(
552 552 self, self.kernel_manager, self.notebook_manager,
553 553 self.cluster_manager, self.session_manager,
554 554 self.log, self.base_project_url, self.webapp_settings
555 555 )
556 556 if self.certfile:
557 557 ssl_options = dict(certfile=self.certfile)
558 558 if self.keyfile:
559 559 ssl_options['keyfile'] = self.keyfile
560 560 else:
561 561 ssl_options = None
562 562 self.web_app.password = self.password
563 563 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
564 564 xheaders=self.trust_xheaders)
565 565 if not self.ip:
566 566 warning = "WARNING: The notebook server is listening on all IP addresses"
567 567 if ssl_options is None:
568 568 self.log.critical(warning + " and not using encryption. This "
569 569 "is not recommended.")
570 570 if not self.password:
571 571 self.log.critical(warning + " and not using authentication. "
572 572 "This is highly insecure and not recommended.")
573 573 success = None
574 574 for port in random_ports(self.port, self.port_retries+1):
575 575 try:
576 576 self.http_server.listen(port, self.ip)
577 577 except socket.error as e:
578 578 # XXX: remove the e.errno == -9 block when we require
579 579 # tornado >= 3.0
580 580 if e.errno == -9 and tornado.version_info[0] < 3:
581 581 # The flags passed to socket.getaddrinfo from
582 582 # tornado.netutils.bind_sockets can cause "gaierror:
583 583 # [Errno -9] Address family for hostname not supported"
584 584 # when the interface is not associated, for example.
585 585 # Changing the flags to exclude socket.AI_ADDRCONFIG does
586 586 # not cause this error, but the only way to do this is to
587 587 # monkeypatch socket to remove the AI_ADDRCONFIG attribute
588 588 saved_AI_ADDRCONFIG = socket.AI_ADDRCONFIG
589 589 self.log.warn('Monkeypatching socket to fix tornado bug')
590 590 del(socket.AI_ADDRCONFIG)
591 591 try:
592 592 # retry the tornado call without AI_ADDRCONFIG flags
593 593 self.http_server.listen(port, self.ip)
594 594 except socket.error as e2:
595 595 e = e2
596 596 else:
597 597 self.port = port
598 598 success = True
599 599 break
600 600 # restore the monekypatch
601 601 socket.AI_ADDRCONFIG = saved_AI_ADDRCONFIG
602 602 if e.errno == errno.EADDRINUSE:
603 603 self.log.info('The port %i is already in use, trying another random port.' % port)
604 604 continue
605 605 elif e.errno in (errno.EACCES, getattr(errno, 'WSAEACCES', errno.EACCES)):
606 606 self.log.warn("Permission to listen on port %i denied" % port)
607 607 continue
608 608 else:
609 609 raise
610 610 else:
611 611 self.port = port
612 612 success = True
613 613 break
614 614 if not success:
615 615 self.log.critical('ERROR: the notebook server could not be started because '
616 616 'no available port could be found.')
617 617 self.exit(1)
618 618
619 619 def init_signal(self):
620 620 if not sys.platform.startswith('win'):
621 621 signal.signal(signal.SIGINT, self._handle_sigint)
622 622 signal.signal(signal.SIGTERM, self._signal_stop)
623 623 if hasattr(signal, 'SIGUSR1'):
624 624 # Windows doesn't support SIGUSR1
625 625 signal.signal(signal.SIGUSR1, self._signal_info)
626 626 if hasattr(signal, 'SIGINFO'):
627 627 # only on BSD-based systems
628 628 signal.signal(signal.SIGINFO, self._signal_info)
629 629
630 630 def _handle_sigint(self, sig, frame):
631 631 """SIGINT handler spawns confirmation dialog"""
632 632 # register more forceful signal handler for ^C^C case
633 633 signal.signal(signal.SIGINT, self._signal_stop)
634 634 # request confirmation dialog in bg thread, to avoid
635 635 # blocking the App
636 636 thread = threading.Thread(target=self._confirm_exit)
637 637 thread.daemon = True
638 638 thread.start()
639 639
640 640 def _restore_sigint_handler(self):
641 641 """callback for restoring original SIGINT handler"""
642 642 signal.signal(signal.SIGINT, self._handle_sigint)
643 643
644 644 def _confirm_exit(self):
645 645 """confirm shutdown on ^C
646 646
647 647 A second ^C, or answering 'y' within 5s will cause shutdown,
648 648 otherwise original SIGINT handler will be restored.
649 649
650 650 This doesn't work on Windows.
651 651 """
652 652 # FIXME: remove this delay when pyzmq dependency is >= 2.1.11
653 653 time.sleep(0.1)
654 654 info = self.log.info
655 655 info('interrupted')
656 656 print self.notebook_info()
657 657 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
658 658 sys.stdout.flush()
659 659 r,w,x = select.select([sys.stdin], [], [], 5)
660 660 if r:
661 661 line = sys.stdin.readline()
662 662 if line.lower().startswith('y'):
663 663 self.log.critical("Shutdown confirmed")
664 664 ioloop.IOLoop.instance().stop()
665 665 return
666 666 else:
667 667 print "No answer for 5s:",
668 668 print "resuming operation..."
669 669 # no answer, or answer is no:
670 670 # set it back to original SIGINT handler
671 671 # use IOLoop.add_callback because signal.signal must be called
672 672 # from main thread
673 673 ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
674 674
675 675 def _signal_stop(self, sig, frame):
676 676 self.log.critical("received signal %s, stopping", sig)
677 677 ioloop.IOLoop.instance().stop()
678 678
679 679 def _signal_info(self, sig, frame):
680 680 print self.notebook_info()
681 681
682 682 def init_components(self):
683 683 """Check the components submodule, and warn if it's unclean"""
684 684 status = submodule.check_submodule_status()
685 685 if status == 'missing':
686 686 self.log.warn("components submodule missing, running `git submodule update`")
687 687 submodule.update_submodules(submodule.ipython_parent())
688 688 elif status == 'unclean':
689 689 self.log.warn("components submodule unclean, you may see 404s on static/components")
690 690 self.log.warn("run `setup.py submodule` or `git submodule update` to update")
691 691
692 692
693 693 @catch_config_error
694 694 def initialize(self, argv=None):
695 695 self.init_logging()
696 696 super(NotebookApp, self).initialize(argv)
697 697 self.init_kernel_argv()
698 698 self.init_configurables()
699 699 self.init_components()
700 700 self.init_webapp()
701 701 self.init_signal()
702 702
703 703 def cleanup_kernels(self):
704 704 """Shutdown all kernels.
705 705
706 706 The kernels will shutdown themselves when this process no longer exists,
707 707 but explicit shutdown allows the KernelManagers to cleanup the connection files.
708 708 """
709 709 self.log.info('Shutting down kernels')
710 710 self.kernel_manager.shutdown_all()
711 711
712 712 def notebook_info(self):
713 713 "Return the current working directory and the server url information"
714 mgr_info = self.notebook_manager.info_string() + "\n"
715 return mgr_info +"The IPython Notebook is running at: %s" % self._url
714 info = self.notebook_manager.info_string() + "\n"
715 info += "%d active kernels \n" % len(self.kernel_manager._kernels)
716 return info + "The IPython Notebook is running at: %s" % self._url
716 717
717 718 def start(self):
718 719 """ Start the IPython Notebook server app, after initialization
719 720
720 721 This method takes no arguments so all configuration and initialization
721 722 must be done prior to calling this method."""
722 723 ip = self.ip if self.ip else '[all ip addresses on your system]'
723 724 proto = 'https' if self.certfile else 'http'
724 725 info = self.log.info
725 726 self._url = "%s://%s:%i%s" % (proto, ip, self.port,
726 727 self.base_project_url)
727 728 for line in self.notebook_info().split("\n"):
728 729 info(line)
729 730 info("Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).")
730 731
731 732 if self.open_browser or self.file_to_run:
732 733 ip = self.ip or localhost()
733 734 try:
734 735 browser = webbrowser.get(self.browser or None)
735 736 except webbrowser.Error as e:
736 737 self.log.warn('No web browser found: %s.' % e)
737 738 browser = None
738 739
739 740 nbdir = os.path.abspath(self.notebook_manager.notebook_dir)
740 741 f = self.file_to_run
741 742 if f and f.startswith(nbdir):
742 743 f = f[len(nbdir):]
743 744 else:
744 745 self.log.warn(
745 746 "Probably won't be able to open notebook %s "
746 747 "because it is not in notebook_dir %s",
747 748 f, nbdir,
748 749 )
749 750
750 751 if os.path.isfile(self.file_to_run):
751 752 url = url_path_join('notebooks', f)
752 753 else:
753 754 url = url_path_join('tree', f)
754 755 if browser:
755 756 b = lambda : browser.open("%s://%s:%i%s%s" % (proto, ip,
756 757 self.port, self.base_project_url, url), new=2)
757 758 threading.Thread(target=b).start()
758 759 try:
759 760 ioloop.IOLoop.instance().start()
760 761 except KeyboardInterrupt:
761 762 info("Interrupted...")
762 763 finally:
763 764 self.cleanup_kernels()
764 765
765 766
766 767 #-----------------------------------------------------------------------------
767 768 # Main entry point
768 769 #-----------------------------------------------------------------------------
769 770
770 771 launch_new_instance = NotebookApp.launch_instance
771 772
General Comments 0
You need to be logged in to leave comments. Login now