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