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