##// END OF EJS Templates
cleanup connection files on notebook shutdown...
MinRK -
Show More
@@ -1,385 +1,402 b''
1 1 """A tornado based IPython notebook server.
2 2
3 3 Authors:
4 4
5 5 * Brian Granger
6 6 """
7 7
8 8 #-----------------------------------------------------------------------------
9 9 # Copyright (C) 2008-2011 The IPython Development Team
10 10 #
11 11 # Distributed under the terms of the BSD License. The full license is in
12 12 # the file COPYING, distributed as part of this software.
13 13 #-----------------------------------------------------------------------------
14 14
15 15 #-----------------------------------------------------------------------------
16 16 # Imports
17 17 #-----------------------------------------------------------------------------
18 18
19 19 # stdlib
20 20 import errno
21 21 import logging
22 22 import os
23 23 import signal
24 24 import socket
25 25 import sys
26 26 import threading
27 27 import webbrowser
28 28
29 29 # Third party
30 30 import zmq
31 31
32 32 # Install the pyzmq ioloop. This has to be done before anything else from
33 33 # tornado is imported.
34 34 from zmq.eventloop import ioloop
35 35 # FIXME: ioloop.install is new in pyzmq-2.1.7, so remove this conditional
36 36 # when pyzmq dependency is updated beyond that.
37 37 if hasattr(ioloop, 'install'):
38 38 ioloop.install()
39 39 else:
40 40 import tornado.ioloop
41 41 tornado.ioloop.IOLoop = ioloop.IOLoop
42 42
43 43 from tornado import httpserver
44 44 from tornado import web
45 45
46 46 # Our own libraries
47 47 from .kernelmanager import MappingKernelManager
48 48 from .handlers import (LoginHandler, LogoutHandler,
49 49 ProjectDashboardHandler, NewHandler, NamedNotebookHandler,
50 50 MainKernelHandler, KernelHandler, KernelActionHandler, IOPubHandler,
51 51 ShellHandler, NotebookRootHandler, NotebookHandler, RSTHandler
52 52 )
53 53 from .notebookmanager import NotebookManager
54 54
55 55 from IPython.config.application import catch_config_error, boolean_flag
56 56 from IPython.core.application import BaseIPythonApplication
57 57 from IPython.core.profiledir import ProfileDir
58 58 from IPython.lib.kernel import swallow_argv
59 59 from IPython.zmq.session import Session, default_secure
60 60 from IPython.zmq.zmqshell import ZMQInteractiveShell
61 61 from IPython.zmq.ipkernel import (
62 62 flags as ipkernel_flags,
63 63 aliases as ipkernel_aliases,
64 64 IPKernelApp
65 65 )
66 66 from IPython.utils.traitlets import Dict, Unicode, Integer, List, Enum, Bool
67 67
68 68 #-----------------------------------------------------------------------------
69 69 # Module globals
70 70 #-----------------------------------------------------------------------------
71 71
72 72 _kernel_id_regex = r"(?P<kernel_id>\w+-\w+-\w+-\w+-\w+)"
73 73 _kernel_action_regex = r"(?P<action>restart|interrupt)"
74 74 _notebook_id_regex = r"(?P<notebook_id>\w+-\w+-\w+-\w+-\w+)"
75 75
76 76 LOCALHOST = '127.0.0.1'
77 77
78 78 _examples = """
79 79 ipython notebook # start the notebook
80 80 ipython notebook --profile=sympy # use the sympy profile
81 81 ipython notebook --pylab=inline # pylab in inline plotting mode
82 82 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
83 83 ipython notebook --port=5555 --ip=* # Listen on port 5555, all interfaces
84 84 """
85 85
86 86 #-----------------------------------------------------------------------------
87 87 # The Tornado web application
88 88 #-----------------------------------------------------------------------------
89 89
90 90 class NotebookWebApplication(web.Application):
91 91
92 92 def __init__(self, ipython_app, kernel_manager, notebook_manager, log, settings_overrides):
93 93 handlers = [
94 94 (r"/", ProjectDashboardHandler),
95 95 (r"/login", LoginHandler),
96 96 (r"/logout", LogoutHandler),
97 97 (r"/new", NewHandler),
98 98 (r"/%s" % _notebook_id_regex, NamedNotebookHandler),
99 99 (r"/kernels", MainKernelHandler),
100 100 (r"/kernels/%s" % _kernel_id_regex, KernelHandler),
101 101 (r"/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
102 102 (r"/kernels/%s/iopub" % _kernel_id_regex, IOPubHandler),
103 103 (r"/kernels/%s/shell" % _kernel_id_regex, ShellHandler),
104 104 (r"/notebooks", NotebookRootHandler),
105 105 (r"/notebooks/%s" % _notebook_id_regex, NotebookHandler),
106 106 (r"/rstservice/render", RSTHandler)
107 107 ]
108 108 settings = dict(
109 109 template_path=os.path.join(os.path.dirname(__file__), "templates"),
110 110 static_path=os.path.join(os.path.dirname(__file__), "static"),
111 111 cookie_secret=os.urandom(1024),
112 112 login_url="/login",
113 113 )
114 114
115 115 # allow custom overrides for the tornado web app.
116 116 settings.update(settings_overrides)
117 117
118 118 super(NotebookWebApplication, self).__init__(handlers, **settings)
119 119
120 120 self.kernel_manager = kernel_manager
121 121 self.log = log
122 122 self.notebook_manager = notebook_manager
123 123 self.ipython_app = ipython_app
124 124 self.read_only = self.ipython_app.read_only
125 125
126 126
127 127 #-----------------------------------------------------------------------------
128 128 # Aliases and Flags
129 129 #-----------------------------------------------------------------------------
130 130
131 131 flags = dict(ipkernel_flags)
132 132 flags['no-browser']=(
133 133 {'NotebookApp' : {'open_browser' : False}},
134 134 "Don't open the notebook in a browser after startup."
135 135 )
136 136 flags['no-mathjax']=(
137 137 {'NotebookApp' : {'enable_mathjax' : False}},
138 138 """Disable MathJax
139 139
140 140 MathJax is the javascript library IPython uses to render math/LaTeX. It is
141 141 very large, so you may want to disable it if you have a slow internet
142 142 connection, or for offline use of the notebook.
143 143
144 144 When disabled, equations etc. will appear as their untransformed TeX source.
145 145 """
146 146 )
147 147 flags['read-only'] = (
148 148 {'NotebookApp' : {'read_only' : True}},
149 149 """Allow read-only access to notebooks.
150 150
151 151 When using a password to protect the notebook server, this flag
152 152 allows unauthenticated clients to view the notebook list, and
153 153 individual notebooks, but not edit them, start kernels, or run
154 154 code.
155 155
156 156 If no password is set, the server will be entirely read-only.
157 157 """
158 158 )
159 159
160 160 # Add notebook manager flags
161 161 flags.update(boolean_flag('script', 'NotebookManager.save_script',
162 162 'Auto-save a .py script everytime the .ipynb notebook is saved',
163 163 'Do not auto-save .py scripts for every notebook'))
164 164
165 165 # the flags that are specific to the frontend
166 166 # these must be scrubbed before being passed to the kernel,
167 167 # or it will raise an error on unrecognized flags
168 168 notebook_flags = ['no-browser', 'no-mathjax', 'read-only', 'script', 'no-script']
169 169
170 170 aliases = dict(ipkernel_aliases)
171 171
172 172 aliases.update({
173 173 'ip': 'NotebookApp.ip',
174 174 'port': 'NotebookApp.port',
175 175 'keyfile': 'NotebookApp.keyfile',
176 176 'certfile': 'NotebookApp.certfile',
177 177 'notebook-dir': 'NotebookManager.notebook_dir',
178 178 })
179 179
180 180 # remove ipkernel flags that are singletons, and don't make sense in
181 181 # multi-kernel evironment:
182 182 aliases.pop('f', None)
183 183
184 184 notebook_aliases = [u'port', u'ip', u'keyfile', u'certfile',
185 185 u'notebook-dir']
186 186
187 187 #-----------------------------------------------------------------------------
188 188 # NotebookApp
189 189 #-----------------------------------------------------------------------------
190 190
191 191 class NotebookApp(BaseIPythonApplication):
192 192
193 193 name = 'ipython-notebook'
194 194 default_config_file_name='ipython_notebook_config.py'
195 195
196 196 description = """
197 197 The IPython HTML Notebook.
198 198
199 199 This launches a Tornado based HTML Notebook Server that serves up an
200 200 HTML5/Javascript Notebook client.
201 201 """
202 202 examples = _examples
203 203
204 204 classes = [IPKernelApp, ZMQInteractiveShell, ProfileDir, Session,
205 205 MappingKernelManager, NotebookManager]
206 206 flags = Dict(flags)
207 207 aliases = Dict(aliases)
208 208
209 209 kernel_argv = List(Unicode)
210 210
211 211 log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
212 212 default_value=logging.INFO,
213 213 config=True,
214 214 help="Set the log level by value or name.")
215 215
216 216 # Network related information.
217 217
218 218 ip = Unicode(LOCALHOST, config=True,
219 219 help="The IP address the notebook server will listen on."
220 220 )
221 221
222 222 def _ip_changed(self, name, old, new):
223 223 if new == u'*': self.ip = u''
224 224
225 225 port = Integer(8888, config=True,
226 226 help="The port the notebook server will listen on."
227 227 )
228 228
229 229 certfile = Unicode(u'', config=True,
230 230 help="""The full path to an SSL/TLS certificate file."""
231 231 )
232 232
233 233 keyfile = Unicode(u'', config=True,
234 234 help="""The full path to a private key file for usage with SSL/TLS."""
235 235 )
236 236
237 237 password = Unicode(u'', config=True,
238 238 help="""Hashed password to use for web authentication.
239 239
240 240 To generate, type in a python/IPython shell:
241 241
242 242 from IPython.lib import passwd; passwd()
243 243
244 244 The string should be of the form type:salt:hashed-password.
245 245 """
246 246 )
247 247
248 248 open_browser = Bool(True, config=True,
249 249 help="Whether to open in a browser after starting.")
250 250
251 251 read_only = Bool(False, config=True,
252 252 help="Whether to prevent editing/execution of notebooks."
253 253 )
254 254
255 255 webapp_settings = Dict(config=True,
256 256 help="Supply overrides for the tornado.web.Application that the "
257 257 "IPython notebook uses.")
258 258
259 259 enable_mathjax = Bool(True, config=True,
260 260 help="""Whether to enable MathJax for typesetting math/TeX
261 261
262 262 MathJax is the javascript library IPython uses to render math/LaTeX. It is
263 263 very large, so you may want to disable it if you have a slow internet
264 264 connection, or for offline use of the notebook.
265 265
266 266 When disabled, equations etc. will appear as their untransformed TeX source.
267 267 """
268 268 )
269 269 def _enable_mathjax_changed(self, name, old, new):
270 270 """set mathjax url to empty if mathjax is disabled"""
271 271 if not new:
272 272 self.mathjax_url = u''
273 273
274 274 mathjax_url = Unicode("", config=True,
275 275 help="""The url for MathJax.js."""
276 276 )
277 277 def _mathjax_url_default(self):
278 278 if not self.enable_mathjax:
279 279 return u''
280 280 static_path = self.webapp_settings.get("static_path", os.path.join(os.path.dirname(__file__), "static"))
281 281 if os.path.exists(os.path.join(static_path, 'mathjax', "MathJax.js")):
282 282 self.log.info("Using local MathJax")
283 283 return u"static/mathjax/MathJax.js"
284 284 else:
285 285 self.log.info("Using MathJax from CDN")
286 286 return u"http://cdn.mathjax.org/mathjax/latest/MathJax.js"
287 287
288 288 def _mathjax_url_changed(self, name, old, new):
289 289 if new and not self.enable_mathjax:
290 290 # enable_mathjax=False overrides mathjax_url
291 291 self.mathjax_url = u''
292 292 else:
293 293 self.log.info("Using MathJax: %s", new)
294 294
295 295 def parse_command_line(self, argv=None):
296 296 super(NotebookApp, self).parse_command_line(argv)
297 297 if argv is None:
298 298 argv = sys.argv[1:]
299 299
300 300 # Scrub frontend-specific flags
301 301 self.kernel_argv = swallow_argv(argv, notebook_aliases, notebook_flags)
302 302 # Kernel should inherit default config file from frontend
303 303 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
304 304
305 305 def init_configurables(self):
306 # Don't let Qt or ZMQ swallow KeyboardInterupts.
307 signal.signal(signal.SIGINT, signal.SIG_DFL)
308
309 306 # force Session default to be secure
310 307 default_secure(self.config)
311 308 # Create a KernelManager and start a kernel.
312 309 self.kernel_manager = MappingKernelManager(
313 310 config=self.config, log=self.log, kernel_argv=self.kernel_argv,
314 311 connection_dir = self.profile_dir.security_dir,
315 312 )
316 313 self.notebook_manager = NotebookManager(config=self.config, log=self.log)
317 314 self.notebook_manager.list_notebooks()
318 315
319 316 def init_logging(self):
320 317 super(NotebookApp, self).init_logging()
321 318 # This prevents double log messages because tornado use a root logger that
322 319 # self.log is a child of. The logging module dipatches log messages to a log
323 320 # and all of its ancenstors until propagate is set to False.
324 321 self.log.propagate = False
325 322
326 @catch_config_error
327 def initialize(self, argv=None):
328 super(NotebookApp, self).initialize(argv)
329 self.init_configurables()
323 def init_webapp(self):
324 """initialize tornado webapp and httpserver"""
330 325 self.web_app = NotebookWebApplication(
331 326 self, self.kernel_manager, self.notebook_manager, self.log,
332 327 self.webapp_settings
333 328 )
334 329 if self.certfile:
335 330 ssl_options = dict(certfile=self.certfile)
336 331 if self.keyfile:
337 332 ssl_options['keyfile'] = self.keyfile
338 333 else:
339 334 ssl_options = None
340 335 self.web_app.password = self.password
341 336 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options)
342 if ssl_options is None and not self.ip:
337 if ssl_options is None and not self.ip and not (self.read_only and not self.password):
343 338 self.log.critical('WARNING: the notebook server is listening on all IP addresses '
344 339 'but not using any encryption or authentication. This is highly '
345 340 'insecure and not recommended.')
346 341
347 342 # Try random ports centered around the default.
348 343 from random import randint
349 344 n = 50 # Max number of attempts, keep reasonably large.
350 345 for port in range(self.port, self.port+5) + [self.port + randint(-2*n, 2*n) for i in range(n-5)]:
351 346 try:
352 347 self.http_server.listen(port, self.ip)
353 348 except socket.error, e:
354 349 if e.errno != errno.EADDRINUSE:
355 350 raise
356 351 self.log.info('The port %i is already in use, trying another random port.' % port)
357 352 else:
358 353 self.port = port
359 354 break
360 355
356 @catch_config_error
357 def initialize(self, argv=None):
358 super(NotebookApp, self).initialize(argv)
359 self.init_configurables()
360 self.init_webapp()
361
362 def cleanup_kernels(self):
363 """shutdown all kernels
364
365 The kernels will shutdown themselves when this process no longer exists,
366 but explicit shutdown allows the KernelManagers to cleanup the connection files.
367 """
368 self.log.info('Shutting down kernels')
369 km = self.kernel_manager
370 while km.kernel_ids:
371 km.kill_kernel(km.kernel_ids[0])
372
361 373 def start(self):
362 374 ip = self.ip if self.ip else '[all ip addresses on your system]'
363 375 proto = 'https' if self.certfile else 'http'
364 376 info = self.log.info
365 377 info("The IPython Notebook is running at: %s://%s:%i" %
366 378 (proto, ip, self.port) )
367 379 info("Use Control-C to stop this server and shut down all kernels.")
368 380
369 381 if self.open_browser:
370 382 ip = self.ip or '127.0.0.1'
371 383 b = lambda : webbrowser.open("%s://%s:%i" % (proto, ip, self.port),
372 384 new=2)
373 385 threading.Thread(target=b).start()
374
386 try:
375 387 ioloop.IOLoop.instance().start()
388 except KeyboardInterrupt:
389 info("Interrupted...")
390 finally:
391 self.cleanup_kernels()
392
376 393
377 394 #-----------------------------------------------------------------------------
378 395 # Main entry point
379 396 #-----------------------------------------------------------------------------
380 397
381 398 def launch_new_instance():
382 app = NotebookApp()
399 app = NotebookApp.instance()
383 400 app.initialize()
384 401 app.start()
385 402
General Comments 0
You need to be logged in to leave comments. Login now