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