##// END OF EJS Templates
use IPythons config subsystem to allow overrides to the tornado web app.
Timo Paulssen -
Show More
@@ -1,369 +1,377 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
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 settings = Dict(config=True,
93 help="Supply overrides for the tornado.web.Application, that the "
94 "IPython notebook uses.")
95
92 96 def __init__(self, ipython_app, kernel_manager, notebook_manager, log):
93 97 handlers = [
94 98 (r"/", ProjectDashboardHandler),
95 99 (r"/login", LoginHandler),
96 100 (r"/logout", LogoutHandler),
97 101 (r"/new", NewHandler),
98 102 (r"/%s" % _notebook_id_regex, NamedNotebookHandler),
99 103 (r"/kernels", MainKernelHandler),
100 104 (r"/kernels/%s" % _kernel_id_regex, KernelHandler),
101 105 (r"/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
102 106 (r"/kernels/%s/iopub" % _kernel_id_regex, IOPubHandler),
103 107 (r"/kernels/%s/shell" % _kernel_id_regex, ShellHandler),
104 108 (r"/notebooks", NotebookRootHandler),
105 109 (r"/notebooks/%s" % _notebook_id_regex, NotebookHandler),
106 110 (r"/rstservice/render", RSTHandler)
107 111 ]
108 112 settings = dict(
109 113 template_path=os.path.join(os.path.dirname(__file__), "templates"),
110 114 static_path=os.path.join(os.path.dirname(__file__), "static"),
111 115 cookie_secret=os.urandom(1024),
112 116 login_url="/login",
113 117 )
114 web.Application.__init__(self, handlers, **settings)
118
119 # allow custom overrides for the tornado web app.
120 settings.update(self.settings)
121
122 super(NotebookWebApplication, self).__init__(self, handlers, **settings)
115 123
116 124 self.kernel_manager = kernel_manager
117 125 self.log = log
118 126 self.notebook_manager = notebook_manager
119 127 self.ipython_app = ipython_app
120 128 self.read_only = self.ipython_app.read_only
121 129
122 130
123 131 #-----------------------------------------------------------------------------
124 132 # Aliases and Flags
125 133 #-----------------------------------------------------------------------------
126 134
127 135 flags = dict(ipkernel_flags)
128 136 flags['no-browser']=(
129 137 {'NotebookApp' : {'open_browser' : False}},
130 138 "Don't open the notebook in a browser after startup."
131 139 )
132 140 flags['no-mathjax']=(
133 141 {'NotebookApp' : {'enable_mathjax' : False}},
134 142 """Disable MathJax
135 143
136 144 MathJax is the javascript library IPython uses to render math/LaTeX. It is
137 145 very large, so you may want to disable it if you have a slow internet
138 146 connection, or for offline use of the notebook.
139 147
140 148 When disabled, equations etc. will appear as their untransformed TeX source.
141 149 """
142 150 )
143 151 flags['read-only'] = (
144 152 {'NotebookApp' : {'read_only' : True}},
145 153 """Allow read-only access to notebooks.
146 154
147 155 When using a password to protect the notebook server, this flag
148 156 allows unauthenticated clients to view the notebook list, and
149 157 individual notebooks, but not edit them, start kernels, or run
150 158 code.
151 159
152 160 If no password is set, the server will be entirely read-only.
153 161 """
154 162 )
155 163
156 164 # the flags that are specific to the frontend
157 165 # these must be scrubbed before being passed to the kernel,
158 166 # or it will raise an error on unrecognized flags
159 167 notebook_flags = ['no-browser', 'no-mathjax', 'read-only']
160 168
161 169 aliases = dict(ipkernel_aliases)
162 170
163 171 aliases.update({
164 172 'ip': 'NotebookApp.ip',
165 173 'port': 'NotebookApp.port',
166 174 'keyfile': 'NotebookApp.keyfile',
167 175 'certfile': 'NotebookApp.certfile',
168 176 'notebook-dir': 'NotebookManager.notebook_dir',
169 177 })
170 178
171 179 # remove ipkernel flags that are singletons, and don't make sense in
172 180 # multi-kernel evironment:
173 181 aliases.pop('f', None)
174 182
175 183 notebook_aliases = [u'port', u'ip', u'keyfile', u'certfile',
176 184 u'notebook-dir']
177 185
178 186 #-----------------------------------------------------------------------------
179 187 # NotebookApp
180 188 #-----------------------------------------------------------------------------
181 189
182 190 class NotebookApp(BaseIPythonApplication):
183 191
184 192 name = 'ipython-notebook'
185 193 default_config_file_name='ipython_notebook_config.py'
186 194
187 195 description = """
188 196 The IPython HTML Notebook.
189 197
190 198 This launches a Tornado based HTML Notebook Server that serves up an
191 199 HTML5/Javascript Notebook client.
192 200 """
193 201 examples = _examples
194 202
195 203 classes = [IPKernelApp, ZMQInteractiveShell, ProfileDir, Session,
196 204 MappingKernelManager, NotebookManager]
197 205 flags = Dict(flags)
198 206 aliases = Dict(aliases)
199 207
200 208 kernel_argv = List(Unicode)
201 209
202 210 log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
203 211 default_value=logging.INFO,
204 212 config=True,
205 213 help="Set the log level by value or name.")
206 214
207 215 # Network related information.
208 216
209 217 ip = Unicode(LOCALHOST, config=True,
210 218 help="The IP address the notebook server will listen on."
211 219 )
212 220
213 221 def _ip_changed(self, name, old, new):
214 222 if new == u'*': self.ip = u''
215 223
216 224 port = Integer(8888, config=True,
217 225 help="The port the notebook server will listen on."
218 226 )
219 227
220 228 certfile = Unicode(u'', config=True,
221 229 help="""The full path to an SSL/TLS certificate file."""
222 230 )
223 231
224 232 keyfile = Unicode(u'', config=True,
225 233 help="""The full path to a private key file for usage with SSL/TLS."""
226 234 )
227 235
228 236 password = Unicode(u'', config=True,
229 237 help="""Hashed password to use for web authentication.
230 238
231 239 To generate, type in a python/IPython shell:
232 240
233 241 from IPython.lib import passwd; passwd()
234 242
235 243 The string should be of the form type:salt:hashed-password.
236 244 """
237 245 )
238 246
239 247 open_browser = Bool(True, config=True,
240 248 help="Whether to open in a browser after starting.")
241 249
242 250 read_only = Bool(False, config=True,
243 251 help="Whether to prevent editing/execution of notebooks."
244 252 )
245 253
246 254 enable_mathjax = Bool(True, config=True,
247 255 help="""Whether to enable MathJax for typesetting math/TeX
248 256
249 257 MathJax is the javascript library IPython uses to render math/LaTeX. It is
250 258 very large, so you may want to disable it if you have a slow internet
251 259 connection, or for offline use of the notebook.
252 260
253 261 When disabled, equations etc. will appear as their untransformed TeX source.
254 262 """
255 263 )
256 264 def _enable_mathjax_changed(self, name, old, new):
257 265 """set mathjax url to empty if mathjax is disabled"""
258 266 if not new:
259 267 self.mathjax_url = u''
260 268
261 269 mathjax_url = Unicode("", config=True,
262 270 help="""The url for MathJax.js."""
263 271 )
264 272 def _mathjax_url_default(self):
265 273 if not self.enable_mathjax:
266 274 return u''
267 275 static_path = os.path.join(os.path.dirname(__file__), "static")
268 276 if os.path.exists(os.path.join(static_path, 'mathjax', "MathJax.js")):
269 277 self.log.info("Using local MathJax")
270 278 return u"static/mathjax/MathJax.js"
271 279 else:
272 280 self.log.info("Using MathJax from CDN")
273 281 return u"http://cdn.mathjax.org/mathjax/latest/MathJax.js"
274 282
275 283 def _mathjax_url_changed(self, name, old, new):
276 284 if new and not self.enable_mathjax:
277 285 # enable_mathjax=False overrides mathjax_url
278 286 self.mathjax_url = u''
279 287 else:
280 288 self.log.info("Using MathJax: %s", new)
281 289
282 290 def parse_command_line(self, argv=None):
283 291 super(NotebookApp, self).parse_command_line(argv)
284 292 if argv is None:
285 293 argv = sys.argv[1:]
286 294
287 295 # Scrub frontend-specific flags
288 296 self.kernel_argv = swallow_argv(argv, notebook_aliases, notebook_flags)
289 297 # Kernel should inherit default config file from frontend
290 298 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
291 299
292 300 def init_configurables(self):
293 301 # Don't let Qt or ZMQ swallow KeyboardInterupts.
294 302 signal.signal(signal.SIGINT, signal.SIG_DFL)
295 303
296 304 # force Session default to be secure
297 305 default_secure(self.config)
298 306 # Create a KernelManager and start a kernel.
299 307 self.kernel_manager = MappingKernelManager(
300 308 config=self.config, log=self.log, kernel_argv=self.kernel_argv,
301 309 connection_dir = self.profile_dir.security_dir,
302 310 )
303 311 self.notebook_manager = NotebookManager(config=self.config, log=self.log)
304 312 self.notebook_manager.list_notebooks()
305 313
306 314 def init_logging(self):
307 315 super(NotebookApp, self).init_logging()
308 316 # This prevents double log messages because tornado use a root logger that
309 317 # self.log is a child of. The logging module dipatches log messages to a log
310 318 # and all of its ancenstors until propagate is set to False.
311 319 self.log.propagate = False
312 320
313 321 @catch_config_error
314 322 def initialize(self, argv=None):
315 323 super(NotebookApp, self).initialize(argv)
316 324 self.init_configurables()
317 325 self.web_app = NotebookWebApplication(
318 326 self, self.kernel_manager, self.notebook_manager, self.log
319 327 )
320 328 if self.certfile:
321 329 ssl_options = dict(certfile=self.certfile)
322 330 if self.keyfile:
323 331 ssl_options['keyfile'] = self.keyfile
324 332 else:
325 333 ssl_options = None
326 334 self.web_app.password = self.password
327 335 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options)
328 336 if ssl_options is None and not self.ip:
329 337 self.log.critical('WARNING: the notebook server is listening on all IP addresses '
330 338 'but not using any encryption or authentication. This is highly '
331 339 'insecure and not recommended.')
332 340
333 341 # Try random ports centered around the default.
334 342 from random import randint
335 343 n = 50 # Max number of attempts, keep reasonably large.
336 344 for port in range(self.port, self.port+5) + [self.port + randint(-2*n, 2*n) for i in range(n-5)]:
337 345 try:
338 346 self.http_server.listen(port, self.ip)
339 347 except socket.error, e:
340 348 if e.errno != errno.EADDRINUSE:
341 349 raise
342 350 self.log.info('The port %i is already in use, trying another random port.' % port)
343 351 else:
344 352 self.port = port
345 353 break
346 354
347 355 def start(self):
348 356 ip = self.ip if self.ip else '[all ip addresses on your system]'
349 357 proto = 'https' if self.certfile else 'http'
350 358 self.log.info("The IPython Notebook is running at: %s://%s:%i" % (proto,
351 359 ip,
352 360 self.port))
353 361 if self.open_browser:
354 362 ip = self.ip or '127.0.0.1'
355 363 b = lambda : webbrowser.open("%s://%s:%i" % (proto, ip, self.port),
356 364 new=2)
357 365 threading.Thread(target=b).start()
358 366
359 367 ioloop.IOLoop.instance().start()
360 368
361 369 #-----------------------------------------------------------------------------
362 370 # Main entry point
363 371 #-----------------------------------------------------------------------------
364 372
365 373 def launch_new_instance():
366 374 app = NotebookApp()
367 375 app.initialize()
368 376 app.start()
369 377
General Comments 0
You need to be logged in to leave comments. Login now