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