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