##// END OF EJS Templates
Added trust_xheaders config option to delegate it to HTTPServer....
Alberto Valverde -
Show More
@@ -1,681 +1,687
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 #-----------------------------------------------------------------------------
9 9 # Copyright (C) 2008-2011 The IPython Development Team
10 10 #
11 11 # Distributed under the terms of the BSD License. The full license is in
12 12 # the file COPYING, distributed as part of this software.
13 13 #-----------------------------------------------------------------------------
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Imports
17 17 #-----------------------------------------------------------------------------
18 18
19 19 # stdlib
20 20 import errno
21 21 import logging
22 22 import os
23 23 import random
24 24 import re
25 25 import select
26 26 import signal
27 27 import socket
28 28 import sys
29 29 import threading
30 30 import time
31 31 import uuid
32 32 import webbrowser
33 33
34 34 # Third party
35 35 import zmq
36 36 from jinja2 import Environment, FileSystemLoader
37 37
38 38 # Install the pyzmq ioloop. This has to be done before anything else from
39 39 # tornado is imported.
40 40 from zmq.eventloop import ioloop
41 41 ioloop.install()
42 42
43 43 from tornado import httpserver
44 44 from tornado import web
45 45
46 46 # Our own libraries
47 47 from .kernelmanager import MappingKernelManager
48 48 from .handlers import (LoginHandler, LogoutHandler,
49 49 ProjectDashboardHandler, NewHandler, NamedNotebookHandler,
50 50 MainKernelHandler, KernelHandler, KernelActionHandler, IOPubHandler,
51 51 ShellHandler, NotebookRootHandler, NotebookHandler, NotebookCopyHandler,
52 52 RSTHandler, AuthenticatedFileHandler, PrintNotebookHandler,
53 53 MainClusterHandler, ClusterProfileHandler, ClusterActionHandler,
54 54 FileFindHandler,
55 55 )
56 56 from .nbmanager import NotebookManager
57 57 from .filenbmanager import FileNotebookManager
58 58 from .clustermanager import ClusterManager
59 59
60 60 from IPython.config.application import catch_config_error, boolean_flag
61 61 from IPython.core.application import BaseIPythonApplication
62 62 from IPython.core.profiledir import ProfileDir
63 63 from IPython.frontend.consoleapp import IPythonConsoleApp
64 64 from IPython.kernel import swallow_argv
65 65 from IPython.kernel.zmq.session import Session, default_secure
66 66 from IPython.kernel.zmq.zmqshell import ZMQInteractiveShell
67 67 from IPython.kernel.zmq.kernelapp import (
68 68 kernel_flags,
69 69 kernel_aliases,
70 70 IPKernelApp
71 71 )
72 72 from IPython.utils.importstring import import_item
73 73 from IPython.utils.localinterfaces import LOCALHOST
74 74 from IPython.utils.traitlets import (
75 75 Dict, Unicode, Integer, List, Enum, Bool,
76 76 DottedObjectName
77 77 )
78 78 from IPython.utils import py3compat
79 79 from IPython.utils.path import filefind
80 80
81 81 #-----------------------------------------------------------------------------
82 82 # Module globals
83 83 #-----------------------------------------------------------------------------
84 84
85 85 _kernel_id_regex = r"(?P<kernel_id>\w+-\w+-\w+-\w+-\w+)"
86 86 _kernel_action_regex = r"(?P<action>restart|interrupt)"
87 87 _notebook_id_regex = r"(?P<notebook_id>\w+-\w+-\w+-\w+-\w+)"
88 88 _profile_regex = r"(?P<profile>[^\/]+)" # there is almost no text that is invalid
89 89 _cluster_action_regex = r"(?P<action>start|stop)"
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 --pylab=inline # pylab in inline plotting mode
95 95 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
96 96 ipython notebook --port=5555 --ip=* # Listen on port 5555, all interfaces
97 97 """
98 98
99 99 # Packagers: modify this line if you store the notebook static files elsewhere
100 100 DEFAULT_STATIC_FILES_PATH = os.path.join(os.path.dirname(__file__), "static")
101 101
102 102 #-----------------------------------------------------------------------------
103 103 # Helper functions
104 104 #-----------------------------------------------------------------------------
105 105
106 106 def url_path_join(a,b):
107 107 if a.endswith('/') and b.startswith('/'):
108 108 return a[:-1]+b
109 109 else:
110 110 return a+b
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 port + random.randint(-2*n, 2*n)
122 122
123 123 #-----------------------------------------------------------------------------
124 124 # The Tornado web application
125 125 #-----------------------------------------------------------------------------
126 126
127 127 class NotebookWebApplication(web.Application):
128 128
129 129 def __init__(self, ipython_app, kernel_manager, notebook_manager,
130 130 cluster_manager, log,
131 131 base_project_url, settings_overrides):
132 132 handlers = [
133 133 (r"/", ProjectDashboardHandler),
134 134 (r"/login", LoginHandler),
135 135 (r"/logout", LogoutHandler),
136 136 (r"/new", NewHandler),
137 137 (r"/%s" % _notebook_id_regex, NamedNotebookHandler),
138 138 (r"/%s/copy" % _notebook_id_regex, NotebookCopyHandler),
139 139 (r"/%s/print" % _notebook_id_regex, PrintNotebookHandler),
140 140 (r"/kernels", MainKernelHandler),
141 141 (r"/kernels/%s" % _kernel_id_regex, KernelHandler),
142 142 (r"/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
143 143 (r"/kernels/%s/iopub" % _kernel_id_regex, IOPubHandler),
144 144 (r"/kernels/%s/shell" % _kernel_id_regex, ShellHandler),
145 145 (r"/notebooks", NotebookRootHandler),
146 146 (r"/notebooks/%s" % _notebook_id_regex, NotebookHandler),
147 147 (r"/rstservice/render", RSTHandler),
148 148 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : notebook_manager.notebook_dir}),
149 149 (r"/clusters", MainClusterHandler),
150 150 (r"/clusters/%s/%s" % (_profile_regex, _cluster_action_regex), ClusterActionHandler),
151 151 (r"/clusters/%s" % _profile_regex, ClusterProfileHandler),
152 152 ]
153 153
154 154 # Python < 2.6.5 doesn't accept unicode keys in f(**kwargs), and
155 155 # base_project_url will always be unicode, which will in turn
156 156 # make the patterns unicode, and ultimately result in unicode
157 157 # keys in kwargs to handler._execute(**kwargs) in tornado.
158 158 # This enforces that base_project_url be ascii in that situation.
159 159 #
160 160 # Note that the URLs these patterns check against are escaped,
161 161 # and thus guaranteed to be ASCII: 'héllo' is really 'h%C3%A9llo'.
162 162 base_project_url = py3compat.unicode_to_str(base_project_url, 'ascii')
163 163
164 164 settings = dict(
165 165 template_path=os.path.join(os.path.dirname(__file__), "templates"),
166 166 static_path=ipython_app.static_file_path,
167 167 static_handler_class = FileFindHandler,
168 168 static_url_prefix = url_path_join(base_project_url,'/static/'),
169 169 cookie_secret=os.urandom(1024),
170 170 login_url=url_path_join(base_project_url,'/login'),
171 171 cookie_name='username-%s' % uuid.uuid4(),
172 172 )
173 173
174 174 # allow custom overrides for the tornado web app.
175 175 settings.update(settings_overrides)
176 176
177 177 # prepend base_project_url onto the patterns that we match
178 178 new_handlers = []
179 179 for handler in handlers:
180 180 pattern = url_path_join(base_project_url, handler[0])
181 181 new_handler = tuple([pattern]+list(handler[1:]))
182 182 new_handlers.append( new_handler )
183 183
184 184 super(NotebookWebApplication, self).__init__(new_handlers, **settings)
185 185
186 186 self.kernel_manager = kernel_manager
187 187 self.notebook_manager = notebook_manager
188 188 self.cluster_manager = cluster_manager
189 189 self.ipython_app = ipython_app
190 190 self.read_only = self.ipython_app.read_only
191 191 self.config = self.ipython_app.config
192 192 self.use_less = self.ipython_app.use_less
193 193 self.log = log
194 194 self.jinja2_env = Environment(loader=FileSystemLoader(os.path.join(os.path.dirname(__file__), "templates")))
195 195
196 196
197 197
198 198 #-----------------------------------------------------------------------------
199 199 # Aliases and Flags
200 200 #-----------------------------------------------------------------------------
201 201
202 202 flags = dict(kernel_flags)
203 203 flags['no-browser']=(
204 204 {'NotebookApp' : {'open_browser' : False}},
205 205 "Don't open the notebook in a browser after startup."
206 206 )
207 207 flags['no-mathjax']=(
208 208 {'NotebookApp' : {'enable_mathjax' : False}},
209 209 """Disable MathJax
210 210
211 211 MathJax is the javascript library IPython uses to render math/LaTeX. It is
212 212 very large, so you may want to disable it if you have a slow internet
213 213 connection, or for offline use of the notebook.
214 214
215 215 When disabled, equations etc. will appear as their untransformed TeX source.
216 216 """
217 217 )
218 218 flags['read-only'] = (
219 219 {'NotebookApp' : {'read_only' : True}},
220 220 """Allow read-only access to notebooks.
221 221
222 222 When using a password to protect the notebook server, this flag
223 223 allows unauthenticated clients to view the notebook list, and
224 224 individual notebooks, but not edit them, start kernels, or run
225 225 code.
226 226
227 227 If no password is set, the server will be entirely read-only.
228 228 """
229 229 )
230 230
231 231 # Add notebook manager flags
232 232 flags.update(boolean_flag('script', 'FileNotebookManager.save_script',
233 233 'Auto-save a .py script everytime the .ipynb notebook is saved',
234 234 'Do not auto-save .py scripts for every notebook'))
235 235
236 236 # the flags that are specific to the frontend
237 237 # these must be scrubbed before being passed to the kernel,
238 238 # or it will raise an error on unrecognized flags
239 239 notebook_flags = ['no-browser', 'no-mathjax', 'read-only', 'script', 'no-script']
240 240
241 241 aliases = dict(kernel_aliases)
242 242
243 243 aliases.update({
244 244 'ip': 'NotebookApp.ip',
245 245 'port': 'NotebookApp.port',
246 246 'port-retries': 'NotebookApp.port_retries',
247 247 'transport': 'KernelManager.transport',
248 248 'keyfile': 'NotebookApp.keyfile',
249 249 'certfile': 'NotebookApp.certfile',
250 250 'notebook-dir': 'NotebookManager.notebook_dir',
251 251 'browser': 'NotebookApp.browser',
252 252 })
253 253
254 254 # remove ipkernel flags that are singletons, and don't make sense in
255 255 # multi-kernel evironment:
256 256 aliases.pop('f', None)
257 257
258 258 notebook_aliases = [u'port', u'port-retries', u'ip', u'keyfile', u'certfile',
259 259 u'notebook-dir']
260 260
261 261 #-----------------------------------------------------------------------------
262 262 # NotebookApp
263 263 #-----------------------------------------------------------------------------
264 264
265 265 class NotebookApp(BaseIPythonApplication):
266 266
267 267 name = 'ipython-notebook'
268 268 default_config_file_name='ipython_notebook_config.py'
269 269
270 270 description = """
271 271 The IPython HTML Notebook.
272 272
273 273 This launches a Tornado based HTML Notebook Server that serves up an
274 274 HTML5/Javascript Notebook client.
275 275 """
276 276 examples = _examples
277 277
278 278 classes = IPythonConsoleApp.classes + [MappingKernelManager, NotebookManager,
279 279 FileNotebookManager]
280 280 flags = Dict(flags)
281 281 aliases = Dict(aliases)
282 282
283 283 kernel_argv = List(Unicode)
284 284
285 285 log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
286 286 default_value=logging.INFO,
287 287 config=True,
288 288 help="Set the log level by value or name.")
289 289
290 290 # create requested profiles by default, if they don't exist:
291 291 auto_create = Bool(True)
292 292
293 293 # file to be opened in the notebook server
294 294 file_to_run = Unicode('')
295 295
296 296 # Network related information.
297 297
298 298 ip = Unicode(LOCALHOST, config=True,
299 299 help="The IP address the notebook server will listen on."
300 300 )
301 301
302 302 def _ip_changed(self, name, old, new):
303 303 if new == u'*': self.ip = u''
304 304
305 305 port = Integer(8888, config=True,
306 306 help="The port the notebook server will listen on."
307 307 )
308 308 port_retries = Integer(50, config=True,
309 309 help="The number of additional ports to try if the specified port is not available."
310 310 )
311 311
312 312 certfile = Unicode(u'', config=True,
313 313 help="""The full path to an SSL/TLS certificate file."""
314 314 )
315 315
316 316 keyfile = Unicode(u'', config=True,
317 317 help="""The full path to a private key file for usage with SSL/TLS."""
318 318 )
319 319
320 320 password = Unicode(u'', config=True,
321 321 help="""Hashed password to use for web authentication.
322 322
323 323 To generate, type in a python/IPython shell:
324 324
325 325 from IPython.lib import passwd; passwd()
326 326
327 327 The string should be of the form type:salt:hashed-password.
328 328 """
329 329 )
330 330
331 331 open_browser = Bool(True, config=True,
332 332 help="""Whether to open in a browser after starting.
333 333 The specific browser used is platform dependent and
334 334 determined by the python standard library `webbrowser`
335 335 module, unless it is overridden using the --browser
336 336 (NotebookApp.browser) configuration option.
337 337 """)
338 338
339 339 browser = Unicode(u'', config=True,
340 340 help="""Specify what command to use to invoke a web
341 341 browser when opening the notebook. If not specified, the
342 342 default browser will be determined by the `webbrowser`
343 343 standard library module, which allows setting of the
344 344 BROWSER environment variable to override it.
345 345 """)
346 346
347 347 read_only = Bool(False, config=True,
348 348 help="Whether to prevent editing/execution of notebooks."
349 349 )
350 350
351 351 use_less = Bool(False, config=True,
352 352 help="""Wether to use Browser Side less-css parsing
353 353 instead of compiled css version in templates that allows
354 354 it. This is mainly convenient when working on the less
355 355 file to avoid a build step, or if user want to overwrite
356 356 some of the less variables without having to recompile
357 357 everything.
358 358
359 359 You will need to install the less.js component in the static directory
360 360 either in the source tree or in your profile folder.
361 361 """)
362 362
363 363 webapp_settings = Dict(config=True,
364 364 help="Supply overrides for the tornado.web.Application that the "
365 365 "IPython notebook uses.")
366 366
367 367 enable_mathjax = Bool(True, config=True,
368 368 help="""Whether to enable MathJax for typesetting math/TeX
369 369
370 370 MathJax is the javascript library IPython uses to render math/LaTeX. It is
371 371 very large, so you may want to disable it if you have a slow internet
372 372 connection, or for offline use of the notebook.
373 373
374 374 When disabled, equations etc. will appear as their untransformed TeX source.
375 375 """
376 376 )
377 377 def _enable_mathjax_changed(self, name, old, new):
378 378 """set mathjax url to empty if mathjax is disabled"""
379 379 if not new:
380 380 self.mathjax_url = u''
381 381
382 382 base_project_url = Unicode('/', config=True,
383 383 help='''The base URL for the notebook server.
384 384
385 385 Leading and trailing slashes can be omitted,
386 386 and will automatically be added.
387 387 ''')
388 388 def _base_project_url_changed(self, name, old, new):
389 389 if not new.startswith('/'):
390 390 self.base_project_url = '/'+new
391 391 elif not new.endswith('/'):
392 392 self.base_project_url = new+'/'
393 393
394 394 base_kernel_url = Unicode('/', config=True,
395 395 help='''The base URL for the kernel server
396 396
397 397 Leading and trailing slashes can be omitted,
398 398 and will automatically be added.
399 399 ''')
400 400 def _base_kernel_url_changed(self, name, old, new):
401 401 if not new.startswith('/'):
402 402 self.base_kernel_url = '/'+new
403 403 elif not new.endswith('/'):
404 404 self.base_kernel_url = new+'/'
405 405
406 406 websocket_host = Unicode("", config=True,
407 407 help="""The hostname for the websocket server."""
408 408 )
409 409
410 410 extra_static_paths = List(Unicode, config=True,
411 411 help="""Extra paths to search for serving static files.
412 412
413 413 This allows adding javascript/css to be available from the notebook server machine,
414 414 or overriding individual files in the IPython"""
415 415 )
416 416 def _extra_static_paths_default(self):
417 417 return [os.path.join(self.profile_dir.location, 'static')]
418 418
419 419 @property
420 420 def static_file_path(self):
421 421 """return extra paths + the default location"""
422 422 return self.extra_static_paths + [DEFAULT_STATIC_FILES_PATH]
423 423
424 424 mathjax_url = Unicode("", config=True,
425 425 help="""The url for MathJax.js."""
426 426 )
427 427 def _mathjax_url_default(self):
428 428 if not self.enable_mathjax:
429 429 return u''
430 430 static_url_prefix = self.webapp_settings.get("static_url_prefix",
431 431 "/static/")
432 432 try:
433 433 mathjax = filefind(os.path.join('mathjax', 'MathJax.js'), self.static_file_path)
434 434 except IOError:
435 435 if self.certfile:
436 436 # HTTPS: load from Rackspace CDN, because SSL certificate requires it
437 437 base = u"https://c328740.ssl.cf1.rackcdn.com"
438 438 else:
439 439 base = u"http://cdn.mathjax.org"
440 440
441 441 url = base + u"/mathjax/latest/MathJax.js"
442 442 self.log.info("Using MathJax from CDN: %s", url)
443 443 return url
444 444 else:
445 445 self.log.info("Using local MathJax from %s" % mathjax)
446 446 return static_url_prefix+u"mathjax/MathJax.js"
447 447
448 448 def _mathjax_url_changed(self, name, old, new):
449 449 if new and not self.enable_mathjax:
450 450 # enable_mathjax=False overrides mathjax_url
451 451 self.mathjax_url = u''
452 452 else:
453 453 self.log.info("Using MathJax: %s", new)
454 454
455 455 notebook_manager_class = DottedObjectName('IPython.frontend.html.notebook.filenbmanager.FileNotebookManager',
456 456 config=True,
457 457 help='The notebook manager class to use.')
458 458
459 trust_xheaders = Bool(False, config=True,
460 help=("Whether to trust or not X-Scheme/X-Forwarded-Proto and X-Real-Ip/X-Forwarded-For headers"
461 "sent by the upstream reverse proxy. Neccesary if the proxy handles SSL")
462 )
463
459 464 def parse_command_line(self, argv=None):
460 465 super(NotebookApp, self).parse_command_line(argv)
461 466 if argv is None:
462 467 argv = sys.argv[1:]
463 468
464 469 # Scrub frontend-specific flags
465 470 self.kernel_argv = swallow_argv(argv, notebook_aliases, notebook_flags)
466 471 # Kernel should inherit default config file from frontend
467 472 self.kernel_argv.append("--IPKernelApp.parent_appname='%s'" % self.name)
468 473
469 474 if self.extra_args:
470 475 f = os.path.abspath(self.extra_args[0])
471 476 if os.path.isdir(f):
472 477 nbdir = f
473 478 else:
474 479 self.file_to_run = f
475 480 nbdir = os.path.dirname(f)
476 481 self.config.NotebookManager.notebook_dir = nbdir
477 482
478 483 def init_configurables(self):
479 484 # force Session default to be secure
480 485 default_secure(self.config)
481 486 self.kernel_manager = MappingKernelManager(
482 487 config=self.config, log=self.log, kernel_argv=self.kernel_argv,
483 488 connection_dir = self.profile_dir.security_dir,
484 489 )
485 490 kls = import_item(self.notebook_manager_class)
486 491 self.notebook_manager = kls(config=self.config, log=self.log)
487 492 self.notebook_manager.load_notebook_names()
488 493 self.cluster_manager = ClusterManager(config=self.config, log=self.log)
489 494 self.cluster_manager.update_profiles()
490 495
491 496 def init_logging(self):
492 497 # This prevents double log messages because tornado use a root logger that
493 498 # self.log is a child of. The logging module dipatches log messages to a log
494 499 # and all of its ancenstors until propagate is set to False.
495 500 self.log.propagate = False
496 501
497 502 def init_webapp(self):
498 503 """initialize tornado webapp and httpserver"""
499 504 self.web_app = NotebookWebApplication(
500 505 self, self.kernel_manager, self.notebook_manager,
501 506 self.cluster_manager, self.log,
502 507 self.base_project_url, self.webapp_settings
503 508 )
504 509 if self.certfile:
505 510 ssl_options = dict(certfile=self.certfile)
506 511 if self.keyfile:
507 512 ssl_options['keyfile'] = self.keyfile
508 513 else:
509 514 ssl_options = None
510 515 self.web_app.password = self.password
511 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options)
516 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options,
517 xheaders=self.trust_xheaders)
512 518 if not self.ip:
513 519 warning = "WARNING: The notebook server is listening on all IP addresses"
514 520 if ssl_options is None:
515 521 self.log.critical(warning + " and not using encryption. This"
516 522 "is not recommended.")
517 523 if not self.password and not self.read_only:
518 524 self.log.critical(warning + "and not using authentication."
519 525 "This is highly insecure and not recommended.")
520 526 success = None
521 527 for port in random_ports(self.port, self.port_retries+1):
522 528 try:
523 529 self.http_server.listen(port, self.ip)
524 530 except socket.error as e:
525 531 if e.errno != errno.EADDRINUSE:
526 532 raise
527 533 self.log.info('The port %i is already in use, trying another random port.' % port)
528 534 else:
529 535 self.port = port
530 536 success = True
531 537 break
532 538 if not success:
533 539 self.log.critical('ERROR: the notebook server could not be started because '
534 540 'no available port could be found.')
535 541 self.exit(1)
536 542
537 543 def init_signal(self):
538 544 # FIXME: remove this check when pyzmq dependency is >= 2.1.11
539 545 # safely extract zmq version info:
540 546 try:
541 547 zmq_v = zmq.pyzmq_version_info()
542 548 except AttributeError:
543 549 zmq_v = [ int(n) for n in re.findall(r'\d+', zmq.__version__) ]
544 550 if 'dev' in zmq.__version__:
545 551 zmq_v.append(999)
546 552 zmq_v = tuple(zmq_v)
547 553 if zmq_v >= (2,1,9) and not sys.platform.startswith('win'):
548 554 # This won't work with 2.1.7 and
549 555 # 2.1.9-10 will log ugly 'Interrupted system call' messages,
550 556 # but it will work
551 557 signal.signal(signal.SIGINT, self._handle_sigint)
552 558 signal.signal(signal.SIGTERM, self._signal_stop)
553 559 if hasattr(signal, 'SIGUSR1'):
554 560 # Windows doesn't support SIGUSR1
555 561 signal.signal(signal.SIGUSR1, self._signal_info)
556 562 if hasattr(signal, 'SIGINFO'):
557 563 # only on BSD-based systems
558 564 signal.signal(signal.SIGINFO, self._signal_info)
559 565
560 566 def _handle_sigint(self, sig, frame):
561 567 """SIGINT handler spawns confirmation dialog"""
562 568 # register more forceful signal handler for ^C^C case
563 569 signal.signal(signal.SIGINT, self._signal_stop)
564 570 # request confirmation dialog in bg thread, to avoid
565 571 # blocking the App
566 572 thread = threading.Thread(target=self._confirm_exit)
567 573 thread.daemon = True
568 574 thread.start()
569 575
570 576 def _restore_sigint_handler(self):
571 577 """callback for restoring original SIGINT handler"""
572 578 signal.signal(signal.SIGINT, self._handle_sigint)
573 579
574 580 def _confirm_exit(self):
575 581 """confirm shutdown on ^C
576 582
577 583 A second ^C, or answering 'y' within 5s will cause shutdown,
578 584 otherwise original SIGINT handler will be restored.
579 585
580 586 This doesn't work on Windows.
581 587 """
582 588 # FIXME: remove this delay when pyzmq dependency is >= 2.1.11
583 589 time.sleep(0.1)
584 590 info = self.log.info
585 591 info('interrupted')
586 592 print self.notebook_info()
587 593 sys.stdout.write("Shutdown this notebook server (y/[n])? ")
588 594 sys.stdout.flush()
589 595 r,w,x = select.select([sys.stdin], [], [], 5)
590 596 if r:
591 597 line = sys.stdin.readline()
592 598 if line.lower().startswith('y'):
593 599 self.log.critical("Shutdown confirmed")
594 600 ioloop.IOLoop.instance().stop()
595 601 return
596 602 else:
597 603 print "No answer for 5s:",
598 604 print "resuming operation..."
599 605 # no answer, or answer is no:
600 606 # set it back to original SIGINT handler
601 607 # use IOLoop.add_callback because signal.signal must be called
602 608 # from main thread
603 609 ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
604 610
605 611 def _signal_stop(self, sig, frame):
606 612 self.log.critical("received signal %s, stopping", sig)
607 613 ioloop.IOLoop.instance().stop()
608 614
609 615 def _signal_info(self, sig, frame):
610 616 print self.notebook_info()
611 617
612 618 @catch_config_error
613 619 def initialize(self, argv=None):
614 620 self.init_logging()
615 621 super(NotebookApp, self).initialize(argv)
616 622 self.init_configurables()
617 623 self.init_webapp()
618 624 self.init_signal()
619 625
620 626 def cleanup_kernels(self):
621 627 """Shutdown all kernels.
622 628
623 629 The kernels will shutdown themselves when this process no longer exists,
624 630 but explicit shutdown allows the KernelManagers to cleanup the connection files.
625 631 """
626 632 self.log.info('Shutting down kernels')
627 633 self.kernel_manager.shutdown_all()
628 634
629 635 def notebook_info(self):
630 636 "Return the current working directory and the server url information"
631 637 mgr_info = self.notebook_manager.info_string() + "\n"
632 638 return mgr_info +"The IPython Notebook is running at: %s" % self._url
633 639
634 640 def start(self):
635 641 """ Start the IPython Notebok server app, after initialization
636 642
637 643 This method takes no arguments so all configuration and initialization
638 644 must be done prior to calling this method."""
639 645 ip = self.ip if self.ip else '[all ip addresses on your system]'
640 646 proto = 'https' if self.certfile else 'http'
641 647 info = self.log.info
642 648 self._url = "%s://%s:%i%s" % (proto, ip, self.port,
643 649 self.base_project_url)
644 650 for line in self.notebook_info().split("\n"):
645 651 info(line)
646 652 info("Use Control-C to stop this server and shut down all kernels.")
647 653
648 654 if self.open_browser or self.file_to_run:
649 655 ip = self.ip or LOCALHOST
650 656 try:
651 657 browser = webbrowser.get(self.browser or None)
652 658 except webbrowser.Error as e:
653 659 self.log.warn('No web browser found: %s.' % e)
654 660 browser = None
655 661
656 662 if self.file_to_run:
657 663 name, _ = os.path.splitext(os.path.basename(self.file_to_run))
658 664 url = self.notebook_manager.rev_mapping.get(name, '')
659 665 else:
660 666 url = ''
661 667 if browser:
662 668 b = lambda : browser.open("%s://%s:%i%s%s" % (proto, ip,
663 669 self.port, self.base_project_url, url), new=2)
664 670 threading.Thread(target=b).start()
665 671 try:
666 672 ioloop.IOLoop.instance().start()
667 673 except KeyboardInterrupt:
668 674 info("Interrupted...")
669 675 finally:
670 676 self.cleanup_kernels()
671 677
672 678
673 679 #-----------------------------------------------------------------------------
674 680 # Main entry point
675 681 #-----------------------------------------------------------------------------
676 682
677 683 def launch_new_instance():
678 684 app = NotebookApp.instance()
679 685 app.initialize()
680 686 app.start()
681 687
General Comments 0
You need to be logged in to leave comments. Login now