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