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