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