##// END OF EJS Templates
NotebookApp: Make the number of ports to retry user configurable....
Bradley M. Froehle -
Show More
@@ -1,565 +1,584 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) 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 # 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 re
24 import re
24 import select
25 import select
25 import signal
26 import signal
26 import socket
27 import socket
27 import sys
28 import sys
28 import threading
29 import threading
29 import time
30 import time
30 import webbrowser
31 import webbrowser
31
32
32 # Third party
33 # Third party
33 import zmq
34 import zmq
34
35
35 # Install the pyzmq ioloop. This has to be done before anything else from
36 # Install the pyzmq ioloop. This has to be done before anything else from
36 # tornado is imported.
37 # tornado is imported.
37 from zmq.eventloop import ioloop
38 from zmq.eventloop import ioloop
38 ioloop.install()
39 ioloop.install()
39
40
40 from tornado import httpserver
41 from tornado import httpserver
41 from tornado import web
42 from tornado import web
42
43
43 # Our own libraries
44 # Our own libraries
44 from .kernelmanager import MappingKernelManager
45 from .kernelmanager import MappingKernelManager
45 from .handlers import (LoginHandler, LogoutHandler,
46 from .handlers import (LoginHandler, LogoutHandler,
46 ProjectDashboardHandler, NewHandler, NamedNotebookHandler,
47 ProjectDashboardHandler, NewHandler, NamedNotebookHandler,
47 MainKernelHandler, KernelHandler, KernelActionHandler, IOPubHandler,
48 MainKernelHandler, KernelHandler, KernelActionHandler, IOPubHandler,
48 ShellHandler, NotebookRootHandler, NotebookHandler, NotebookCopyHandler,
49 ShellHandler, NotebookRootHandler, NotebookHandler, NotebookCopyHandler,
49 RSTHandler, AuthenticatedFileHandler, PrintNotebookHandler,
50 RSTHandler, AuthenticatedFileHandler, PrintNotebookHandler,
50 MainClusterHandler, ClusterProfileHandler, ClusterActionHandler
51 MainClusterHandler, ClusterProfileHandler, ClusterActionHandler
51 )
52 )
52 from .notebookmanager import NotebookManager
53 from .notebookmanager import NotebookManager
53 from .clustermanager import ClusterManager
54 from .clustermanager import ClusterManager
54
55
55 from IPython.config.application import catch_config_error, boolean_flag
56 from IPython.config.application import catch_config_error, boolean_flag
56 from IPython.core.application import BaseIPythonApplication
57 from IPython.core.application import BaseIPythonApplication
57 from IPython.core.profiledir import ProfileDir
58 from IPython.core.profiledir import ProfileDir
58 from IPython.lib.kernel import swallow_argv
59 from IPython.lib.kernel import swallow_argv
59 from IPython.zmq.session import Session, default_secure
60 from IPython.zmq.session import Session, default_secure
60 from IPython.zmq.zmqshell import ZMQInteractiveShell
61 from IPython.zmq.zmqshell import ZMQInteractiveShell
61 from IPython.zmq.ipkernel import (
62 from IPython.zmq.ipkernel import (
62 flags as ipkernel_flags,
63 flags as ipkernel_flags,
63 aliases as ipkernel_aliases,
64 aliases as ipkernel_aliases,
64 IPKernelApp
65 IPKernelApp
65 )
66 )
66 from IPython.utils.traitlets import Dict, Unicode, Integer, List, Enum, Bool
67 from IPython.utils.traitlets import Dict, Unicode, Integer, List, Enum, Bool
67 from IPython.utils import py3compat
68 from IPython.utils import py3compat
68
69
69 #-----------------------------------------------------------------------------
70 #-----------------------------------------------------------------------------
70 # Module globals
71 # Module globals
71 #-----------------------------------------------------------------------------
72 #-----------------------------------------------------------------------------
72
73
73 _kernel_id_regex = r"(?P<kernel_id>\w+-\w+-\w+-\w+-\w+)"
74 _kernel_id_regex = r"(?P<kernel_id>\w+-\w+-\w+-\w+-\w+)"
74 _kernel_action_regex = r"(?P<action>restart|interrupt)"
75 _kernel_action_regex = r"(?P<action>restart|interrupt)"
75 _notebook_id_regex = r"(?P<notebook_id>\w+-\w+-\w+-\w+-\w+)"
76 _notebook_id_regex = r"(?P<notebook_id>\w+-\w+-\w+-\w+-\w+)"
76 _profile_regex = r"(?P<profile>[a-zA-Z0-9]+)"
77 _profile_regex = r"(?P<profile>[a-zA-Z0-9]+)"
77 _cluster_action_regex = r"(?P<action>start|stop)"
78 _cluster_action_regex = r"(?P<action>start|stop)"
78
79
79
80
80 LOCALHOST = '127.0.0.1'
81 LOCALHOST = '127.0.0.1'
81
82
82 _examples = """
83 _examples = """
83 ipython notebook # start the notebook
84 ipython notebook # start the notebook
84 ipython notebook --profile=sympy # use the sympy profile
85 ipython notebook --profile=sympy # use the sympy profile
85 ipython notebook --pylab=inline # pylab in inline plotting mode
86 ipython notebook --pylab=inline # pylab in inline plotting mode
86 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
87 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
87 ipython notebook --port=5555 --ip=* # Listen on port 5555, all interfaces
88 ipython notebook --port=5555 --ip=* # Listen on port 5555, all interfaces
88 """
89 """
89
90
90 #-----------------------------------------------------------------------------
91 #-----------------------------------------------------------------------------
91 # Helper functions
92 # Helper functions
92 #-----------------------------------------------------------------------------
93 #-----------------------------------------------------------------------------
93
94
94 def url_path_join(a,b):
95 def url_path_join(a,b):
95 if a.endswith('/') and b.startswith('/'):
96 if a.endswith('/') and b.startswith('/'):
96 return a[:-1]+b
97 return a[:-1]+b
97 else:
98 else:
98 return a+b
99 return a+b
99
100
101 def random_ports(port, n):
102 """Generate a list of n random ports near the given port.
103
104 The first 5 ports will be sequential, and the remaining n-5 will be
105 randomly selected in the range [port-2*n, port+2*n].
106 """
107 for i in range(min(5, n)):
108 yield port + i
109 for i in range(n-5):
110 yield port + random.randint(-2*n, 2*n)
111
100 #-----------------------------------------------------------------------------
112 #-----------------------------------------------------------------------------
101 # The Tornado web application
113 # The Tornado web application
102 #-----------------------------------------------------------------------------
114 #-----------------------------------------------------------------------------
103
115
104 class NotebookWebApplication(web.Application):
116 class NotebookWebApplication(web.Application):
105
117
106 def __init__(self, ipython_app, kernel_manager, notebook_manager,
118 def __init__(self, ipython_app, kernel_manager, notebook_manager,
107 cluster_manager, log,
119 cluster_manager, log,
108 base_project_url, settings_overrides):
120 base_project_url, settings_overrides):
109 handlers = [
121 handlers = [
110 (r"/", ProjectDashboardHandler),
122 (r"/", ProjectDashboardHandler),
111 (r"/login", LoginHandler),
123 (r"/login", LoginHandler),
112 (r"/logout", LogoutHandler),
124 (r"/logout", LogoutHandler),
113 (r"/new", NewHandler),
125 (r"/new", NewHandler),
114 (r"/%s" % _notebook_id_regex, NamedNotebookHandler),
126 (r"/%s" % _notebook_id_regex, NamedNotebookHandler),
115 (r"/%s/copy" % _notebook_id_regex, NotebookCopyHandler),
127 (r"/%s/copy" % _notebook_id_regex, NotebookCopyHandler),
116 (r"/%s/print" % _notebook_id_regex, PrintNotebookHandler),
128 (r"/%s/print" % _notebook_id_regex, PrintNotebookHandler),
117 (r"/kernels", MainKernelHandler),
129 (r"/kernels", MainKernelHandler),
118 (r"/kernels/%s" % _kernel_id_regex, KernelHandler),
130 (r"/kernels/%s" % _kernel_id_regex, KernelHandler),
119 (r"/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
131 (r"/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
120 (r"/kernels/%s/iopub" % _kernel_id_regex, IOPubHandler),
132 (r"/kernels/%s/iopub" % _kernel_id_regex, IOPubHandler),
121 (r"/kernels/%s/shell" % _kernel_id_regex, ShellHandler),
133 (r"/kernels/%s/shell" % _kernel_id_regex, ShellHandler),
122 (r"/notebooks", NotebookRootHandler),
134 (r"/notebooks", NotebookRootHandler),
123 (r"/notebooks/%s" % _notebook_id_regex, NotebookHandler),
135 (r"/notebooks/%s" % _notebook_id_regex, NotebookHandler),
124 (r"/rstservice/render", RSTHandler),
136 (r"/rstservice/render", RSTHandler),
125 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : notebook_manager.notebook_dir}),
137 (r"/files/(.*)", AuthenticatedFileHandler, {'path' : notebook_manager.notebook_dir}),
126 (r"/clusters", MainClusterHandler),
138 (r"/clusters", MainClusterHandler),
127 (r"/clusters/%s/%s" % (_profile_regex, _cluster_action_regex), ClusterActionHandler),
139 (r"/clusters/%s/%s" % (_profile_regex, _cluster_action_regex), ClusterActionHandler),
128 (r"/clusters/%s" % _profile_regex, ClusterProfileHandler),
140 (r"/clusters/%s" % _profile_regex, ClusterProfileHandler),
129 ]
141 ]
130 settings = dict(
142 settings = dict(
131 template_path=os.path.join(os.path.dirname(__file__), "templates"),
143 template_path=os.path.join(os.path.dirname(__file__), "templates"),
132 static_path=os.path.join(os.path.dirname(__file__), "static"),
144 static_path=os.path.join(os.path.dirname(__file__), "static"),
133 cookie_secret=os.urandom(1024),
145 cookie_secret=os.urandom(1024),
134 login_url="/login",
146 login_url="/login",
135 )
147 )
136
148
137 # allow custom overrides for the tornado web app.
149 # allow custom overrides for the tornado web app.
138 settings.update(settings_overrides)
150 settings.update(settings_overrides)
139
151
140 # Python < 2.6.5 doesn't accept unicode keys in f(**kwargs), and
152 # Python < 2.6.5 doesn't accept unicode keys in f(**kwargs), and
141 # base_project_url will always be unicode, which will in turn
153 # base_project_url will always be unicode, which will in turn
142 # make the patterns unicode, and ultimately result in unicode
154 # make the patterns unicode, and ultimately result in unicode
143 # keys in kwargs to handler._execute(**kwargs) in tornado.
155 # keys in kwargs to handler._execute(**kwargs) in tornado.
144 # This enforces that base_project_url be ascii in that situation.
156 # This enforces that base_project_url be ascii in that situation.
145 #
157 #
146 # Note that the URLs these patterns check against are escaped,
158 # Note that the URLs these patterns check against are escaped,
147 # and thus guaranteed to be ASCII: 'hΓ©llo' is really 'h%C3%A9llo'.
159 # and thus guaranteed to be ASCII: 'hΓ©llo' is really 'h%C3%A9llo'.
148 base_project_url = py3compat.unicode_to_str(base_project_url, 'ascii')
160 base_project_url = py3compat.unicode_to_str(base_project_url, 'ascii')
149
161
150 # prepend base_project_url onto the patterns that we match
162 # prepend base_project_url onto the patterns that we match
151 new_handlers = []
163 new_handlers = []
152 for handler in handlers:
164 for handler in handlers:
153 pattern = url_path_join(base_project_url, handler[0])
165 pattern = url_path_join(base_project_url, handler[0])
154 new_handler = tuple([pattern]+list(handler[1:]))
166 new_handler = tuple([pattern]+list(handler[1:]))
155 new_handlers.append( new_handler )
167 new_handlers.append( new_handler )
156
168
157 super(NotebookWebApplication, self).__init__(new_handlers, **settings)
169 super(NotebookWebApplication, self).__init__(new_handlers, **settings)
158
170
159 self.kernel_manager = kernel_manager
171 self.kernel_manager = kernel_manager
160 self.notebook_manager = notebook_manager
172 self.notebook_manager = notebook_manager
161 self.cluster_manager = cluster_manager
173 self.cluster_manager = cluster_manager
162 self.ipython_app = ipython_app
174 self.ipython_app = ipython_app
163 self.read_only = self.ipython_app.read_only
175 self.read_only = self.ipython_app.read_only
164 self.log = log
176 self.log = log
165
177
166
178
167 #-----------------------------------------------------------------------------
179 #-----------------------------------------------------------------------------
168 # Aliases and Flags
180 # Aliases and Flags
169 #-----------------------------------------------------------------------------
181 #-----------------------------------------------------------------------------
170
182
171 flags = dict(ipkernel_flags)
183 flags = dict(ipkernel_flags)
172 flags['no-browser']=(
184 flags['no-browser']=(
173 {'NotebookApp' : {'open_browser' : False}},
185 {'NotebookApp' : {'open_browser' : False}},
174 "Don't open the notebook in a browser after startup."
186 "Don't open the notebook in a browser after startup."
175 )
187 )
176 flags['no-mathjax']=(
188 flags['no-mathjax']=(
177 {'NotebookApp' : {'enable_mathjax' : False}},
189 {'NotebookApp' : {'enable_mathjax' : False}},
178 """Disable MathJax
190 """Disable MathJax
179
191
180 MathJax is the javascript library IPython uses to render math/LaTeX. It is
192 MathJax is the javascript library IPython uses to render math/LaTeX. It is
181 very large, so you may want to disable it if you have a slow internet
193 very large, so you may want to disable it if you have a slow internet
182 connection, or for offline use of the notebook.
194 connection, or for offline use of the notebook.
183
195
184 When disabled, equations etc. will appear as their untransformed TeX source.
196 When disabled, equations etc. will appear as their untransformed TeX source.
185 """
197 """
186 )
198 )
187 flags['read-only'] = (
199 flags['read-only'] = (
188 {'NotebookApp' : {'read_only' : True}},
200 {'NotebookApp' : {'read_only' : True}},
189 """Allow read-only access to notebooks.
201 """Allow read-only access to notebooks.
190
202
191 When using a password to protect the notebook server, this flag
203 When using a password to protect the notebook server, this flag
192 allows unauthenticated clients to view the notebook list, and
204 allows unauthenticated clients to view the notebook list, and
193 individual notebooks, but not edit them, start kernels, or run
205 individual notebooks, but not edit them, start kernels, or run
194 code.
206 code.
195
207
196 If no password is set, the server will be entirely read-only.
208 If no password is set, the server will be entirely read-only.
197 """
209 """
198 )
210 )
199
211
200 # Add notebook manager flags
212 # Add notebook manager flags
201 flags.update(boolean_flag('script', 'NotebookManager.save_script',
213 flags.update(boolean_flag('script', 'NotebookManager.save_script',
202 'Auto-save a .py script everytime the .ipynb notebook is saved',
214 'Auto-save a .py script everytime the .ipynb notebook is saved',
203 'Do not auto-save .py scripts for every notebook'))
215 'Do not auto-save .py scripts for every notebook'))
204
216
205 # the flags that are specific to the frontend
217 # the flags that are specific to the frontend
206 # these must be scrubbed before being passed to the kernel,
218 # these must be scrubbed before being passed to the kernel,
207 # or it will raise an error on unrecognized flags
219 # or it will raise an error on unrecognized flags
208 notebook_flags = ['no-browser', 'no-mathjax', 'read-only', 'script', 'no-script']
220 notebook_flags = ['no-browser', 'no-mathjax', 'read-only', 'script', 'no-script']
209
221
210 aliases = dict(ipkernel_aliases)
222 aliases = dict(ipkernel_aliases)
211
223
212 aliases.update({
224 aliases.update({
213 'ip': 'NotebookApp.ip',
225 'ip': 'NotebookApp.ip',
214 'port': 'NotebookApp.port',
226 'port': 'NotebookApp.port',
227 'port-retries': 'NotebookApp.port_retries',
215 'keyfile': 'NotebookApp.keyfile',
228 'keyfile': 'NotebookApp.keyfile',
216 'certfile': 'NotebookApp.certfile',
229 'certfile': 'NotebookApp.certfile',
217 'notebook-dir': 'NotebookManager.notebook_dir',
230 'notebook-dir': 'NotebookManager.notebook_dir',
218 'browser': 'NotebookApp.browser',
231 'browser': 'NotebookApp.browser',
219 })
232 })
220
233
221 # remove ipkernel flags that are singletons, and don't make sense in
234 # remove ipkernel flags that are singletons, and don't make sense in
222 # multi-kernel evironment:
235 # multi-kernel evironment:
223 aliases.pop('f', None)
236 aliases.pop('f', None)
224
237
225 notebook_aliases = [u'port', u'ip', u'keyfile', u'certfile',
238 notebook_aliases = [u'port', u'port-retries', u'ip', u'keyfile', u'certfile',
226 u'notebook-dir']
239 u'notebook-dir']
227
240
228 #-----------------------------------------------------------------------------
241 #-----------------------------------------------------------------------------
229 # NotebookApp
242 # NotebookApp
230 #-----------------------------------------------------------------------------
243 #-----------------------------------------------------------------------------
231
244
232 class NotebookApp(BaseIPythonApplication):
245 class NotebookApp(BaseIPythonApplication):
233
246
234 name = 'ipython-notebook'
247 name = 'ipython-notebook'
235 default_config_file_name='ipython_notebook_config.py'
248 default_config_file_name='ipython_notebook_config.py'
236
249
237 description = """
250 description = """
238 The IPython HTML Notebook.
251 The IPython HTML Notebook.
239
252
240 This launches a Tornado based HTML Notebook Server that serves up an
253 This launches a Tornado based HTML Notebook Server that serves up an
241 HTML5/Javascript Notebook client.
254 HTML5/Javascript Notebook client.
242 """
255 """
243 examples = _examples
256 examples = _examples
244
257
245 classes = [IPKernelApp, ZMQInteractiveShell, ProfileDir, Session,
258 classes = [IPKernelApp, ZMQInteractiveShell, ProfileDir, Session,
246 MappingKernelManager, NotebookManager]
259 MappingKernelManager, NotebookManager]
247 flags = Dict(flags)
260 flags = Dict(flags)
248 aliases = Dict(aliases)
261 aliases = Dict(aliases)
249
262
250 kernel_argv = List(Unicode)
263 kernel_argv = List(Unicode)
251
264
252 log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
265 log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
253 default_value=logging.INFO,
266 default_value=logging.INFO,
254 config=True,
267 config=True,
255 help="Set the log level by value or name.")
268 help="Set the log level by value or name.")
256
269
257 # create requested profiles by default, if they don't exist:
270 # create requested profiles by default, if they don't exist:
258 auto_create = Bool(True)
271 auto_create = Bool(True)
259
272
260 # file to be opened in the notebook server
273 # file to be opened in the notebook server
261 file_to_run = Unicode('')
274 file_to_run = Unicode('')
262
275
263 # Network related information.
276 # Network related information.
264
277
265 ip = Unicode(LOCALHOST, config=True,
278 ip = Unicode(LOCALHOST, config=True,
266 help="The IP address the notebook server will listen on."
279 help="The IP address the notebook server will listen on."
267 )
280 )
268
281
269 def _ip_changed(self, name, old, new):
282 def _ip_changed(self, name, old, new):
270 if new == u'*': self.ip = u''
283 if new == u'*': self.ip = u''
271
284
272 port = Integer(8888, config=True,
285 port = Integer(8888, config=True,
273 help="The port the notebook server will listen on."
286 help="The port the notebook server will listen on."
274 )
287 )
288 port_retries = Integer(50, config=True,
289 help="The number of additional ports to try if the specified port is not available."
290 )
275
291
276 certfile = Unicode(u'', config=True,
292 certfile = Unicode(u'', config=True,
277 help="""The full path to an SSL/TLS certificate file."""
293 help="""The full path to an SSL/TLS certificate file."""
278 )
294 )
279
295
280 keyfile = Unicode(u'', config=True,
296 keyfile = Unicode(u'', config=True,
281 help="""The full path to a private key file for usage with SSL/TLS."""
297 help="""The full path to a private key file for usage with SSL/TLS."""
282 )
298 )
283
299
284 password = Unicode(u'', config=True,
300 password = Unicode(u'', config=True,
285 help="""Hashed password to use for web authentication.
301 help="""Hashed password to use for web authentication.
286
302
287 To generate, type in a python/IPython shell:
303 To generate, type in a python/IPython shell:
288
304
289 from IPython.lib import passwd; passwd()
305 from IPython.lib import passwd; passwd()
290
306
291 The string should be of the form type:salt:hashed-password.
307 The string should be of the form type:salt:hashed-password.
292 """
308 """
293 )
309 )
294
310
295 open_browser = Bool(True, config=True,
311 open_browser = Bool(True, config=True,
296 help="""Whether to open in a browser after starting.
312 help="""Whether to open in a browser after starting.
297 The specific browser used is platform dependent and
313 The specific browser used is platform dependent and
298 determined by the python standard library `webbrowser`
314 determined by the python standard library `webbrowser`
299 module, unless it is overridden using the --browser
315 module, unless it is overridden using the --browser
300 (NotebookApp.browser) configuration option.
316 (NotebookApp.browser) configuration option.
301 """)
317 """)
302
318
303 browser = Unicode(u'', config=True,
319 browser = Unicode(u'', config=True,
304 help="""Specify what command to use to invoke a web
320 help="""Specify what command to use to invoke a web
305 browser when opening the notebook. If not specified, the
321 browser when opening the notebook. If not specified, the
306 default browser will be determined by the `webbrowser`
322 default browser will be determined by the `webbrowser`
307 standard library module, which allows setting of the
323 standard library module, which allows setting of the
308 BROWSER environment variable to override it.
324 BROWSER environment variable to override it.
309 """)
325 """)
310
326
311 read_only = Bool(False, config=True,
327 read_only = Bool(False, config=True,
312 help="Whether to prevent editing/execution of notebooks."
328 help="Whether to prevent editing/execution of notebooks."
313 )
329 )
314
330
315 webapp_settings = Dict(config=True,
331 webapp_settings = Dict(config=True,
316 help="Supply overrides for the tornado.web.Application that the "
332 help="Supply overrides for the tornado.web.Application that the "
317 "IPython notebook uses.")
333 "IPython notebook uses.")
318
334
319 enable_mathjax = Bool(True, config=True,
335 enable_mathjax = Bool(True, config=True,
320 help="""Whether to enable MathJax for typesetting math/TeX
336 help="""Whether to enable MathJax for typesetting math/TeX
321
337
322 MathJax is the javascript library IPython uses to render math/LaTeX. It is
338 MathJax is the javascript library IPython uses to render math/LaTeX. It is
323 very large, so you may want to disable it if you have a slow internet
339 very large, so you may want to disable it if you have a slow internet
324 connection, or for offline use of the notebook.
340 connection, or for offline use of the notebook.
325
341
326 When disabled, equations etc. will appear as their untransformed TeX source.
342 When disabled, equations etc. will appear as their untransformed TeX source.
327 """
343 """
328 )
344 )
329 def _enable_mathjax_changed(self, name, old, new):
345 def _enable_mathjax_changed(self, name, old, new):
330 """set mathjax url to empty if mathjax is disabled"""
346 """set mathjax url to empty if mathjax is disabled"""
331 if not new:
347 if not new:
332 self.mathjax_url = u''
348 self.mathjax_url = u''
333
349
334 base_project_url = Unicode('/', config=True,
350 base_project_url = Unicode('/', config=True,
335 help='''The base URL for the notebook server''')
351 help='''The base URL for the notebook server''')
336 base_kernel_url = Unicode('/', config=True,
352 base_kernel_url = Unicode('/', config=True,
337 help='''The base URL for the kernel server''')
353 help='''The base URL for the kernel server''')
338 websocket_host = Unicode("", config=True,
354 websocket_host = Unicode("", config=True,
339 help="""The hostname for the websocket server."""
355 help="""The hostname for the websocket server."""
340 )
356 )
341
357
342 mathjax_url = Unicode("", config=True,
358 mathjax_url = Unicode("", config=True,
343 help="""The url for MathJax.js."""
359 help="""The url for MathJax.js."""
344 )
360 )
345 def _mathjax_url_default(self):
361 def _mathjax_url_default(self):
346 if not self.enable_mathjax:
362 if not self.enable_mathjax:
347 return u''
363 return u''
348 static_path = self.webapp_settings.get("static_path", os.path.join(os.path.dirname(__file__), "static"))
364 static_path = self.webapp_settings.get("static_path", os.path.join(os.path.dirname(__file__), "static"))
349 static_url_prefix = self.webapp_settings.get("static_url_prefix",
365 static_url_prefix = self.webapp_settings.get("static_url_prefix",
350 "/static/")
366 "/static/")
351 if os.path.exists(os.path.join(static_path, 'mathjax', "MathJax.js")):
367 if os.path.exists(os.path.join(static_path, 'mathjax', "MathJax.js")):
352 self.log.info("Using local MathJax")
368 self.log.info("Using local MathJax")
353 return static_url_prefix+u"mathjax/MathJax.js"
369 return static_url_prefix+u"mathjax/MathJax.js"
354 else:
370 else:
355 if self.certfile:
371 if self.certfile:
356 # HTTPS: load from Rackspace CDN, because SSL certificate requires it
372 # HTTPS: load from Rackspace CDN, because SSL certificate requires it
357 base = u"https://c328740.ssl.cf1.rackcdn.com"
373 base = u"https://c328740.ssl.cf1.rackcdn.com"
358 else:
374 else:
359 base = u"http://cdn.mathjax.org"
375 base = u"http://cdn.mathjax.org"
360
376
361 url = base + u"/mathjax/latest/MathJax.js"
377 url = base + u"/mathjax/latest/MathJax.js"
362 self.log.info("Using MathJax from CDN: %s", url)
378 self.log.info("Using MathJax from CDN: %s", url)
363 return url
379 return url
364
380
365 def _mathjax_url_changed(self, name, old, new):
381 def _mathjax_url_changed(self, name, old, new):
366 if new and not self.enable_mathjax:
382 if new and not self.enable_mathjax:
367 # enable_mathjax=False overrides mathjax_url
383 # enable_mathjax=False overrides mathjax_url
368 self.mathjax_url = u''
384 self.mathjax_url = u''
369 else:
385 else:
370 self.log.info("Using MathJax: %s", new)
386 self.log.info("Using MathJax: %s", new)
371
387
372 def parse_command_line(self, argv=None):
388 def parse_command_line(self, argv=None):
373 super(NotebookApp, self).parse_command_line(argv)
389 super(NotebookApp, self).parse_command_line(argv)
374 if argv is None:
390 if argv is None:
375 argv = sys.argv[1:]
391 argv = sys.argv[1:]
376
392
377 # Scrub frontend-specific flags
393 # Scrub frontend-specific flags
378 self.kernel_argv = swallow_argv(argv, notebook_aliases, notebook_flags)
394 self.kernel_argv = swallow_argv(argv, notebook_aliases, notebook_flags)
379 # Kernel should inherit default config file from frontend
395 # Kernel should inherit default config file from frontend
380 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
396 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
381
397
382 if self.extra_args:
398 if self.extra_args:
383 self.file_to_run = os.path.abspath(self.extra_args[0])
399 self.file_to_run = os.path.abspath(self.extra_args[0])
384 self.config.NotebookManager.notebook_dir = os.path.dirname(self.file_to_run)
400 self.config.NotebookManager.notebook_dir = os.path.dirname(self.file_to_run)
385
401
386 def init_configurables(self):
402 def init_configurables(self):
387 # force Session default to be secure
403 # force Session default to be secure
388 default_secure(self.config)
404 default_secure(self.config)
389 # Create a KernelManager and start a kernel.
405 # Create a KernelManager and start a kernel.
390 self.kernel_manager = MappingKernelManager(
406 self.kernel_manager = MappingKernelManager(
391 config=self.config, log=self.log, kernel_argv=self.kernel_argv,
407 config=self.config, log=self.log, kernel_argv=self.kernel_argv,
392 connection_dir = self.profile_dir.security_dir,
408 connection_dir = self.profile_dir.security_dir,
393 )
409 )
394 self.notebook_manager = NotebookManager(config=self.config, log=self.log)
410 self.notebook_manager = NotebookManager(config=self.config, log=self.log)
395 self.notebook_manager.list_notebooks()
411 self.notebook_manager.list_notebooks()
396 self.cluster_manager = ClusterManager(config=self.config, log=self.log)
412 self.cluster_manager = ClusterManager(config=self.config, log=self.log)
397 self.cluster_manager.update_profiles()
413 self.cluster_manager.update_profiles()
398
414
399 def init_logging(self):
415 def init_logging(self):
400 # This prevents double log messages because tornado use a root logger that
416 # This prevents double log messages because tornado use a root logger that
401 # self.log is a child of. The logging module dipatches log messages to a log
417 # self.log is a child of. The logging module dipatches log messages to a log
402 # and all of its ancenstors until propagate is set to False.
418 # and all of its ancenstors until propagate is set to False.
403 self.log.propagate = False
419 self.log.propagate = False
404
420
405 def init_webapp(self):
421 def init_webapp(self):
406 """initialize tornado webapp and httpserver"""
422 """initialize tornado webapp and httpserver"""
407 self.web_app = NotebookWebApplication(
423 self.web_app = NotebookWebApplication(
408 self, self.kernel_manager, self.notebook_manager,
424 self, self.kernel_manager, self.notebook_manager,
409 self.cluster_manager, self.log,
425 self.cluster_manager, self.log,
410 self.base_project_url, self.webapp_settings
426 self.base_project_url, self.webapp_settings
411 )
427 )
412 if self.certfile:
428 if self.certfile:
413 ssl_options = dict(certfile=self.certfile)
429 ssl_options = dict(certfile=self.certfile)
414 if self.keyfile:
430 if self.keyfile:
415 ssl_options['keyfile'] = self.keyfile
431 ssl_options['keyfile'] = self.keyfile
416 else:
432 else:
417 ssl_options = None
433 ssl_options = None
418 self.web_app.password = self.password
434 self.web_app.password = self.password
419 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options)
435 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options)
420 if ssl_options is None and not self.ip and not (self.read_only and not self.password):
436 if ssl_options is None and not self.ip and not (self.read_only and not self.password):
421 self.log.critical('WARNING: the notebook server is listening on all IP addresses '
437 self.log.critical('WARNING: the notebook server is listening on all IP addresses '
422 'but not using any encryption or authentication. This is highly '
438 'but not using any encryption or authentication. This is highly '
423 'insecure and not recommended.')
439 'insecure and not recommended.')
424
440
425 # Try random ports centered around the default.
441 success = None
426 from random import randint
442 for port in random_ports(self.port, self.port_retries+1):
427 n = 50 # Max number of attempts, keep reasonably large.
428 for port in range(self.port, self.port+5) + [self.port + randint(-2*n, 2*n) for i in range(n-5)]:
429 try:
443 try:
430 self.http_server.listen(port, self.ip)
444 self.http_server.listen(port, self.ip)
431 except socket.error, e:
445 except socket.error, e:
432 if e.errno != errno.EADDRINUSE:
446 if e.errno != errno.EADDRINUSE:
433 raise
447 raise
434 self.log.info('The port %i is already in use, trying another random port.' % port)
448 self.log.info('The port %i is already in use, trying another random port.' % port)
435 else:
449 else:
436 self.port = port
450 self.port = port
451 success = True
437 break
452 break
453 if not success:
454 self.log.critical('ERROR: the notebook server could not be started because '
455 'no available port could be found.')
456 raise RuntimeError
438
457
439 def init_signal(self):
458 def init_signal(self):
440 # FIXME: remove this check when pyzmq dependency is >= 2.1.11
459 # FIXME: remove this check when pyzmq dependency is >= 2.1.11
441 # safely extract zmq version info:
460 # safely extract zmq version info:
442 try:
461 try:
443 zmq_v = zmq.pyzmq_version_info()
462 zmq_v = zmq.pyzmq_version_info()
444 except AttributeError:
463 except AttributeError:
445 zmq_v = [ int(n) for n in re.findall(r'\d+', zmq.__version__) ]
464 zmq_v = [ int(n) for n in re.findall(r'\d+', zmq.__version__) ]
446 if 'dev' in zmq.__version__:
465 if 'dev' in zmq.__version__:
447 zmq_v.append(999)
466 zmq_v.append(999)
448 zmq_v = tuple(zmq_v)
467 zmq_v = tuple(zmq_v)
449 if zmq_v >= (2,1,9):
468 if zmq_v >= (2,1,9):
450 # This won't work with 2.1.7 and
469 # This won't work with 2.1.7 and
451 # 2.1.9-10 will log ugly 'Interrupted system call' messages,
470 # 2.1.9-10 will log ugly 'Interrupted system call' messages,
452 # but it will work
471 # but it will work
453 signal.signal(signal.SIGINT, self._handle_sigint)
472 signal.signal(signal.SIGINT, self._handle_sigint)
454 signal.signal(signal.SIGTERM, self._signal_stop)
473 signal.signal(signal.SIGTERM, self._signal_stop)
455
474
456 def _handle_sigint(self, sig, frame):
475 def _handle_sigint(self, sig, frame):
457 """SIGINT handler spawns confirmation dialog"""
476 """SIGINT handler spawns confirmation dialog"""
458 # register more forceful signal handler for ^C^C case
477 # register more forceful signal handler for ^C^C case
459 signal.signal(signal.SIGINT, self._signal_stop)
478 signal.signal(signal.SIGINT, self._signal_stop)
460 # request confirmation dialog in bg thread, to avoid
479 # request confirmation dialog in bg thread, to avoid
461 # blocking the App
480 # blocking the App
462 thread = threading.Thread(target=self._confirm_exit)
481 thread = threading.Thread(target=self._confirm_exit)
463 thread.daemon = True
482 thread.daemon = True
464 thread.start()
483 thread.start()
465
484
466 def _restore_sigint_handler(self):
485 def _restore_sigint_handler(self):
467 """callback for restoring original SIGINT handler"""
486 """callback for restoring original SIGINT handler"""
468 signal.signal(signal.SIGINT, self._handle_sigint)
487 signal.signal(signal.SIGINT, self._handle_sigint)
469
488
470 def _confirm_exit(self):
489 def _confirm_exit(self):
471 """confirm shutdown on ^C
490 """confirm shutdown on ^C
472
491
473 A second ^C, or answering 'y' within 5s will cause shutdown,
492 A second ^C, or answering 'y' within 5s will cause shutdown,
474 otherwise original SIGINT handler will be restored.
493 otherwise original SIGINT handler will be restored.
475 """
494 """
476 # FIXME: remove this delay when pyzmq dependency is >= 2.1.11
495 # FIXME: remove this delay when pyzmq dependency is >= 2.1.11
477 time.sleep(0.1)
496 time.sleep(0.1)
478 sys.stdout.write("Shutdown Notebook Server (y/[n])? ")
497 sys.stdout.write("Shutdown Notebook Server (y/[n])? ")
479 sys.stdout.flush()
498 sys.stdout.flush()
480 r,w,x = select.select([sys.stdin], [], [], 5)
499 r,w,x = select.select([sys.stdin], [], [], 5)
481 if r:
500 if r:
482 line = sys.stdin.readline()
501 line = sys.stdin.readline()
483 if line.lower().startswith('y'):
502 if line.lower().startswith('y'):
484 self.log.critical("Shutdown confirmed")
503 self.log.critical("Shutdown confirmed")
485 ioloop.IOLoop.instance().stop()
504 ioloop.IOLoop.instance().stop()
486 return
505 return
487 else:
506 else:
488 print "No answer for 5s:",
507 print "No answer for 5s:",
489 print "resuming operation..."
508 print "resuming operation..."
490 # no answer, or answer is no:
509 # no answer, or answer is no:
491 # set it back to original SIGINT handler
510 # set it back to original SIGINT handler
492 # use IOLoop.add_callback because signal.signal must be called
511 # use IOLoop.add_callback because signal.signal must be called
493 # from main thread
512 # from main thread
494 ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
513 ioloop.IOLoop.instance().add_callback(self._restore_sigint_handler)
495
514
496 def _signal_stop(self, sig, frame):
515 def _signal_stop(self, sig, frame):
497 self.log.critical("received signal %s, stopping", sig)
516 self.log.critical("received signal %s, stopping", sig)
498 ioloop.IOLoop.instance().stop()
517 ioloop.IOLoop.instance().stop()
499
518
500 @catch_config_error
519 @catch_config_error
501 def initialize(self, argv=None):
520 def initialize(self, argv=None):
502 self.init_logging()
521 self.init_logging()
503 super(NotebookApp, self).initialize(argv)
522 super(NotebookApp, self).initialize(argv)
504 self.init_configurables()
523 self.init_configurables()
505 self.init_webapp()
524 self.init_webapp()
506 self.init_signal()
525 self.init_signal()
507
526
508 def cleanup_kernels(self):
527 def cleanup_kernels(self):
509 """shutdown all kernels
528 """shutdown all kernels
510
529
511 The kernels will shutdown themselves when this process no longer exists,
530 The kernels will shutdown themselves when this process no longer exists,
512 but explicit shutdown allows the KernelManagers to cleanup the connection files.
531 but explicit shutdown allows the KernelManagers to cleanup the connection files.
513 """
532 """
514 self.log.info('Shutting down kernels')
533 self.log.info('Shutting down kernels')
515 km = self.kernel_manager
534 km = self.kernel_manager
516 # copy list, since kill_kernel deletes keys
535 # copy list, since kill_kernel deletes keys
517 for kid in list(km.kernel_ids):
536 for kid in list(km.kernel_ids):
518 km.kill_kernel(kid)
537 km.kill_kernel(kid)
519
538
520 def start(self):
539 def start(self):
521 ip = self.ip if self.ip else '[all ip addresses on your system]'
540 ip = self.ip if self.ip else '[all ip addresses on your system]'
522 proto = 'https' if self.certfile else 'http'
541 proto = 'https' if self.certfile else 'http'
523 info = self.log.info
542 info = self.log.info
524 info("The IPython Notebook is running at: %s://%s:%i%s" %
543 info("The IPython Notebook is running at: %s://%s:%i%s" %
525 (proto, ip, self.port,self.base_project_url) )
544 (proto, ip, self.port,self.base_project_url) )
526 info("Use Control-C to stop this server and shut down all kernels.")
545 info("Use Control-C to stop this server and shut down all kernels.")
527
546
528 if self.open_browser:
547 if self.open_browser:
529 ip = self.ip or '127.0.0.1'
548 ip = self.ip or '127.0.0.1'
530 if self.browser:
549 if self.browser:
531 browser = webbrowser.get(self.browser)
550 browser = webbrowser.get(self.browser)
532 else:
551 else:
533 browser = webbrowser.get()
552 browser = webbrowser.get()
534
553
535 if self.file_to_run:
554 if self.file_to_run:
536 filename, _ = os.path.splitext(os.path.basename(self.file_to_run))
555 filename, _ = os.path.splitext(os.path.basename(self.file_to_run))
537 for nb in self.notebook_manager.list_notebooks():
556 for nb in self.notebook_manager.list_notebooks():
538 if filename == nb['name']:
557 if filename == nb['name']:
539 url = nb['notebook_id']
558 url = nb['notebook_id']
540 break
559 break
541 else:
560 else:
542 url = ''
561 url = ''
543 else:
562 else:
544 url = ''
563 url = ''
545 b = lambda : browser.open("%s://%s:%i%s%s" % (proto, ip,
564 b = lambda : browser.open("%s://%s:%i%s%s" % (proto, ip,
546 self.port, self.base_project_url, url),
565 self.port, self.base_project_url, url),
547 new=2)
566 new=2)
548 threading.Thread(target=b).start()
567 threading.Thread(target=b).start()
549 try:
568 try:
550 ioloop.IOLoop.instance().start()
569 ioloop.IOLoop.instance().start()
551 except KeyboardInterrupt:
570 except KeyboardInterrupt:
552 info("Interrupted...")
571 info("Interrupted...")
553 finally:
572 finally:
554 self.cleanup_kernels()
573 self.cleanup_kernels()
555
574
556
575
557 #-----------------------------------------------------------------------------
576 #-----------------------------------------------------------------------------
558 # Main entry point
577 # Main entry point
559 #-----------------------------------------------------------------------------
578 #-----------------------------------------------------------------------------
560
579
561 def launch_new_instance():
580 def launch_new_instance():
562 app = NotebookApp.instance()
581 app = NotebookApp.instance()
563 app.initialize()
582 app.initialize()
564 app.start()
583 app.start()
565
584
General Comments 0
You need to be logged in to leave comments. Login now