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