##// END OF EJS Templates
add ssl_options config value...
Min RK -
Show More
@@ -0,0 +1,4 b''
1 * add ``NotebookApp.ssl_options`` config value.
2 This is passed through to tornado HTTPServer,
3 and allows SSL configuration beyong specifying a cert and key,
4 such as disabling SSLv3.
@@ -1,1112 +1,1118 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 base64
10 10 import datetime
11 11 import errno
12 12 import importlib
13 13 import io
14 14 import json
15 15 import logging
16 16 import os
17 17 import random
18 18 import re
19 19 import select
20 20 import signal
21 21 import socket
22 22 import sys
23 23 import threading
24 24 import webbrowser
25 25
26 26
27 27 # check for pyzmq
28 28 from IPython.utils.zmqrelated import check_for_zmq
29 29 check_for_zmq('13', 'IPython.html')
30 30
31 31 from jinja2 import Environment, FileSystemLoader
32 32
33 33 # Install the pyzmq ioloop. This has to be done before anything else from
34 34 # tornado is imported.
35 35 from zmq.eventloop import ioloop
36 36 ioloop.install()
37 37
38 38 # check for tornado 3.1.0
39 39 msg = "The IPython Notebook requires tornado >= 4.0"
40 40 try:
41 41 import tornado
42 42 except ImportError:
43 43 raise ImportError(msg)
44 44 try:
45 45 version_info = tornado.version_info
46 46 except AttributeError:
47 47 raise ImportError(msg + ", but you have < 1.1.0")
48 48 if version_info < (4,0):
49 49 raise ImportError(msg + ", but you have %s" % tornado.version)
50 50
51 51 from tornado import httpserver
52 52 from tornado import web
53 53 from tornado.log import LogFormatter, app_log, access_log, gen_log
54 54
55 55 from IPython.html import (
56 56 DEFAULT_STATIC_FILES_PATH,
57 57 DEFAULT_TEMPLATE_PATH_LIST,
58 58 )
59 59 from .base.handlers import Template404
60 60 from .log import log_request
61 61 from .services.kernels.kernelmanager import MappingKernelManager
62 62 from .services.config import ConfigManager
63 63 from .services.contents.manager import ContentsManager
64 64 from .services.contents.filemanager import FileContentsManager
65 65 from .services.clusters.clustermanager import ClusterManager
66 66 from .services.sessions.sessionmanager import SessionManager
67 67
68 68 from .auth.login import LoginHandler
69 69 from .auth.logout import LogoutHandler
70 70 from .base.handlers import IPythonHandler, FileFindHandler
71 71
72 72 from IPython.config import Config
73 73 from IPython.config.application import catch_config_error, boolean_flag
74 74 from IPython.core.application import (
75 75 BaseIPythonApplication, base_flags, base_aliases,
76 76 )
77 77 from IPython.core.profiledir import ProfileDir
78 78 from IPython.kernel import KernelManager
79 79 from IPython.kernel.kernelspec import KernelSpecManager
80 80 from IPython.kernel.zmq.session import default_secure, Session
81 81 from IPython.nbformat.sign import NotebookNotary
82 82 from IPython.utils.importstring import import_item
83 83 from IPython.utils import submodule
84 84 from IPython.utils.process import check_pid
85 85 from IPython.utils.traitlets import (
86 86 Dict, Unicode, Integer, List, Bool, Bytes, Instance,
87 87 TraitError, Type,
88 88 )
89 89 from IPython.utils import py3compat
90 90 from IPython.utils.path import filefind, get_ipython_dir
91 91 from IPython.utils.sysinfo import get_sys_info
92 92
93 93 from .nbextensions import SYSTEM_NBEXTENSIONS_DIRS
94 94 from .utils import url_path_join
95 95
96 96 #-----------------------------------------------------------------------------
97 97 # Module globals
98 98 #-----------------------------------------------------------------------------
99 99
100 100 _examples = """
101 101 ipython notebook # start the notebook
102 102 ipython notebook --profile=sympy # use the sympy profile
103 103 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
104 104 """
105 105
106 106 #-----------------------------------------------------------------------------
107 107 # Helper functions
108 108 #-----------------------------------------------------------------------------
109 109
110 110 def random_ports(port, n):
111 111 """Generate a list of n random ports near the given port.
112 112
113 113 The first 5 ports will be sequential, and the remaining n-5 will be
114 114 randomly selected in the range [port-2*n, port+2*n].
115 115 """
116 116 for i in range(min(5, n)):
117 117 yield port + i
118 118 for i in range(n-5):
119 119 yield max(1, port + random.randint(-2*n, 2*n))
120 120
121 121 def load_handlers(name):
122 122 """Load the (URL pattern, handler) tuples for each component."""
123 123 name = 'IPython.html.' + name
124 124 mod = __import__(name, fromlist=['default_handlers'])
125 125 return mod.default_handlers
126 126
127 127 #-----------------------------------------------------------------------------
128 128 # The Tornado web application
129 129 #-----------------------------------------------------------------------------
130 130
131 131 class NotebookWebApplication(web.Application):
132 132
133 133 def __init__(self, ipython_app, kernel_manager, contents_manager,
134 134 cluster_manager, session_manager, kernel_spec_manager,
135 135 config_manager, log,
136 136 base_url, default_url, settings_overrides, jinja_env_options):
137 137
138 138 settings = self.init_settings(
139 139 ipython_app, kernel_manager, contents_manager, cluster_manager,
140 140 session_manager, kernel_spec_manager, config_manager, log, base_url,
141 141 default_url, settings_overrides, jinja_env_options)
142 142 handlers = self.init_handlers(settings)
143 143
144 144 super(NotebookWebApplication, self).__init__(handlers, **settings)
145 145
146 146 def init_settings(self, ipython_app, kernel_manager, contents_manager,
147 147 cluster_manager, session_manager, kernel_spec_manager,
148 148 config_manager,
149 149 log, base_url, default_url, settings_overrides,
150 150 jinja_env_options=None):
151 151
152 152 _template_path = settings_overrides.get(
153 153 "template_path",
154 154 ipython_app.template_file_path,
155 155 )
156 156 if isinstance(_template_path, str):
157 157 _template_path = (_template_path,)
158 158 template_path = [os.path.expanduser(path) for path in _template_path]
159 159
160 160 jenv_opt = jinja_env_options if jinja_env_options else {}
161 161 env = Environment(loader=FileSystemLoader(template_path), **jenv_opt)
162 162
163 163 sys_info = get_sys_info()
164 164 if sys_info['commit_source'] == 'repository':
165 165 # don't cache (rely on 304) when working from master
166 166 version_hash = ''
167 167 else:
168 168 # reset the cache on server restart
169 169 version_hash = datetime.datetime.now().strftime("%Y%m%d%H%M%S")
170 170
171 171 settings = dict(
172 172 # basics
173 173 log_function=log_request,
174 174 base_url=base_url,
175 175 default_url=default_url,
176 176 template_path=template_path,
177 177 static_path=ipython_app.static_file_path,
178 178 static_handler_class = FileFindHandler,
179 179 static_url_prefix = url_path_join(base_url,'/static/'),
180 180 static_handler_args = {
181 181 # don't cache custom.js
182 182 'no_cache_paths': [url_path_join(base_url, 'static', 'custom')],
183 183 },
184 184 version_hash=version_hash,
185 185
186 186 # authentication
187 187 cookie_secret=ipython_app.cookie_secret,
188 188 login_url=url_path_join(base_url,'/login'),
189 189 login_handler_class=ipython_app.login_handler_class,
190 190 logout_handler_class=ipython_app.logout_handler_class,
191 191 password=ipython_app.password,
192 192
193 193 # managers
194 194 kernel_manager=kernel_manager,
195 195 contents_manager=contents_manager,
196 196 cluster_manager=cluster_manager,
197 197 session_manager=session_manager,
198 198 kernel_spec_manager=kernel_spec_manager,
199 199 config_manager=config_manager,
200 200
201 201 # IPython stuff
202 202 nbextensions_path=ipython_app.nbextensions_path,
203 203 websocket_url=ipython_app.websocket_url,
204 204 mathjax_url=ipython_app.mathjax_url,
205 205 config=ipython_app.config,
206 206 jinja2_env=env,
207 207 terminals_available=False, # Set later if terminals are available
208 208 )
209 209
210 210 # allow custom overrides for the tornado web app.
211 211 settings.update(settings_overrides)
212 212 return settings
213 213
214 214 def init_handlers(self, settings):
215 215 """Load the (URL pattern, handler) tuples for each component."""
216 216
217 217 # Order matters. The first handler to match the URL will handle the request.
218 218 handlers = []
219 219 handlers.extend(load_handlers('tree.handlers'))
220 220 handlers.extend([(r"/login", settings['login_handler_class'])])
221 221 handlers.extend([(r"/logout", settings['logout_handler_class'])])
222 222 handlers.extend(load_handlers('files.handlers'))
223 223 handlers.extend(load_handlers('notebook.handlers'))
224 224 handlers.extend(load_handlers('nbconvert.handlers'))
225 225 handlers.extend(load_handlers('kernelspecs.handlers'))
226 226 handlers.extend(load_handlers('edit.handlers'))
227 227 handlers.extend(load_handlers('services.config.handlers'))
228 228 handlers.extend(load_handlers('services.kernels.handlers'))
229 229 handlers.extend(load_handlers('services.contents.handlers'))
230 230 handlers.extend(load_handlers('services.clusters.handlers'))
231 231 handlers.extend(load_handlers('services.sessions.handlers'))
232 232 handlers.extend(load_handlers('services.nbconvert.handlers'))
233 233 handlers.extend(load_handlers('services.kernelspecs.handlers'))
234 234 handlers.extend(load_handlers('services.security.handlers'))
235 235 handlers.append(
236 236 (r"/nbextensions/(.*)", FileFindHandler, {
237 237 'path': settings['nbextensions_path'],
238 238 'no_cache_paths': ['/'], # don't cache anything in nbextensions
239 239 }),
240 240 )
241 241 # register base handlers last
242 242 handlers.extend(load_handlers('base.handlers'))
243 243 # set the URL that will be redirected from `/`
244 244 handlers.append(
245 245 (r'/?', web.RedirectHandler, {
246 246 'url' : settings['default_url'],
247 247 'permanent': False, # want 302, not 301
248 248 })
249 249 )
250 250 # prepend base_url onto the patterns that we match
251 251 new_handlers = []
252 252 for handler in handlers:
253 253 pattern = url_path_join(settings['base_url'], handler[0])
254 254 new_handler = tuple([pattern] + list(handler[1:]))
255 255 new_handlers.append(new_handler)
256 256 # add 404 on the end, which will catch everything that falls through
257 257 new_handlers.append((r'(.*)', Template404))
258 258 return new_handlers
259 259
260 260
261 261 class NbserverListApp(BaseIPythonApplication):
262 262
263 263 description="List currently running notebook servers in this profile."
264 264
265 265 flags = dict(
266 266 json=({'NbserverListApp': {'json': True}},
267 267 "Produce machine-readable JSON output."),
268 268 )
269 269
270 270 json = Bool(False, config=True,
271 271 help="If True, each line of output will be a JSON object with the "
272 272 "details from the server info file.")
273 273
274 274 def start(self):
275 275 if not self.json:
276 276 print("Currently running servers:")
277 277 for serverinfo in list_running_servers(self.profile):
278 278 if self.json:
279 279 print(json.dumps(serverinfo))
280 280 else:
281 281 print(serverinfo['url'], "::", serverinfo['notebook_dir'])
282 282
283 283 #-----------------------------------------------------------------------------
284 284 # Aliases and Flags
285 285 #-----------------------------------------------------------------------------
286 286
287 287 flags = dict(base_flags)
288 288 flags['no-browser']=(
289 289 {'NotebookApp' : {'open_browser' : False}},
290 290 "Don't open the notebook in a browser after startup."
291 291 )
292 292 flags['pylab']=(
293 293 {'NotebookApp' : {'pylab' : 'warn'}},
294 294 "DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib."
295 295 )
296 296 flags['no-mathjax']=(
297 297 {'NotebookApp' : {'enable_mathjax' : False}},
298 298 """Disable MathJax
299 299
300 300 MathJax is the javascript library IPython uses to render math/LaTeX. It is
301 301 very large, so you may want to disable it if you have a slow internet
302 302 connection, or for offline use of the notebook.
303 303
304 304 When disabled, equations etc. will appear as their untransformed TeX source.
305 305 """
306 306 )
307 307
308 308 # Add notebook manager flags
309 309 flags.update(boolean_flag('script', 'FileContentsManager.save_script',
310 310 'DEPRECATED, IGNORED',
311 311 'DEPRECATED, IGNORED'))
312 312
313 313 aliases = dict(base_aliases)
314 314
315 315 aliases.update({
316 316 'ip': 'NotebookApp.ip',
317 317 'port': 'NotebookApp.port',
318 318 'port-retries': 'NotebookApp.port_retries',
319 319 'transport': 'KernelManager.transport',
320 320 'keyfile': 'NotebookApp.keyfile',
321 321 'certfile': 'NotebookApp.certfile',
322 322 'notebook-dir': 'NotebookApp.notebook_dir',
323 323 'browser': 'NotebookApp.browser',
324 324 'pylab': 'NotebookApp.pylab',
325 325 })
326 326
327 327 #-----------------------------------------------------------------------------
328 328 # NotebookApp
329 329 #-----------------------------------------------------------------------------
330 330
331 331 class NotebookApp(BaseIPythonApplication):
332 332
333 333 name = 'ipython-notebook'
334 334
335 335 description = """
336 336 The IPython HTML Notebook.
337 337
338 338 This launches a Tornado based HTML Notebook Server that serves up an
339 339 HTML5/Javascript Notebook client.
340 340 """
341 341 examples = _examples
342 342 aliases = aliases
343 343 flags = flags
344 344
345 345 classes = [
346 346 KernelManager, ProfileDir, Session, MappingKernelManager,
347 347 ContentsManager, FileContentsManager, NotebookNotary,
348 348 KernelSpecManager,
349 349 ]
350 350 flags = Dict(flags)
351 351 aliases = Dict(aliases)
352 352
353 353 subcommands = dict(
354 354 list=(NbserverListApp, NbserverListApp.description.splitlines()[0]),
355 355 )
356 356
357 357 ipython_kernel_argv = List(Unicode)
358 358
359 359 _log_formatter_cls = LogFormatter
360 360
361 361 def _log_level_default(self):
362 362 return logging.INFO
363 363
364 364 def _log_datefmt_default(self):
365 365 """Exclude date from default date format"""
366 366 return "%H:%M:%S"
367 367
368 368 def _log_format_default(self):
369 369 """override default log format to include time"""
370 370 return u"%(color)s[%(levelname)1.1s %(asctime)s.%(msecs).03d %(name)s]%(end_color)s %(message)s"
371 371
372 372 # create requested profiles by default, if they don't exist:
373 373 auto_create = Bool(True)
374 374
375 375 # file to be opened in the notebook server
376 376 file_to_run = Unicode('', config=True)
377 377
378 378 # Network related information
379 379
380 380 allow_origin = Unicode('', config=True,
381 381 help="""Set the Access-Control-Allow-Origin header
382 382
383 383 Use '*' to allow any origin to access your server.
384 384
385 385 Takes precedence over allow_origin_pat.
386 386 """
387 387 )
388 388
389 389 allow_origin_pat = Unicode('', config=True,
390 390 help="""Use a regular expression for the Access-Control-Allow-Origin header
391 391
392 392 Requests from an origin matching the expression will get replies with:
393 393
394 394 Access-Control-Allow-Origin: origin
395 395
396 396 where `origin` is the origin of the request.
397 397
398 398 Ignored if allow_origin is set.
399 399 """
400 400 )
401 401
402 402 allow_credentials = Bool(False, config=True,
403 403 help="Set the Access-Control-Allow-Credentials: true header"
404 404 )
405 405
406 406 default_url = Unicode('/tree', config=True,
407 407 help="The default URL to redirect to from `/`"
408 408 )
409 409
410 410 ip = Unicode('localhost', config=True,
411 411 help="The IP address the notebook server will listen on."
412 412 )
413 413
414 414 def _ip_changed(self, name, old, new):
415 415 if new == u'*': self.ip = u''
416 416
417 417 port = Integer(8888, config=True,
418 418 help="The port the notebook server will listen on."
419 419 )
420 420 port_retries = Integer(50, config=True,
421 421 help="The number of additional ports to try if the specified port is not available."
422 422 )
423 423
424 424 certfile = Unicode(u'', config=True,
425 425 help="""The full path to an SSL/TLS certificate file."""
426 426 )
427 427
428 428 keyfile = Unicode(u'', config=True,
429 429 help="""The full path to a private key file for usage with SSL/TLS."""
430 430 )
431 431
432 432 cookie_secret_file = Unicode(config=True,
433 433 help="""The file where the cookie secret is stored."""
434 434 )
435 435 def _cookie_secret_file_default(self):
436 436 if self.profile_dir is None:
437 437 return ''
438 438 return os.path.join(self.profile_dir.security_dir, 'notebook_cookie_secret')
439 439
440 440 cookie_secret = Bytes(b'', config=True,
441 441 help="""The random bytes used to secure cookies.
442 442 By default this is a new random number every time you start the Notebook.
443 443 Set it to a value in a config file to enable logins to persist across server sessions.
444 444
445 445 Note: Cookie secrets should be kept private, do not share config files with
446 446 cookie_secret stored in plaintext (you can read the value from a file).
447 447 """
448 448 )
449 449 def _cookie_secret_default(self):
450 450 if os.path.exists(self.cookie_secret_file):
451 451 with io.open(self.cookie_secret_file, 'rb') as f:
452 452 return f.read()
453 453 else:
454 454 secret = base64.encodestring(os.urandom(1024))
455 455 self._write_cookie_secret_file(secret)
456 456 return secret
457 457
458 458 def _write_cookie_secret_file(self, secret):
459 459 """write my secret to my secret_file"""
460 460 self.log.info("Writing notebook server cookie secret to %s", self.cookie_secret_file)
461 461 with io.open(self.cookie_secret_file, 'wb') as f:
462 462 f.write(secret)
463 463 try:
464 464 os.chmod(self.cookie_secret_file, 0o600)
465 465 except OSError:
466 466 self.log.warn(
467 467 "Could not set permissions on %s",
468 468 self.cookie_secret_file
469 469 )
470 470
471 471 password = Unicode(u'', config=True,
472 472 help="""Hashed password to use for web authentication.
473 473
474 474 To generate, type in a python/IPython shell:
475 475
476 476 from IPython.lib import passwd; passwd()
477 477
478 478 The string should be of the form type:salt:hashed-password.
479 479 """
480 480 )
481 481
482 482 open_browser = Bool(True, config=True,
483 483 help="""Whether to open in a browser after starting.
484 484 The specific browser used is platform dependent and
485 485 determined by the python standard library `webbrowser`
486 486 module, unless it is overridden using the --browser
487 487 (NotebookApp.browser) configuration option.
488 488 """)
489 489
490 490 browser = Unicode(u'', config=True,
491 491 help="""Specify what command to use to invoke a web
492 492 browser when opening the notebook. If not specified, the
493 493 default browser will be determined by the `webbrowser`
494 494 standard library module, which allows setting of the
495 495 BROWSER environment variable to override it.
496 496 """)
497 497
498 498 webapp_settings = Dict(config=True,
499 499 help="DEPRECATED, use tornado_settings"
500 500 )
501 501 def _webapp_settings_changed(self, name, old, new):
502 502 self.log.warn("\n webapp_settings is deprecated, use tornado_settings.\n")
503 503 self.tornado_settings = new
504 504
505 505 tornado_settings = Dict(config=True,
506 506 help="Supply overrides for the tornado.web.Application that the "
507 507 "IPython notebook uses.")
508
508
509 ssl_options = Dict(config=True,
510 help="""Supply SSL options for the tornado HTTPServer.
511 See the tornado docs for details.""")
512
509 513 jinja_environment_options = Dict(config=True,
510 514 help="Supply extra arguments that will be passed to Jinja environment.")
511 515
512 516 enable_mathjax = Bool(True, config=True,
513 517 help="""Whether to enable MathJax for typesetting math/TeX
514 518
515 519 MathJax is the javascript library IPython uses to render math/LaTeX. It is
516 520 very large, so you may want to disable it if you have a slow internet
517 521 connection, or for offline use of the notebook.
518 522
519 523 When disabled, equations etc. will appear as their untransformed TeX source.
520 524 """
521 525 )
522 526 def _enable_mathjax_changed(self, name, old, new):
523 527 """set mathjax url to empty if mathjax is disabled"""
524 528 if not new:
525 529 self.mathjax_url = u''
526 530
527 531 base_url = Unicode('/', config=True,
528 532 help='''The base URL for the notebook server.
529 533
530 534 Leading and trailing slashes can be omitted,
531 535 and will automatically be added.
532 536 ''')
533 537 def _base_url_changed(self, name, old, new):
534 538 if not new.startswith('/'):
535 539 self.base_url = '/'+new
536 540 elif not new.endswith('/'):
537 541 self.base_url = new+'/'
538 542
539 543 base_project_url = Unicode('/', config=True, help="""DEPRECATED use base_url""")
540 544 def _base_project_url_changed(self, name, old, new):
541 545 self.log.warn("base_project_url is deprecated, use base_url")
542 546 self.base_url = new
543 547
544 548 extra_static_paths = List(Unicode, config=True,
545 549 help="""Extra paths to search for serving static files.
546 550
547 551 This allows adding javascript/css to be available from the notebook server machine,
548 552 or overriding individual files in the IPython"""
549 553 )
550 554 def _extra_static_paths_default(self):
551 555 return [os.path.join(self.profile_dir.location, 'static')]
552 556
553 557 @property
554 558 def static_file_path(self):
555 559 """return extra paths + the default location"""
556 560 return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH]
557 561
558 562 extra_template_paths = List(Unicode, config=True,
559 563 help="""Extra paths to search for serving jinja templates.
560 564
561 565 Can be used to override templates from IPython.html.templates."""
562 566 )
563 567 def _extra_template_paths_default(self):
564 568 return []
565 569
566 570 @property
567 571 def template_file_path(self):
568 572 """return extra paths + the default locations"""
569 573 return self.extra_template_paths + DEFAULT_TEMPLATE_PATH_LIST
570 574
571 575 extra_nbextensions_path = List(Unicode, config=True,
572 576 help="""extra paths to look for Javascript notebook extensions"""
573 577 )
574 578
575 579 @property
576 580 def nbextensions_path(self):
577 581 """The path to look for Javascript notebook extensions"""
578 582 return self.extra_nbextensions_path + [os.path.join(get_ipython_dir(), 'nbextensions')] + SYSTEM_NBEXTENSIONS_DIRS
579 583
580 584 websocket_url = Unicode("", config=True,
581 585 help="""The base URL for websockets,
582 586 if it differs from the HTTP server (hint: it almost certainly doesn't).
583 587
584 588 Should be in the form of an HTTP origin: ws[s]://hostname[:port]
585 589 """
586 590 )
587 591 mathjax_url = Unicode("", config=True,
588 592 help="""The url for MathJax.js."""
589 593 )
590 594 def _mathjax_url_default(self):
591 595 if not self.enable_mathjax:
592 596 return u''
593 597 static_url_prefix = self.tornado_settings.get("static_url_prefix",
594 598 url_path_join(self.base_url, "static")
595 599 )
596 600
597 601 # try local mathjax, either in nbextensions/mathjax or static/mathjax
598 602 for (url_prefix, search_path) in [
599 603 (url_path_join(self.base_url, "nbextensions"), self.nbextensions_path),
600 604 (static_url_prefix, self.static_file_path),
601 605 ]:
602 606 self.log.debug("searching for local mathjax in %s", search_path)
603 607 try:
604 608 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), search_path)
605 609 except IOError:
606 610 continue
607 611 else:
608 612 url = url_path_join(url_prefix, u"mathjax/MathJax.js")
609 613 self.log.info("Serving local MathJax from %s at %s", mathjax, url)
610 614 return url
611 615
612 616 # no local mathjax, serve from CDN
613 617 url = u"https://cdn.mathjax.org/mathjax/latest/MathJax.js"
614 618 self.log.info("Using MathJax from CDN: %s", url)
615 619 return url
616 620
617 621 def _mathjax_url_changed(self, name, old, new):
618 622 if new and not self.enable_mathjax:
619 623 # enable_mathjax=False overrides mathjax_url
620 624 self.mathjax_url = u''
621 625 else:
622 626 self.log.info("Using MathJax: %s", new)
623 627
624 628 contents_manager_class = Type(
625 629 default_value=FileContentsManager,
626 630 klass=ContentsManager,
627 631 config=True,
628 632 help='The notebook manager class to use.'
629 633 )
630 634 kernel_manager_class = Type(
631 635 default_value=MappingKernelManager,
632 636 config=True,
633 637 help='The kernel manager class to use.'
634 638 )
635 639 session_manager_class = Type(
636 640 default_value=SessionManager,
637 641 config=True,
638 642 help='The session manager class to use.'
639 643 )
640 644 cluster_manager_class = Type(
641 645 default_value=ClusterManager,
642 646 config=True,
643 647 help='The cluster manager class to use.'
644 648 )
645 649
646 650 config_manager_class = Type(
647 651 default_value=ConfigManager,
648 652 config = True,
649 653 help='The config manager class to use'
650 654 )
651 655
652 656 kernel_spec_manager = Instance(KernelSpecManager)
653 657
654 658 kernel_spec_manager_class = Type(
655 659 default_value=KernelSpecManager,
656 660 config=True,
657 661 help="""
658 662 The kernel spec manager class to use. Should be a subclass
659 663 of `IPython.kernel.kernelspec.KernelSpecManager`.
660 664
661 665 The Api of KernelSpecManager is provisional and might change
662 666 without warning between this version of IPython and the next stable one.
663 667 """
664 668 )
665 669
666 670 login_handler_class = Type(
667 671 default_value=LoginHandler,
668 672 klass=web.RequestHandler,
669 673 config=True,
670 674 help='The login handler class to use.',
671 675 )
672 676
673 677 logout_handler_class = Type(
674 678 default_value=LogoutHandler,
675 679 klass=web.RequestHandler,
676 680 config=True,
677 681 help='The logout handler class to use.',
678 682 )
679 683
680 684 trust_xheaders = Bool(False, config=True,
681 685 help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
682 686 "sent by the upstream reverse proxy. Necessary if the proxy handles SSL")
683 687 )
684 688
685 689 info_file = Unicode()
686 690
687 691 def _info_file_default(self):
688 692 info_file = "nbserver-%s.json"%os.getpid()
689 693 return os.path.join(self.profile_dir.security_dir, info_file)
690 694
691 695 pylab = Unicode('disabled', config=True,
692 696 help="""
693 697 DISABLED: use %pylab or %matplotlib in the notebook to enable matplotlib.
694 698 """
695 699 )
696 700 def _pylab_changed(self, name, old, new):
697 701 """when --pylab is specified, display a warning and exit"""
698 702 if new != 'warn':
699 703 backend = ' %s' % new
700 704 else:
701 705 backend = ''
702 706 self.log.error("Support for specifying --pylab on the command line has been removed.")
703 707 self.log.error(
704 708 "Please use `%pylab{0}` or `%matplotlib{0}` in the notebook itself.".format(backend)
705 709 )
706 710 self.exit(1)
707 711
708 712 notebook_dir = Unicode(config=True,
709 713 help="The directory to use for notebooks and kernels."
710 714 )
711 715
712 716 def _notebook_dir_default(self):
713 717 if self.file_to_run:
714 718 return os.path.dirname(os.path.abspath(self.file_to_run))
715 719 else:
716 720 return py3compat.getcwd()
717 721
718 722 def _notebook_dir_changed(self, name, old, new):
719 723 """Do a bit of validation of the notebook dir."""
720 724 if not os.path.isabs(new):
721 725 # If we receive a non-absolute path, make it absolute.
722 726 self.notebook_dir = os.path.abspath(new)
723 727 return
724 728 if not os.path.isdir(new):
725 729 raise TraitError("No such notebook dir: %r" % new)
726 730
727 731 # setting App.notebook_dir implies setting notebook and kernel dirs as well
728 732 self.config.FileContentsManager.root_dir = new
729 733 self.config.MappingKernelManager.root_dir = new
730 734
731 735 server_extensions = List(Unicode(), config=True,
732 736 help=("Python modules to load as notebook server extensions. "
733 737 "This is an experimental API, and may change in future releases.")
734 738 )
735 739
736 740 def parse_command_line(self, argv=None):
737 741 super(NotebookApp, self).parse_command_line(argv)
738 742
739 743 if self.extra_args:
740 744 arg0 = self.extra_args[0]
741 745 f = os.path.abspath(arg0)
742 746 self.argv.remove(arg0)
743 747 if not os.path.exists(f):
744 748 self.log.critical("No such file or directory: %s", f)
745 749 self.exit(1)
746 750
747 751 # Use config here, to ensure that it takes higher priority than
748 752 # anything that comes from the profile.
749 753 c = Config()
750 754 if os.path.isdir(f):
751 755 c.NotebookApp.notebook_dir = f
752 756 elif os.path.isfile(f):
753 757 c.NotebookApp.file_to_run = f
754 758 self.update_config(c)
755 759
756 760 def init_kernel_argv(self):
757 761 """add the profile-dir to arguments to be passed to IPython kernels"""
758 762 # FIXME: remove special treatment of IPython kernels
759 763 # Kernel should get *absolute* path to profile directory
760 764 self.ipython_kernel_argv = ["--profile-dir", self.profile_dir.location]
761 765
762 766 def init_configurables(self):
763 767 # force Session default to be secure
764 768 default_secure(self.config)
765 769
766 770 self.kernel_spec_manager = self.kernel_spec_manager_class(
767 771 parent=self,
768 772 ipython_dir=self.ipython_dir,
769 773 )
770 774 self.kernel_manager = self.kernel_manager_class(
771 775 parent=self,
772 776 log=self.log,
773 777 ipython_kernel_argv=self.ipython_kernel_argv,
774 778 connection_dir=self.profile_dir.security_dir,
775 779 )
776 780 self.contents_manager = self.contents_manager_class(
777 781 parent=self,
778 782 log=self.log,
779 783 )
780 784 self.session_manager = self.session_manager_class(
781 785 parent=self,
782 786 log=self.log,
783 787 kernel_manager=self.kernel_manager,
784 788 contents_manager=self.contents_manager,
785 789 )
786 790 self.cluster_manager = self.cluster_manager_class(
787 791 parent=self,
788 792 log=self.log,
789 793 )
790 794
791 795 self.config_manager = self.config_manager_class(
792 796 parent=self,
793 797 log=self.log,
794 798 profile_dir=self.profile_dir.location,
795 799 )
796 800
797 801 def init_logging(self):
798 802 # This prevents double log messages because tornado use a root logger that
799 803 # self.log is a child of. The logging module dipatches log messages to a log
800 804 # and all of its ancenstors until propagate is set to False.
801 805 self.log.propagate = False
802 806
803 807 for log in app_log, access_log, gen_log:
804 808 # consistent log output name (NotebookApp instead of tornado.access, etc.)
805 809 log.name = self.log.name
806 810 # hook up tornado 3's loggers to our app handlers
807 811 logger = logging.getLogger('tornado')
808 812 logger.propagate = True
809 813 logger.parent = self.log
810 814 logger.setLevel(self.log.level)
811 815
812 816 def init_webapp(self):
813 817 """initialize tornado webapp and httpserver"""
814 818 self.tornado_settings['allow_origin'] = self.allow_origin
815 819 if self.allow_origin_pat:
816 820 self.tornado_settings['allow_origin_pat'] = re.compile(self.allow_origin_pat)
817 821 self.tornado_settings['allow_credentials'] = self.allow_credentials
818 822 # ensure default_url starts with base_url
819 823 if not self.default_url.startswith(self.base_url):
820 824 self.default_url = url_path_join(self.base_url, self.default_url)
821 825
822 826 self.web_app = NotebookWebApplication(
823 827 self, self.kernel_manager, self.contents_manager,
824 828 self.cluster_manager, self.session_manager, self.kernel_spec_manager,
825 829 self.config_manager,
826 830 self.log, self.base_url, self.default_url, self.tornado_settings,
827 831 self.jinja_environment_options
828 832 )
833 ssl_options = self.ssl_options
829 834 if self.certfile:
830 ssl_options = dict(certfile=self.certfile)
831 if self.keyfile:
832 ssl_options['keyfile'] = self.keyfile
833 else:
835 ssl_options['certfile'] = self.certfile
836 if self.keyfile:
837 ssl_options['keyfile'] = self.keyfile
838 if not ssl_options:
839 # None indicates no SSL config
834 840 ssl_options = None
835 841 self.login_handler_class.validate_security(self, ssl_options=ssl_options)
836 842 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
837 843 xheaders=self.trust_xheaders)
838 844
839 845 success = None
840 846 for port in random_ports(self.port, self.port_retries+1):
841 847 try:
842 848 self.http_server.listen(port, self.ip)
843 849 except socket.error as e:
844 850 if e.errno == errno.EADDRINUSE:
845 851 self.log.info('The port %i is already in use, trying another random port.' % port)
846 852 continue
847 853 elif e.errno in (errno.EACCES, getattr(errno, 'WSAEACCES', errno.EACCES)):
848 854 self.log.warn("Permission to listen on port %i denied" % port)
849 855 continue
850 856 else:
851 857 raise
852 858 else:
853 859 self.port = port
854 860 success = True
855 861 break
856 862 if not success:
857 863 self.log.critical('ERROR: the notebook server could not be started because '
858 864 'no available port could be found.')
859 865 self.exit(1)
860 866
861 867 @property
862 868 def display_url(self):
863 869 ip = self.ip if self.ip else '[all ip addresses on your system]'
864 870 return self._url(ip)
865 871
866 872 @property
867 873 def connection_url(self):
868 874 ip = self.ip if self.ip else 'localhost'
869 875 return self._url(ip)
870 876
871 877 def _url(self, ip):
872 878 proto = 'https' if self.certfile else 'http'
873 879 return "%s://%s:%i%s" % (proto, ip, self.port, self.base_url)
874 880
875 881 def init_terminals(self):
876 882 try:
877 883 from .terminal import initialize
878 884 initialize(self.web_app)
879 885 self.web_app.settings['terminals_available'] = True
880 886 except ImportError as e:
881 887 log = self.log.debug if sys.platform == 'win32' else self.log.warn
882 888 log("Terminals not available (error was %s)", e)
883 889
884 890 def init_signal(self):
885 891 if not sys.platform.startswith('win'):
886 892 signal.signal(signal.SIGINT, self._handle_sigint)
887 893 signal.signal(signal.SIGTERM, self._signal_stop)
888 894 if hasattr(signal, 'SIGUSR1'):
889 895 # Windows doesn't support SIGUSR1
890 896 signal.signal(signal.SIGUSR1, self._signal_info)
891 897 if hasattr(signal, 'SIGINFO'):
892 898 # only on BSD-based systems
893 899 signal.signal(signal.SIGINFO, self._signal_info)
894 900
895 901 def _handle_sigint(self, sig, frame):
896 902 """SIGINT handler spawns confirmation dialog"""
897 903 # register more forceful signal handler for ^C^C case
898 904 signal.signal(signal.SIGINT, self._signal_stop)
899 905 # request confirmation dialog in bg thread, to avoid
900 906 # blocking the App
901 907 thread = threading.Thread(target=self._confirm_exit)
902 908 thread.daemon = True
903 909 thread.start()
904 910
905 911 def _restore_sigint_handler(self):
906 912 """callback for restoring original SIGINT handler"""
907 913 signal.signal(signal.SIGINT, self._handle_sigint)
908 914
909 915 def _confirm_exit(self):
910 916 """confirm shutdown on ^C
911 917
912 918 A second ^C, or answering 'y' within 5s will cause shutdown,
913 919 otherwise original SIGINT handler will be restored.
914 920
915 921 This doesn't work on Windows.
916 922 """
917 923 info = self.log.info
918 924 info('interrupted')
919 925 print(self.notebook_info())
920 926 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
921 927 sys.stdout.flush()
922 928 r,w,x = select.select([sys.stdin], [], [], 5)
923 929 if r:
924 930 line = sys.stdin.readline()
925 931 if line.lower().startswith('y') and 'n' not in line.lower():
926 932 self.log.critical("Shutdown confirmed")
927 933 ioloop.IOLoop.current().stop()
928 934 return
929 935 else:
930 936 print("No answer for 5s:", end=' ')
931 937 print("resuming operation...")
932 938 # no answer, or answer is no:
933 939 # set it back to original SIGINT handler
934 940 # use IOLoop.add_callback because signal.signal must be called
935 941 # from main thread
936 942 ioloop.IOLoop.current().add_callback(self._restore_sigint_handler)
937 943
938 944 def _signal_stop(self, sig, frame):
939 945 self.log.critical("received signal %s, stopping", sig)
940 946 ioloop.IOLoop.current().stop()
941 947
942 948 def _signal_info(self, sig, frame):
943 949 print(self.notebook_info())
944 950
945 951 def init_components(self):
946 952 """Check the components submodule, and warn if it's unclean"""
947 953 status = submodule.check_submodule_status()
948 954 if status == 'missing':
949 955 self.log.warn("components submodule missing, running `git submodule update`")
950 956 submodule.update_submodules(submodule.ipython_parent())
951 957 elif status == 'unclean':
952 958 self.log.warn("components submodule unclean, you may see 404s on static/components")
953 959 self.log.warn("run `setup.py submodule` or `git submodule update` to update")
954 960
955 961 def init_server_extensions(self):
956 962 """Load any extensions specified by config.
957 963
958 964 Import the module, then call the load_jupyter_server_extension function,
959 965 if one exists.
960 966
961 967 The extension API is experimental, and may change in future releases.
962 968 """
963 969 for modulename in self.server_extensions:
964 970 try:
965 971 mod = importlib.import_module(modulename)
966 972 func = getattr(mod, 'load_jupyter_server_extension', None)
967 973 if func is not None:
968 974 func(self)
969 975 except Exception:
970 976 self.log.warn("Error loading server extension %s", modulename,
971 977 exc_info=True)
972 978
973 979 @catch_config_error
974 980 def initialize(self, argv=None):
975 981 super(NotebookApp, self).initialize(argv)
976 982 self.init_logging()
977 983 self.init_kernel_argv()
978 984 self.init_configurables()
979 985 self.init_components()
980 986 self.init_webapp()
981 987 self.init_terminals()
982 988 self.init_signal()
983 989 self.init_server_extensions()
984 990
985 991 def cleanup_kernels(self):
986 992 """Shutdown all kernels.
987 993
988 994 The kernels will shutdown themselves when this process no longer exists,
989 995 but explicit shutdown allows the KernelManagers to cleanup the connection files.
990 996 """
991 997 self.log.info('Shutting down kernels')
992 998 self.kernel_manager.shutdown_all()
993 999
994 1000 def notebook_info(self):
995 1001 "Return the current working directory and the server url information"
996 1002 info = self.contents_manager.info_string() + "\n"
997 1003 info += "%d active kernels \n" % len(self.kernel_manager._kernels)
998 1004 return info + "The IPython Notebook is running at: %s" % self.display_url
999 1005
1000 1006 def server_info(self):
1001 1007 """Return a JSONable dict of information about this server."""
1002 1008 return {'url': self.connection_url,
1003 1009 'hostname': self.ip if self.ip else 'localhost',
1004 1010 'port': self.port,
1005 1011 'secure': bool(self.certfile),
1006 1012 'base_url': self.base_url,
1007 1013 'notebook_dir': os.path.abspath(self.notebook_dir),
1008 1014 'pid': os.getpid()
1009 1015 }
1010 1016
1011 1017 def write_server_info_file(self):
1012 1018 """Write the result of server_info() to the JSON file info_file."""
1013 1019 with open(self.info_file, 'w') as f:
1014 1020 json.dump(self.server_info(), f, indent=2)
1015 1021
1016 1022 def remove_server_info_file(self):
1017 1023 """Remove the nbserver-<pid>.json file created for this server.
1018 1024
1019 1025 Ignores the error raised when the file has already been removed.
1020 1026 """
1021 1027 try:
1022 1028 os.unlink(self.info_file)
1023 1029 except OSError as e:
1024 1030 if e.errno != errno.ENOENT:
1025 1031 raise
1026 1032
1027 1033 def start(self):
1028 1034 """ Start the IPython Notebook server app, after initialization
1029 1035
1030 1036 This method takes no arguments so all configuration and initialization
1031 1037 must be done prior to calling this method."""
1032 1038 if self.subapp is not None:
1033 1039 return self.subapp.start()
1034 1040
1035 1041 info = self.log.info
1036 1042 for line in self.notebook_info().split("\n"):
1037 1043 info(line)
1038 1044 info("Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).")
1039 1045
1040 1046 self.write_server_info_file()
1041 1047
1042 1048 if self.open_browser or self.file_to_run:
1043 1049 try:
1044 1050 browser = webbrowser.get(self.browser or None)
1045 1051 except webbrowser.Error as e:
1046 1052 self.log.warn('No web browser found: %s.' % e)
1047 1053 browser = None
1048 1054
1049 1055 if self.file_to_run:
1050 1056 if not os.path.exists(self.file_to_run):
1051 1057 self.log.critical("%s does not exist" % self.file_to_run)
1052 1058 self.exit(1)
1053 1059
1054 1060 relpath = os.path.relpath(self.file_to_run, self.notebook_dir)
1055 1061 uri = url_path_join('notebooks', *relpath.split(os.sep))
1056 1062 else:
1057 1063 uri = 'tree'
1058 1064 if browser:
1059 1065 b = lambda : browser.open(url_path_join(self.connection_url, uri),
1060 1066 new=2)
1061 1067 threading.Thread(target=b).start()
1062 1068
1063 1069 self.io_loop = ioloop.IOLoop.current()
1064 1070 if sys.platform.startswith('win'):
1065 1071 # add no-op to wake every 5s
1066 1072 # to handle signals that may be ignored by the inner loop
1067 1073 pc = ioloop.PeriodicCallback(lambda : None, 5000)
1068 1074 pc.start()
1069 1075 try:
1070 1076 self.io_loop.start()
1071 1077 except KeyboardInterrupt:
1072 1078 info("Interrupted...")
1073 1079 finally:
1074 1080 self.cleanup_kernels()
1075 1081 self.remove_server_info_file()
1076 1082
1077 1083 def stop(self):
1078 1084 def _stop():
1079 1085 self.http_server.stop()
1080 1086 self.io_loop.stop()
1081 1087 self.io_loop.add_callback(_stop)
1082 1088
1083 1089
1084 1090 def list_running_servers(profile='default'):
1085 1091 """Iterate over the server info files of running notebook servers.
1086 1092
1087 1093 Given a profile name, find nbserver-* files in the security directory of
1088 1094 that profile, and yield dicts of their information, each one pertaining to
1089 1095 a currently running notebook server instance.
1090 1096 """
1091 1097 pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), name=profile)
1092 1098 for file in os.listdir(pd.security_dir):
1093 1099 if file.startswith('nbserver-'):
1094 1100 with io.open(os.path.join(pd.security_dir, file), encoding='utf-8') as f:
1095 1101 info = json.load(f)
1096 1102
1097 1103 # Simple check whether that process is really still running
1098 1104 # Also remove leftover files from IPython 2.x without a pid field
1099 1105 if ('pid' in info) and check_pid(info['pid']):
1100 1106 yield info
1101 1107 else:
1102 1108 # If the process has died, try to delete its info file
1103 1109 try:
1104 1110 os.unlink(file)
1105 1111 except OSError:
1106 1112 pass # TODO: This should warn or log or something
1107 1113 #-----------------------------------------------------------------------------
1108 1114 # Main entry point
1109 1115 #-----------------------------------------------------------------------------
1110 1116
1111 1117 launch_new_instance = NotebookApp.launch_instance
1112 1118
General Comments 0
You need to be logged in to leave comments. Login now