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