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