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