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