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