##// END OF EJS Templates
Allow notebook server to run in read-only mode...
MinRK -
Show More
@@ -1,450 +1,470 b''
1 1 """Tornado handlers for the notebook.
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 import logging
20 20 import Cookie
21 21 import uuid
22 22
23 23 from tornado import web
24 24 from tornado import websocket
25 25
26 26 from zmq.eventloop import ioloop
27 27 from zmq.utils import jsonapi
28 28
29 from IPython.external.decorator import decorator
29 30 from IPython.zmq.session import Session
30 31
31 32 try:
32 33 from docutils.core import publish_string
33 34 except ImportError:
34 35 publish_string = None
35 36
36 37
38 #-----------------------------------------------------------------------------
39 # Decorator for disabling read-only handlers
40 #-----------------------------------------------------------------------------
41
42 @decorator
43 def not_if_readonly(f, self, *args, **kwargs):
44 if self.application.ipython_app.read_only:
45 raise web.HTTPError(403, "Notebook server is read-only")
46 else:
47 return f(self, *args, **kwargs)
37 48
38 49 #-----------------------------------------------------------------------------
39 50 # Top-level handlers
40 51 #-----------------------------------------------------------------------------
41 52
42 53 class AuthenticatedHandler(web.RequestHandler):
43 54 """A RequestHandler with an authenticated user."""
44 55
45 56 def get_current_user(self):
46 57 user_id = self.get_secure_cookie("username")
47 58 # For now the user_id should not return empty, but it could eventually
48 59 if user_id == '':
49 60 user_id = 'anonymous'
50 61 if user_id is None:
51 62 # prevent extra Invalid cookie sig warnings:
52 63 self.clear_cookie('username')
53 64 if not self.application.password:
54 65 user_id = 'anonymous'
55 66 return user_id
56 67
57 68
58 69 class ProjectDashboardHandler(AuthenticatedHandler):
59 70
60 71 @web.authenticated
61 72 def get(self):
62 73 nbm = self.application.notebook_manager
63 74 project = nbm.notebook_dir
64 75 self.render(
65 76 'projectdashboard.html', project=project,
66 77 base_project_url=u'/', base_kernel_url=u'/'
67 78 )
68 79
69 80
70 81 class LoginHandler(AuthenticatedHandler):
71 82
72 83 def get(self):
73 84 self.render('login.html', next='/')
74 85
75 86 def post(self):
76 87 pwd = self.get_argument('password', default=u'')
77 88 if self.application.password and pwd == self.application.password:
78 89 self.set_secure_cookie('username', str(uuid.uuid4()))
79 90 url = self.get_argument('next', default='/')
80 91 self.redirect(url)
81 92
82 93
83 94 class NewHandler(AuthenticatedHandler):
84 95
96 @not_if_readonly
85 97 @web.authenticated
86 98 def get(self):
87 99 nbm = self.application.notebook_manager
88 100 project = nbm.notebook_dir
89 101 notebook_id = nbm.new_notebook()
90 102 self.render(
91 103 'notebook.html', project=project,
92 104 notebook_id=notebook_id,
93 105 base_project_url=u'/', base_kernel_url=u'/',
94 106 kill_kernel=False
95 107 )
96 108
97 109
98 110 class NamedNotebookHandler(AuthenticatedHandler):
99 111
100 112 @web.authenticated
101 113 def get(self, notebook_id):
102 114 nbm = self.application.notebook_manager
103 115 project = nbm.notebook_dir
104 116 if not nbm.notebook_exists(notebook_id):
105 117 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
106 118 self.render(
107 119 'notebook.html', project=project,
108 120 notebook_id=notebook_id,
109 121 base_project_url=u'/', base_kernel_url=u'/',
110 122 kill_kernel=False
111 123 )
112 124
113 125
114 126 #-----------------------------------------------------------------------------
115 127 # Kernel handlers
116 128 #-----------------------------------------------------------------------------
117 129
118 130
119 131 class MainKernelHandler(AuthenticatedHandler):
120 132
133 @not_if_readonly
121 134 @web.authenticated
122 135 def get(self):
123 136 km = self.application.kernel_manager
124 137 self.finish(jsonapi.dumps(km.kernel_ids))
125 138
139 @not_if_readonly
126 140 @web.authenticated
127 141 def post(self):
128 142 km = self.application.kernel_manager
129 143 notebook_id = self.get_argument('notebook', default=None)
130 144 kernel_id = km.start_kernel(notebook_id)
131 145 ws_url = self.application.ipython_app.get_ws_url()
132 146 data = {'ws_url':ws_url,'kernel_id':kernel_id}
133 147 self.set_header('Location', '/'+kernel_id)
134 148 self.finish(jsonapi.dumps(data))
135 149
136 150
137 151 class KernelHandler(AuthenticatedHandler):
138 152
139 153 SUPPORTED_METHODS = ('DELETE')
140 154
155 @not_if_readonly
141 156 @web.authenticated
142 157 def delete(self, kernel_id):
143 158 km = self.application.kernel_manager
144 159 km.kill_kernel(kernel_id)
145 160 self.set_status(204)
146 161 self.finish()
147 162
148 163
149 164 class KernelActionHandler(AuthenticatedHandler):
150 165
166 @not_if_readonly
151 167 @web.authenticated
152 168 def post(self, kernel_id, action):
153 169 km = self.application.kernel_manager
154 170 if action == 'interrupt':
155 171 km.interrupt_kernel(kernel_id)
156 172 self.set_status(204)
157 173 if action == 'restart':
158 174 new_kernel_id = km.restart_kernel(kernel_id)
159 175 ws_url = self.application.ipython_app.get_ws_url()
160 176 data = {'ws_url':ws_url,'kernel_id':new_kernel_id}
161 177 self.set_header('Location', '/'+new_kernel_id)
162 178 self.write(jsonapi.dumps(data))
163 179 self.finish()
164 180
165 181
166 182 class ZMQStreamHandler(websocket.WebSocketHandler):
167 183
168 184 def _reserialize_reply(self, msg_list):
169 185 """Reserialize a reply message using JSON.
170 186
171 187 This takes the msg list from the ZMQ socket, unserializes it using
172 188 self.session and then serializes the result using JSON. This method
173 189 should be used by self._on_zmq_reply to build messages that can
174 190 be sent back to the browser.
175 191 """
176 192 idents, msg_list = self.session.feed_identities(msg_list)
177 193 msg = self.session.unserialize(msg_list)
178 194 try:
179 195 msg['header'].pop('date')
180 196 except KeyError:
181 197 pass
182 198 try:
183 199 msg['parent_header'].pop('date')
184 200 except KeyError:
185 201 pass
186 202 msg.pop('buffers')
187 203 return jsonapi.dumps(msg)
188 204
189 205 def _on_zmq_reply(self, msg_list):
190 206 try:
191 207 msg = self._reserialize_reply(msg_list)
192 208 except:
193 209 self.application.log.critical("Malformed message: %r" % msg_list)
194 210 else:
195 211 self.write_message(msg)
196 212
197 213
198 214 class AuthenticatedZMQStreamHandler(ZMQStreamHandler):
199 215
200 216 def open(self, kernel_id):
201 217 self.kernel_id = kernel_id.decode('ascii')
202 218 try:
203 219 cfg = self.application.ipython_app.config
204 220 except AttributeError:
205 221 # protect from the case where this is run from something other than
206 222 # the notebook app:
207 223 cfg = None
208 224 self.session = Session(config=cfg)
209 225 self.save_on_message = self.on_message
210 226 self.on_message = self.on_first_message
211 227
212 228 def get_current_user(self):
213 229 user_id = self.get_secure_cookie("username")
214 230 if user_id == '' or (user_id is None and not self.application.password):
215 231 user_id = 'anonymous'
216 232 return user_id
217 233
218 234 def _inject_cookie_message(self, msg):
219 235 """Inject the first message, which is the document cookie,
220 236 for authentication."""
221 237 if isinstance(msg, unicode):
222 238 # Cookie can't constructor doesn't accept unicode strings for some reason
223 239 msg = msg.encode('utf8', 'replace')
224 240 try:
225 241 self.request._cookies = Cookie.SimpleCookie(msg)
226 242 except:
227 243 logging.warn("couldn't parse cookie string: %s",msg, exc_info=True)
228 244
245 @not_if_readonly
229 246 def on_first_message(self, msg):
230 247 self._inject_cookie_message(msg)
231 248 if self.get_current_user() is None:
232 249 logging.warn("Couldn't authenticate WebSocket connection")
233 250 raise web.HTTPError(403)
234 251 self.on_message = self.save_on_message
235 252
236 253
237 254 class IOPubHandler(AuthenticatedZMQStreamHandler):
238 255
239 256 def initialize(self, *args, **kwargs):
240 257 self._kernel_alive = True
241 258 self._beating = False
242 259 self.iopub_stream = None
243 260 self.hb_stream = None
244 261
245 262 def on_first_message(self, msg):
246 263 try:
247 264 super(IOPubHandler, self).on_first_message(msg)
248 265 except web.HTTPError:
249 266 self.close()
250 267 return
251 268 km = self.application.kernel_manager
252 269 self.time_to_dead = km.time_to_dead
253 270 kernel_id = self.kernel_id
254 271 try:
255 272 self.iopub_stream = km.create_iopub_stream(kernel_id)
256 273 self.hb_stream = km.create_hb_stream(kernel_id)
257 274 except web.HTTPError:
258 275 # WebSockets don't response to traditional error codes so we
259 276 # close the connection.
260 277 if not self.stream.closed():
261 278 self.stream.close()
262 279 self.close()
263 280 else:
264 281 self.iopub_stream.on_recv(self._on_zmq_reply)
265 282 self.start_hb(self.kernel_died)
266 283
267 284 def on_message(self, msg):
268 285 pass
269 286
270 287 def on_close(self):
271 288 # This method can be called twice, once by self.kernel_died and once
272 289 # from the WebSocket close event. If the WebSocket connection is
273 290 # closed before the ZMQ streams are setup, they could be None.
274 291 self.stop_hb()
275 292 if self.iopub_stream is not None and not self.iopub_stream.closed():
276 293 self.iopub_stream.on_recv(None)
277 294 self.iopub_stream.close()
278 295 if self.hb_stream is not None and not self.hb_stream.closed():
279 296 self.hb_stream.close()
280 297
281 298 def start_hb(self, callback):
282 299 """Start the heartbeating and call the callback if the kernel dies."""
283 300 if not self._beating:
284 301 self._kernel_alive = True
285 302
286 303 def ping_or_dead():
287 304 if self._kernel_alive:
288 305 self._kernel_alive = False
289 306 self.hb_stream.send(b'ping')
290 307 else:
291 308 try:
292 309 callback()
293 310 except:
294 311 pass
295 312 finally:
296 313 self._hb_periodic_callback.stop()
297 314
298 315 def beat_received(msg):
299 316 self._kernel_alive = True
300 317
301 318 self.hb_stream.on_recv(beat_received)
302 319 self._hb_periodic_callback = ioloop.PeriodicCallback(ping_or_dead, self.time_to_dead*1000)
303 320 self._hb_periodic_callback.start()
304 321 self._beating= True
305 322
306 323 def stop_hb(self):
307 324 """Stop the heartbeating and cancel all related callbacks."""
308 325 if self._beating:
309 326 self._hb_periodic_callback.stop()
310 327 if not self.hb_stream.closed():
311 328 self.hb_stream.on_recv(None)
312 329
313 330 def kernel_died(self):
314 331 self.application.kernel_manager.delete_mapping_for_kernel(self.kernel_id)
315 332 self.write_message(
316 333 {'header': {'msg_type': 'status'},
317 334 'parent_header': {},
318 335 'content': {'execution_state':'dead'}
319 336 }
320 337 )
321 338 self.on_close()
322 339
323 340
324 341 class ShellHandler(AuthenticatedZMQStreamHandler):
325 342
326 343 def initialize(self, *args, **kwargs):
327 344 self.shell_stream = None
328 345
329 346 def on_first_message(self, msg):
330 347 try:
331 348 super(ShellHandler, self).on_first_message(msg)
332 349 except web.HTTPError:
333 350 self.close()
334 351 return
335 352 km = self.application.kernel_manager
336 353 self.max_msg_size = km.max_msg_size
337 354 kernel_id = self.kernel_id
338 355 try:
339 356 self.shell_stream = km.create_shell_stream(kernel_id)
340 357 except web.HTTPError:
341 358 # WebSockets don't response to traditional error codes so we
342 359 # close the connection.
343 360 if not self.stream.closed():
344 361 self.stream.close()
345 362 self.close()
346 363 else:
347 364 self.shell_stream.on_recv(self._on_zmq_reply)
348 365
349 366 def on_message(self, msg):
350 367 if len(msg) < self.max_msg_size:
351 368 msg = jsonapi.loads(msg)
352 369 self.session.send(self.shell_stream, msg)
353 370
354 371 def on_close(self):
355 372 # Make sure the stream exists and is not already closed.
356 373 if self.shell_stream is not None and not self.shell_stream.closed():
357 374 self.shell_stream.close()
358 375
359 376
360 377 #-----------------------------------------------------------------------------
361 378 # Notebook web service handlers
362 379 #-----------------------------------------------------------------------------
363 380
364 381 class NotebookRootHandler(AuthenticatedHandler):
365 382
366 383 @web.authenticated
367 384 def get(self):
368 385 nbm = self.application.notebook_manager
369 386 files = nbm.list_notebooks()
370 387 self.finish(jsonapi.dumps(files))
371 388
389 @not_if_readonly
372 390 @web.authenticated
373 391 def post(self):
374 392 nbm = self.application.notebook_manager
375 393 body = self.request.body.strip()
376 394 format = self.get_argument('format', default='json')
377 395 name = self.get_argument('name', default=None)
378 396 if body:
379 397 notebook_id = nbm.save_new_notebook(body, name=name, format=format)
380 398 else:
381 399 notebook_id = nbm.new_notebook()
382 400 self.set_header('Location', '/'+notebook_id)
383 401 self.finish(jsonapi.dumps(notebook_id))
384 402
385 403
386 404 class NotebookHandler(AuthenticatedHandler):
387 405
388 406 SUPPORTED_METHODS = ('GET', 'PUT', 'DELETE')
389 407
390 408 @web.authenticated
391 409 def get(self, notebook_id):
392 410 nbm = self.application.notebook_manager
393 411 format = self.get_argument('format', default='json')
394 412 last_mod, name, data = nbm.get_notebook(notebook_id, format)
395 413 if format == u'json':
396 414 self.set_header('Content-Type', 'application/json')
397 415 self.set_header('Content-Disposition','attachment; filename="%s.ipynb"' % name)
398 416 elif format == u'py':
399 417 self.set_header('Content-Type', 'application/x-python')
400 418 self.set_header('Content-Disposition','attachment; filename="%s.py"' % name)
401 419 self.set_header('Last-Modified', last_mod)
402 420 self.finish(data)
403 421
422 @not_if_readonly
404 423 @web.authenticated
405 424 def put(self, notebook_id):
406 425 nbm = self.application.notebook_manager
407 426 format = self.get_argument('format', default='json')
408 427 name = self.get_argument('name', default=None)
409 428 nbm.save_notebook(notebook_id, self.request.body, name=name, format=format)
410 429 self.set_status(204)
411 430 self.finish()
412 431
432 @not_if_readonly
413 433 @web.authenticated
414 434 def delete(self, notebook_id):
415 435 nbm = self.application.notebook_manager
416 436 nbm.delete_notebook(notebook_id)
417 437 self.set_status(204)
418 438 self.finish()
419 439
420 440 #-----------------------------------------------------------------------------
421 441 # RST web service handlers
422 442 #-----------------------------------------------------------------------------
423 443
424 444
425 445 class RSTHandler(AuthenticatedHandler):
426 446
427 447 @web.authenticated
428 448 def post(self):
429 449 if publish_string is None:
430 450 raise web.HTTPError(503, u'docutils not available')
431 451 body = self.request.body.strip()
432 452 source = body
433 453 # template_path=os.path.join(os.path.dirname(__file__), u'templates', u'rst_template.html')
434 454 defaults = {'file_insertion_enabled': 0,
435 455 'raw_enabled': 0,
436 456 '_disable_config': 1,
437 457 'stylesheet_path': 0
438 458 # 'template': template_path
439 459 }
440 460 try:
441 461 html = publish_string(source, writer_name='html',
442 462 settings_overrides=defaults
443 463 )
444 464 except:
445 465 raise web.HTTPError(400, u'Invalid RST')
446 466 print html
447 467 self.set_header('Content-Type', 'text/html')
448 468 self.finish(html)
449 469
450 470
@@ -1,315 +1,323 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 import errno
20 20 import logging
21 21 import os
22 22 import signal
23 23 import socket
24 24 import sys
25 25 import webbrowser
26 26
27 27 import zmq
28 28
29 29 # Install the pyzmq ioloop. This has to be done before anything else from
30 30 # tornado is imported.
31 31 from zmq.eventloop import ioloop
32 32 import tornado.ioloop
33 33 tornado.ioloop.IOLoop = ioloop.IOLoop
34 34
35 35 from tornado import httpserver
36 36 from tornado import web
37 37
38 38 from .kernelmanager import MappingKernelManager
39 39 from .handlers import (LoginHandler,
40 40 ProjectDashboardHandler, NewHandler, NamedNotebookHandler,
41 41 MainKernelHandler, KernelHandler, KernelActionHandler, IOPubHandler,
42 42 ShellHandler, NotebookRootHandler, NotebookHandler, RSTHandler
43 43 )
44 44 from .notebookmanager import NotebookManager
45 45
46 46 from IPython.core.application import BaseIPythonApplication
47 47 from IPython.core.profiledir import ProfileDir
48 48 from IPython.zmq.session import Session, default_secure
49 49 from IPython.zmq.zmqshell import ZMQInteractiveShell
50 50 from IPython.zmq.ipkernel import (
51 51 flags as ipkernel_flags,
52 52 aliases as ipkernel_aliases,
53 53 IPKernelApp
54 54 )
55 55 from IPython.utils.traitlets import Dict, Unicode, Int, List, Enum, Bool
56 56
57 57 #-----------------------------------------------------------------------------
58 58 # Module globals
59 59 #-----------------------------------------------------------------------------
60 60
61 61 _kernel_id_regex = r"(?P<kernel_id>\w+-\w+-\w+-\w+-\w+)"
62 62 _kernel_action_regex = r"(?P<action>restart|interrupt)"
63 63 _notebook_id_regex = r"(?P<notebook_id>\w+-\w+-\w+-\w+-\w+)"
64 64
65 65 LOCALHOST = '127.0.0.1'
66 66
67 67 _examples = """
68 68 ipython notebook # start the notebook
69 69 ipython notebook --profile=sympy # use the sympy profile
70 70 ipython notebook --pylab=inline # pylab in inline plotting mode
71 71 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
72 72 ipython notebook --port=5555 --ip=* # Listen on port 5555, all interfaces
73 73 """
74 74
75 75 #-----------------------------------------------------------------------------
76 76 # The Tornado web application
77 77 #-----------------------------------------------------------------------------
78 78
79 79 class NotebookWebApplication(web.Application):
80 80
81 81 def __init__(self, ipython_app, kernel_manager, notebook_manager, log):
82 82 handlers = [
83 83 (r"/", ProjectDashboardHandler),
84 84 (r"/login", LoginHandler),
85 85 (r"/new", NewHandler),
86 86 (r"/%s" % _notebook_id_regex, NamedNotebookHandler),
87 87 (r"/kernels", MainKernelHandler),
88 88 (r"/kernels/%s" % _kernel_id_regex, KernelHandler),
89 89 (r"/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
90 90 (r"/kernels/%s/iopub" % _kernel_id_regex, IOPubHandler),
91 91 (r"/kernels/%s/shell" % _kernel_id_regex, ShellHandler),
92 92 (r"/notebooks", NotebookRootHandler),
93 93 (r"/notebooks/%s" % _notebook_id_regex, NotebookHandler),
94 94 (r"/rstservice/render", RSTHandler)
95 95 ]
96 96 settings = dict(
97 97 template_path=os.path.join(os.path.dirname(__file__), "templates"),
98 98 static_path=os.path.join(os.path.dirname(__file__), "static"),
99 99 cookie_secret=os.urandom(1024),
100 100 login_url="/login",
101 101 )
102 102 web.Application.__init__(self, handlers, **settings)
103 103
104 104 self.kernel_manager = kernel_manager
105 105 self.log = log
106 106 self.notebook_manager = notebook_manager
107 107 self.ipython_app = ipython_app
108 108
109 109
110 110 #-----------------------------------------------------------------------------
111 111 # Aliases and Flags
112 112 #-----------------------------------------------------------------------------
113 113
114 114 flags = dict(ipkernel_flags)
115 115 flags['no-browser']=(
116 116 {'NotebookApp' : {'open_browser' : False}},
117 117 "Don't open the notebook in a browser after startup."
118 118 )
119 flags['read-only'] = (
120 {'NotebookApp' : {'read_only' : True}},
121 "Launch the Notebook server in read-only mode, not allowing execution or editing"
122 )
119 123
120 124 # the flags that are specific to the frontend
121 125 # these must be scrubbed before being passed to the kernel,
122 126 # or it will raise an error on unrecognized flags
123 127 notebook_flags = ['no-browser']
124 128
125 129 aliases = dict(ipkernel_aliases)
126 130
127 131 aliases.update({
128 132 'ip': 'NotebookApp.ip',
129 133 'port': 'NotebookApp.port',
130 134 'keyfile': 'NotebookApp.keyfile',
131 135 'certfile': 'NotebookApp.certfile',
132 136 'ws-hostname': 'NotebookApp.ws_hostname',
133 137 'notebook-dir': 'NotebookManager.notebook_dir',
134 138 })
135 139
136 140 # remove ipkernel flags that are singletons, and don't make sense in
137 141 # multi-kernel evironment:
138 142 aliases.pop('f', None)
139 143
140 144 notebook_aliases = [u'port', u'ip', u'keyfile', u'certfile', u'ws-hostname',
141 145 u'notebook-dir']
142 146
143 147 #-----------------------------------------------------------------------------
144 148 # NotebookApp
145 149 #-----------------------------------------------------------------------------
146 150
147 151 class NotebookApp(BaseIPythonApplication):
148 152
149 153 name = 'ipython-notebook'
150 154 default_config_file_name='ipython_notebook_config.py'
151 155
152 156 description = """
153 157 The IPython HTML Notebook.
154 158
155 159 This launches a Tornado based HTML Notebook Server that serves up an
156 160 HTML5/Javascript Notebook client.
157 161 """
158 162 examples = _examples
159 163
160 164 classes = [IPKernelApp, ZMQInteractiveShell, ProfileDir, Session,
161 165 MappingKernelManager, NotebookManager]
162 166 flags = Dict(flags)
163 167 aliases = Dict(aliases)
164 168
165 169 kernel_argv = List(Unicode)
166 170
167 171 log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
168 172 default_value=logging.INFO,
169 173 config=True,
170 174 help="Set the log level by value or name.")
171 175
172 176 # Network related information.
173 177
174 178 ip = Unicode(LOCALHOST, config=True,
175 179 help="The IP address the notebook server will listen on."
176 180 )
177 181
178 182 def _ip_changed(self, name, old, new):
179 183 if new == u'*': self.ip = u''
180 184
181 185 port = Int(8888, config=True,
182 186 help="The port the notebook server will listen on."
183 187 )
184 188
185 189 ws_hostname = Unicode(LOCALHOST, config=True,
186 190 help="""The FQDN or IP for WebSocket connections. The default will work
187 191 fine when the server is listening on localhost, but this needs to
188 192 be set if the ip option is used. It will be used as the hostname part
189 193 of the WebSocket url: ws://hostname/path."""
190 194 )
191 195
192 196 certfile = Unicode(u'', config=True,
193 197 help="""The full path to an SSL/TLS certificate file."""
194 198 )
195 199
196 200 keyfile = Unicode(u'', config=True,
197 201 help="""The full path to a private key file for usage with SSL/TLS."""
198 202 )
199 203
200 204 password = Unicode(u'', config=True,
201 205 help="""Password to use for web authentication"""
202 206 )
203 207
204 208 open_browser = Bool(True, config=True,
205 209 help="Whether to open in a browser after starting.")
210
211 read_only = Bool(False, config=True,
212 help="Whether to prevent editing/execution of notebooks."
213 )
206 214
207 215 def get_ws_url(self):
208 216 """Return the WebSocket URL for this server."""
209 217 if self.certfile:
210 218 prefix = u'wss://'
211 219 else:
212 220 prefix = u'ws://'
213 221 return prefix + self.ws_hostname + u':' + unicode(self.port)
214 222
215 223 def parse_command_line(self, argv=None):
216 224 super(NotebookApp, self).parse_command_line(argv)
217 225 if argv is None:
218 226 argv = sys.argv[1:]
219 227
220 228 self.kernel_argv = list(argv) # copy
221 229 # Kernel should inherit default config file from frontend
222 230 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
223 231 # Scrub frontend-specific flags
224 232 for a in argv:
225 233 if a.startswith('-') and a.lstrip('-') in notebook_flags:
226 234 self.kernel_argv.remove(a)
227 235 swallow_next = False
228 236 for a in argv:
229 237 if swallow_next:
230 238 self.kernel_argv.remove(a)
231 239 swallow_next = False
232 240 continue
233 241 if a.startswith('-'):
234 242 split = a.lstrip('-').split('=')
235 243 alias = split[0]
236 244 if alias in notebook_aliases:
237 245 self.kernel_argv.remove(a)
238 246 if len(split) == 1:
239 247 # alias passed with arg via space
240 248 swallow_next = True
241 249
242 250 def init_configurables(self):
243 251 # Don't let Qt or ZMQ swallow KeyboardInterupts.
244 252 signal.signal(signal.SIGINT, signal.SIG_DFL)
245 253
246 254 # force Session default to be secure
247 255 default_secure(self.config)
248 256 # Create a KernelManager and start a kernel.
249 257 self.kernel_manager = MappingKernelManager(
250 258 config=self.config, log=self.log, kernel_argv=self.kernel_argv,
251 259 connection_dir = self.profile_dir.security_dir,
252 260 )
253 261 self.notebook_manager = NotebookManager(config=self.config, log=self.log)
254 262 self.notebook_manager.list_notebooks()
255 263
256 264 def init_logging(self):
257 265 super(NotebookApp, self).init_logging()
258 266 # This prevents double log messages because tornado use a root logger that
259 267 # self.log is a child of. The logging module dipatches log messages to a log
260 268 # and all of its ancenstors until propagate is set to False.
261 269 self.log.propagate = False
262 270
263 271 def initialize(self, argv=None):
264 272 super(NotebookApp, self).initialize(argv)
265 273 self.init_configurables()
266 274 self.web_app = NotebookWebApplication(
267 275 self, self.kernel_manager, self.notebook_manager, self.log
268 276 )
269 277 if self.certfile:
270 278 ssl_options = dict(certfile=self.certfile)
271 279 if self.keyfile:
272 280 ssl_options['keyfile'] = self.keyfile
273 281 else:
274 282 ssl_options = None
275 283 self.web_app.password = self.password
276 284 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options)
277 285 if ssl_options is None and not self.ip:
278 286 self.log.critical('WARNING: the notebook server is listening on all IP addresses '
279 287 'but not using any encryption or authentication. This is highly '
280 288 'insecure and not recommended.')
281 289
282 290 # Try random ports centered around the default.
283 291 from random import randint
284 292 n = 50 # Max number of attempts, keep reasonably large.
285 for port in [self.port] + [self.port + randint(-2*n, 2*n) for i in range(n)]:
293 for port in range(self.port, self.port+5) + [self.port + randint(-2*n, 2*n) for i in range(n-5)]:
286 294 try:
287 295 self.http_server.listen(port, self.ip)
288 296 except socket.error, e:
289 297 if e.errno != errno.EADDRINUSE:
290 298 raise
291 299 self.log.info('The port %i is already in use, trying another random port.' % port)
292 300 else:
293 301 self.port = port
294 302 break
295 303
296 304 def start(self):
297 305 ip = self.ip if self.ip else '[all ip addresses on your system]'
298 306 proto = 'https' if self.certfile else 'http'
299 307 self.log.info("The IPython Notebook is running at: %s://%s:%i" % (proto,
300 308 ip,
301 309 self.port))
302 310 if self.open_browser:
303 311 ip = self.ip or '127.0.0.1'
304 312 webbrowser.open("%s://%s:%i" % (proto, ip, self.port), new=2)
305 313 ioloop.IOLoop.instance().start()
306 314
307 315 #-----------------------------------------------------------------------------
308 316 # Main entry point
309 317 #-----------------------------------------------------------------------------
310 318
311 319 def launch_new_instance():
312 320 app = NotebookApp()
313 321 app.initialize()
314 322 app.start()
315 323
General Comments 0
You need to be logged in to leave comments. Login now