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