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