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