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