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