##// END OF EJS Templates
enh: added authentication ability for webapp
Satrajit Ghosh -
Show More
@@ -1,334 +1,357 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 from tornado import web
19 from tornado import web
20 from tornado import websocket
20 from tornado import websocket
21
21
22 from zmq.eventloop import ioloop
22 from zmq.eventloop import ioloop
23 from zmq.utils import jsonapi
23 from zmq.utils import jsonapi
24
24
25 from IPython.zmq.session import Session
25 from IPython.zmq.session import Session
26
26
27 try:
27 try:
28 from docutils.core import publish_string
28 from docutils.core import publish_string
29 except ImportError:
29 except ImportError:
30 publish_string = None
30 publish_string = None
31
31
32
32
33
33
34 #-----------------------------------------------------------------------------
34 #-----------------------------------------------------------------------------
35 # Top-level handlers
35 # Top-level handlers
36 #-----------------------------------------------------------------------------
36 #-----------------------------------------------------------------------------
37
37
38
38 class BaseHandler(web.RequestHandler):
39 class NBBrowserHandler(web.RequestHandler):
39 def get_current_user(self):
40 user_id = self.get_secure_cookie("user")
41 keyword = self.get_secure_cookie("keyword")
42 if self.application.keyword and self.application.keyword != keyword:
43 return None
44 if not user_id:
45 user_id = 'anonymous'
46 return user_id
47
48 class NBBrowserHandler(BaseHandler):
49 @web.authenticated
40 def get(self):
50 def get(self):
41 nbm = self.application.notebook_manager
51 nbm = self.application.notebook_manager
42 project = nbm.notebook_dir
52 project = nbm.notebook_dir
43 self.render('nbbrowser.html', project=project)
53 self.render('nbbrowser.html', project=project)
44
54
55 class LoginHandler(BaseHandler):
56 def get(self):
57 user_id = self.get_secure_cookie("user")
58 self.write('<html><body><form action="/login" method="post">'
59 'Name: <input type="text" name="name" value=%s>'
60 'Keyword: <input type="text" name="keyword">'
61 '<input type="submit" value="Sign in">'
62 '</form></body></html>'%user_id)
63
64 def post(self):
65 self.set_secure_cookie("user", self.get_argument("name", default=u''))
66 self.set_secure_cookie("keyword", self.get_argument("keyword", default=u''))
67 self.redirect("/")
45
68
46 class NewHandler(web.RequestHandler):
69 class NewHandler(web.RequestHandler):
47 def get(self):
70 def get(self):
48 notebook_id = self.application.notebook_manager.new_notebook()
71 notebook_id = self.application.notebook_manager.new_notebook()
49 self.render('notebook.html', notebook_id=notebook_id)
72 self.render('notebook.html', notebook_id=notebook_id)
50
73
51
74
52 class NamedNotebookHandler(web.RequestHandler):
75 class NamedNotebookHandler(web.RequestHandler):
53 def get(self, notebook_id):
76 def get(self, notebook_id):
54 nbm = self.application.notebook_manager
77 nbm = self.application.notebook_manager
55 if not nbm.notebook_exists(notebook_id):
78 if not nbm.notebook_exists(notebook_id):
56 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
79 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
57 self.render('notebook.html', notebook_id=notebook_id)
80 self.render('notebook.html', notebook_id=notebook_id)
58
81
59
82
60 #-----------------------------------------------------------------------------
83 #-----------------------------------------------------------------------------
61 # Kernel handlers
84 # Kernel handlers
62 #-----------------------------------------------------------------------------
85 #-----------------------------------------------------------------------------
63
86
64
87
65 class MainKernelHandler(web.RequestHandler):
88 class MainKernelHandler(web.RequestHandler):
66
89
67 def get(self):
90 def get(self):
68 km = self.application.kernel_manager
91 km = self.application.kernel_manager
69 self.finish(jsonapi.dumps(km.kernel_ids))
92 self.finish(jsonapi.dumps(km.kernel_ids))
70
93
71 def post(self):
94 def post(self):
72 km = self.application.kernel_manager
95 km = self.application.kernel_manager
73 notebook_id = self.get_argument('notebook', default=None)
96 notebook_id = self.get_argument('notebook', default=None)
74 kernel_id = km.start_kernel(notebook_id)
97 kernel_id = km.start_kernel(notebook_id)
75 ws_url = self.application.ipython_app.get_ws_url()
98 ws_url = self.application.ipython_app.get_ws_url()
76 data = {'ws_url':ws_url,'kernel_id':kernel_id}
99 data = {'ws_url':ws_url,'kernel_id':kernel_id}
77 self.set_header('Location', '/'+kernel_id)
100 self.set_header('Location', '/'+kernel_id)
78 self.finish(jsonapi.dumps(data))
101 self.finish(jsonapi.dumps(data))
79
102
80
103
81 class KernelHandler(web.RequestHandler):
104 class KernelHandler(web.RequestHandler):
82
105
83 SUPPORTED_METHODS = ('DELETE')
106 SUPPORTED_METHODS = ('DELETE')
84
107
85 def delete(self, kernel_id):
108 def delete(self, kernel_id):
86 km = self.application.kernel_manager
109 km = self.application.kernel_manager
87 km.kill_kernel(kernel_id)
110 km.kill_kernel(kernel_id)
88 self.set_status(204)
111 self.set_status(204)
89 self.finish()
112 self.finish()
90
113
91
114
92 class KernelActionHandler(web.RequestHandler):
115 class KernelActionHandler(web.RequestHandler):
93
116
94 def post(self, kernel_id, action):
117 def post(self, kernel_id, action):
95 km = self.application.kernel_manager
118 km = self.application.kernel_manager
96 if action == 'interrupt':
119 if action == 'interrupt':
97 km.interrupt_kernel(kernel_id)
120 km.interrupt_kernel(kernel_id)
98 self.set_status(204)
121 self.set_status(204)
99 if action == 'restart':
122 if action == 'restart':
100 new_kernel_id = km.restart_kernel(kernel_id)
123 new_kernel_id = km.restart_kernel(kernel_id)
101 ws_url = self.application.ipython_app.get_ws_url()
124 ws_url = self.application.ipython_app.get_ws_url()
102 data = {'ws_url':ws_url,'kernel_id':new_kernel_id}
125 data = {'ws_url':ws_url,'kernel_id':new_kernel_id}
103 self.set_header('Location', '/'+new_kernel_id)
126 self.set_header('Location', '/'+new_kernel_id)
104 self.write(jsonapi.dumps(data))
127 self.write(jsonapi.dumps(data))
105 self.finish()
128 self.finish()
106
129
107
130
108 class ZMQStreamHandler(websocket.WebSocketHandler):
131 class ZMQStreamHandler(websocket.WebSocketHandler):
109
132
110 def _reserialize_reply(self, msg_list):
133 def _reserialize_reply(self, msg_list):
111 """Reserialize a reply message using JSON.
134 """Reserialize a reply message using JSON.
112
135
113 This takes the msg list from the ZMQ socket, unserializes it using
136 This takes the msg list from the ZMQ socket, unserializes it using
114 self.session and then serializes the result using JSON. This method
137 self.session and then serializes the result using JSON. This method
115 should be used by self._on_zmq_reply to build messages that can
138 should be used by self._on_zmq_reply to build messages that can
116 be sent back to the browser.
139 be sent back to the browser.
117 """
140 """
118 idents, msg_list = self.session.feed_identities(msg_list)
141 idents, msg_list = self.session.feed_identities(msg_list)
119 msg = self.session.unserialize(msg_list)
142 msg = self.session.unserialize(msg_list)
120 try:
143 try:
121 msg['header'].pop('date')
144 msg['header'].pop('date')
122 except KeyError:
145 except KeyError:
123 pass
146 pass
124 try:
147 try:
125 msg['parent_header'].pop('date')
148 msg['parent_header'].pop('date')
126 except KeyError:
149 except KeyError:
127 pass
150 pass
128 msg.pop('buffers')
151 msg.pop('buffers')
129 return jsonapi.dumps(msg)
152 return jsonapi.dumps(msg)
130
153
131 def _on_zmq_reply(self, msg_list):
154 def _on_zmq_reply(self, msg_list):
132 try:
155 try:
133 msg = self._reserialize_reply(msg_list)
156 msg = self._reserialize_reply(msg_list)
134 except:
157 except:
135 self.application.kernel_manager.log.critical("Malformed message: %r" % msg_list)
158 self.application.kernel_manager.log.critical("Malformed message: %r" % msg_list)
136 else:
159 else:
137 self.write_message(msg)
160 self.write_message(msg)
138
161
139
162
140 class IOPubHandler(ZMQStreamHandler):
163 class IOPubHandler(ZMQStreamHandler):
141
164
142 def initialize(self, *args, **kwargs):
165 def initialize(self, *args, **kwargs):
143 self._kernel_alive = True
166 self._kernel_alive = True
144 self._beating = False
167 self._beating = False
145 self.iopub_stream = None
168 self.iopub_stream = None
146 self.hb_stream = None
169 self.hb_stream = None
147
170
148 def open(self, kernel_id):
171 def open(self, kernel_id):
149 km = self.application.kernel_manager
172 km = self.application.kernel_manager
150 self.kernel_id = kernel_id
173 self.kernel_id = kernel_id
151 self.session = Session()
174 self.session = Session()
152 self.time_to_dead = km.time_to_dead
175 self.time_to_dead = km.time_to_dead
153 try:
176 try:
154 self.iopub_stream = km.create_iopub_stream(kernel_id)
177 self.iopub_stream = km.create_iopub_stream(kernel_id)
155 self.hb_stream = km.create_hb_stream(kernel_id)
178 self.hb_stream = km.create_hb_stream(kernel_id)
156 except web.HTTPError:
179 except web.HTTPError:
157 # WebSockets don't response to traditional error codes so we
180 # WebSockets don't response to traditional error codes so we
158 # close the connection.
181 # close the connection.
159 if not self.stream.closed():
182 if not self.stream.closed():
160 self.stream.close()
183 self.stream.close()
161 else:
184 else:
162 self.iopub_stream.on_recv(self._on_zmq_reply)
185 self.iopub_stream.on_recv(self._on_zmq_reply)
163 self.start_hb(self.kernel_died)
186 self.start_hb(self.kernel_died)
164
187
165 def on_close(self):
188 def on_close(self):
166 # This method can be called twice, once by self.kernel_died and once
189 # This method can be called twice, once by self.kernel_died and once
167 # from the WebSocket close event. If the WebSocket connection is
190 # from the WebSocket close event. If the WebSocket connection is
168 # closed before the ZMQ streams are setup, they could be None.
191 # closed before the ZMQ streams are setup, they could be None.
169 self.stop_hb()
192 self.stop_hb()
170 if self.iopub_stream is not None and not self.iopub_stream.closed():
193 if self.iopub_stream is not None and not self.iopub_stream.closed():
171 self.iopub_stream.on_recv(None)
194 self.iopub_stream.on_recv(None)
172 self.iopub_stream.close()
195 self.iopub_stream.close()
173 if self.hb_stream is not None and not self.hb_stream.closed():
196 if self.hb_stream is not None and not self.hb_stream.closed():
174 self.hb_stream.close()
197 self.hb_stream.close()
175
198
176 def start_hb(self, callback):
199 def start_hb(self, callback):
177 """Start the heartbeating and call the callback if the kernel dies."""
200 """Start the heartbeating and call the callback if the kernel dies."""
178 if not self._beating:
201 if not self._beating:
179 self._kernel_alive = True
202 self._kernel_alive = True
180
203
181 def ping_or_dead():
204 def ping_or_dead():
182 if self._kernel_alive:
205 if self._kernel_alive:
183 self._kernel_alive = False
206 self._kernel_alive = False
184 self.hb_stream.send(b'ping')
207 self.hb_stream.send(b'ping')
185 else:
208 else:
186 try:
209 try:
187 callback()
210 callback()
188 except:
211 except:
189 pass
212 pass
190 finally:
213 finally:
191 self._hb_periodic_callback.stop()
214 self._hb_periodic_callback.stop()
192
215
193 def beat_received(msg):
216 def beat_received(msg):
194 self._kernel_alive = True
217 self._kernel_alive = True
195
218
196 self.hb_stream.on_recv(beat_received)
219 self.hb_stream.on_recv(beat_received)
197 self._hb_periodic_callback = ioloop.PeriodicCallback(ping_or_dead, self.time_to_dead*1000)
220 self._hb_periodic_callback = ioloop.PeriodicCallback(ping_or_dead, self.time_to_dead*1000)
198 self._hb_periodic_callback.start()
221 self._hb_periodic_callback.start()
199 self._beating= True
222 self._beating= True
200
223
201 def stop_hb(self):
224 def stop_hb(self):
202 """Stop the heartbeating and cancel all related callbacks."""
225 """Stop the heartbeating and cancel all related callbacks."""
203 if self._beating:
226 if self._beating:
204 self._hb_periodic_callback.stop()
227 self._hb_periodic_callback.stop()
205 if not self.hb_stream.closed():
228 if not self.hb_stream.closed():
206 self.hb_stream.on_recv(None)
229 self.hb_stream.on_recv(None)
207
230
208 def kernel_died(self):
231 def kernel_died(self):
209 self.application.kernel_manager.delete_mapping_for_kernel(self.kernel_id)
232 self.application.kernel_manager.delete_mapping_for_kernel(self.kernel_id)
210 self.write_message(
233 self.write_message(
211 {'header': {'msg_type': 'status'},
234 {'header': {'msg_type': 'status'},
212 'parent_header': {},
235 'parent_header': {},
213 'content': {'execution_state':'dead'}
236 'content': {'execution_state':'dead'}
214 }
237 }
215 )
238 )
216 self.on_close()
239 self.on_close()
217
240
218
241
219 class ShellHandler(ZMQStreamHandler):
242 class ShellHandler(ZMQStreamHandler):
220
243
221 def initialize(self, *args, **kwargs):
244 def initialize(self, *args, **kwargs):
222 self.shell_stream = None
245 self.shell_stream = None
223
246
224 def open(self, kernel_id):
247 def open(self, kernel_id):
225 km = self.application.kernel_manager
248 km = self.application.kernel_manager
226 self.max_msg_size = km.max_msg_size
249 self.max_msg_size = km.max_msg_size
227 self.kernel_id = kernel_id
250 self.kernel_id = kernel_id
228 try:
251 try:
229 self.shell_stream = km.create_shell_stream(kernel_id)
252 self.shell_stream = km.create_shell_stream(kernel_id)
230 except web.HTTPError:
253 except web.HTTPError:
231 # WebSockets don't response to traditional error codes so we
254 # WebSockets don't response to traditional error codes so we
232 # close the connection.
255 # close the connection.
233 if not self.stream.closed():
256 if not self.stream.closed():
234 self.stream.close()
257 self.stream.close()
235 else:
258 else:
236 self.session = Session()
259 self.session = Session()
237 self.shell_stream.on_recv(self._on_zmq_reply)
260 self.shell_stream.on_recv(self._on_zmq_reply)
238
261
239 def on_message(self, msg):
262 def on_message(self, msg):
240 if len(msg) < self.max_msg_size:
263 if len(msg) < self.max_msg_size:
241 msg = jsonapi.loads(msg)
264 msg = jsonapi.loads(msg)
242 self.session.send(self.shell_stream, msg)
265 self.session.send(self.shell_stream, msg)
243
266
244 def on_close(self):
267 def on_close(self):
245 # Make sure the stream exists and is not already closed.
268 # Make sure the stream exists and is not already closed.
246 if self.shell_stream is not None and not self.shell_stream.closed():
269 if self.shell_stream is not None and not self.shell_stream.closed():
247 self.shell_stream.close()
270 self.shell_stream.close()
248
271
249
272
250 #-----------------------------------------------------------------------------
273 #-----------------------------------------------------------------------------
251 # Notebook web service handlers
274 # Notebook web service handlers
252 #-----------------------------------------------------------------------------
275 #-----------------------------------------------------------------------------
253
276
254 class NotebookRootHandler(web.RequestHandler):
277 class NotebookRootHandler(web.RequestHandler):
255
278
256 def get(self):
279 def get(self):
257 nbm = self.application.notebook_manager
280 nbm = self.application.notebook_manager
258 files = nbm.list_notebooks()
281 files = nbm.list_notebooks()
259 self.finish(jsonapi.dumps(files))
282 self.finish(jsonapi.dumps(files))
260
283
261 def post(self):
284 def post(self):
262 nbm = self.application.notebook_manager
285 nbm = self.application.notebook_manager
263 body = self.request.body.strip()
286 body = self.request.body.strip()
264 format = self.get_argument('format', default='json')
287 format = self.get_argument('format', default='json')
265 name = self.get_argument('name', default=None)
288 name = self.get_argument('name', default=None)
266 if body:
289 if body:
267 notebook_id = nbm.save_new_notebook(body, name=name, format=format)
290 notebook_id = nbm.save_new_notebook(body, name=name, format=format)
268 else:
291 else:
269 notebook_id = nbm.new_notebook()
292 notebook_id = nbm.new_notebook()
270 self.set_header('Location', '/'+notebook_id)
293 self.set_header('Location', '/'+notebook_id)
271 self.finish(jsonapi.dumps(notebook_id))
294 self.finish(jsonapi.dumps(notebook_id))
272
295
273
296
274 class NotebookHandler(web.RequestHandler):
297 class NotebookHandler(web.RequestHandler):
275
298
276 SUPPORTED_METHODS = ('GET', 'PUT', 'DELETE')
299 SUPPORTED_METHODS = ('GET', 'PUT', 'DELETE')
277
300
278 def get(self, notebook_id):
301 def get(self, notebook_id):
279 nbm = self.application.notebook_manager
302 nbm = self.application.notebook_manager
280 format = self.get_argument('format', default='json')
303 format = self.get_argument('format', default='json')
281 last_mod, name, data = nbm.get_notebook(notebook_id, format)
304 last_mod, name, data = nbm.get_notebook(notebook_id, format)
282 if format == u'json':
305 if format == u'json':
283 self.set_header('Content-Type', 'application/json')
306 self.set_header('Content-Type', 'application/json')
284 self.set_header('Content-Disposition','attachment; filename="%s.ipynb"' % name)
307 self.set_header('Content-Disposition','attachment; filename="%s.ipynb"' % name)
285 elif format == u'py':
308 elif format == u'py':
286 self.set_header('Content-Type', 'application/x-python')
309 self.set_header('Content-Type', 'application/x-python')
287 self.set_header('Content-Disposition','attachment; filename="%s.py"' % name)
310 self.set_header('Content-Disposition','attachment; filename="%s.py"' % name)
288 self.set_header('Last-Modified', last_mod)
311 self.set_header('Last-Modified', last_mod)
289 self.finish(data)
312 self.finish(data)
290
313
291 def put(self, notebook_id):
314 def put(self, notebook_id):
292 nbm = self.application.notebook_manager
315 nbm = self.application.notebook_manager
293 format = self.get_argument('format', default='json')
316 format = self.get_argument('format', default='json')
294 name = self.get_argument('name', default=None)
317 name = self.get_argument('name', default=None)
295 nbm.save_notebook(notebook_id, self.request.body, name=name, format=format)
318 nbm.save_notebook(notebook_id, self.request.body, name=name, format=format)
296 self.set_status(204)
319 self.set_status(204)
297 self.finish()
320 self.finish()
298
321
299 def delete(self, notebook_id):
322 def delete(self, notebook_id):
300 nbm = self.application.notebook_manager
323 nbm = self.application.notebook_manager
301 nbm.delete_notebook(notebook_id)
324 nbm.delete_notebook(notebook_id)
302 self.set_status(204)
325 self.set_status(204)
303 self.finish()
326 self.finish()
304
327
305 #-----------------------------------------------------------------------------
328 #-----------------------------------------------------------------------------
306 # RST web service handlers
329 # RST web service handlers
307 #-----------------------------------------------------------------------------
330 #-----------------------------------------------------------------------------
308
331
309
332
310 class RSTHandler(web.RequestHandler):
333 class RSTHandler(web.RequestHandler):
311
334
312 def post(self):
335 def post(self):
313 if publish_string is None:
336 if publish_string is None:
314 raise web.HTTPError(503, u'docutils not available')
337 raise web.HTTPError(503, u'docutils not available')
315 body = self.request.body.strip()
338 body = self.request.body.strip()
316 source = body
339 source = body
317 # template_path=os.path.join(os.path.dirname(__file__), u'templates', u'rst_template.html')
340 # template_path=os.path.join(os.path.dirname(__file__), u'templates', u'rst_template.html')
318 defaults = {'file_insertion_enabled': 0,
341 defaults = {'file_insertion_enabled': 0,
319 'raw_enabled': 0,
342 'raw_enabled': 0,
320 '_disable_config': 1,
343 '_disable_config': 1,
321 'stylesheet_path': 0
344 'stylesheet_path': 0
322 # 'template': template_path
345 # 'template': template_path
323 }
346 }
324 try:
347 try:
325 html = publish_string(source, writer_name='html',
348 html = publish_string(source, writer_name='html',
326 settings_overrides=defaults
349 settings_overrides=defaults
327 )
350 )
328 except:
351 except:
329 raise web.HTTPError(400, u'Invalid RST')
352 raise web.HTTPError(400, u'Invalid RST')
330 print html
353 print html
331 self.set_header('Content-Type', 'text/html')
354 self.set_header('Content-Type', 'text/html')
332 self.finish(html)
355 self.finish(html)
333
356
334
357
@@ -1,280 +1,289 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
25
26 import zmq
26 import zmq
27
27
28 # Install the pyzmq ioloop. This has to be done before anything else from
28 # Install the pyzmq ioloop. This has to be done before anything else from
29 # tornado is imported.
29 # tornado is imported.
30 from zmq.eventloop import ioloop
30 from zmq.eventloop import ioloop
31 import tornado.ioloop
31 import tornado.ioloop
32 tornado.ioloop = ioloop
32 tornado.ioloop = ioloop
33
33
34 from tornado import httpserver
34 from tornado import httpserver
35 from tornado import web
35 from tornado import web
36
36
37 from .kernelmanager import MappingKernelManager
37 from .kernelmanager import MappingKernelManager
38 from .handlers import (
38 from .handlers import (LoginHandler,
39 NBBrowserHandler, NewHandler, NamedNotebookHandler,
39 NBBrowserHandler, NewHandler, NamedNotebookHandler,
40 MainKernelHandler, KernelHandler, KernelActionHandler, IOPubHandler,
40 MainKernelHandler, KernelHandler, KernelActionHandler, IOPubHandler,
41 ShellHandler, NotebookRootHandler, NotebookHandler, RSTHandler
41 ShellHandler, NotebookRootHandler, NotebookHandler, RSTHandler
42 )
42 )
43 from .notebookmanager import NotebookManager
43 from .notebookmanager import NotebookManager
44
44
45 from IPython.core.application import BaseIPythonApplication
45 from IPython.core.application import BaseIPythonApplication
46 from IPython.core.profiledir import ProfileDir
46 from IPython.core.profiledir import ProfileDir
47 from IPython.zmq.session import Session
47 from IPython.zmq.session import Session
48 from IPython.zmq.zmqshell import ZMQInteractiveShell
48 from IPython.zmq.zmqshell import ZMQInteractiveShell
49 from IPython.zmq.ipkernel import (
49 from IPython.zmq.ipkernel import (
50 flags as ipkernel_flags,
50 flags as ipkernel_flags,
51 aliases as ipkernel_aliases,
51 aliases as ipkernel_aliases,
52 IPKernelApp
52 IPKernelApp
53 )
53 )
54 from IPython.utils.traitlets import Dict, Unicode, Int, List, Enum
54 from IPython.utils.traitlets import Dict, Unicode, Int, List, Enum
55
55
56 #-----------------------------------------------------------------------------
56 #-----------------------------------------------------------------------------
57 # Module globals
57 # Module globals
58 #-----------------------------------------------------------------------------
58 #-----------------------------------------------------------------------------
59
59
60 _kernel_id_regex = r"(?P<kernel_id>\w+-\w+-\w+-\w+-\w+)"
60 _kernel_id_regex = r"(?P<kernel_id>\w+-\w+-\w+-\w+-\w+)"
61 _kernel_action_regex = r"(?P<action>restart|interrupt)"
61 _kernel_action_regex = r"(?P<action>restart|interrupt)"
62 _notebook_id_regex = r"(?P<notebook_id>\w+-\w+-\w+-\w+-\w+)"
62 _notebook_id_regex = r"(?P<notebook_id>\w+-\w+-\w+-\w+-\w+)"
63
63
64 LOCALHOST = '127.0.0.1'
64 LOCALHOST = '127.0.0.1'
65
65
66 _examples = """
66 _examples = """
67 ipython notebook # start the notebook
67 ipython notebook # start the notebook
68 ipython notebook --profile=sympy # use the sympy profile
68 ipython notebook --profile=sympy # use the sympy profile
69 ipython notebook --pylab=inline # pylab in inline plotting mode
69 ipython notebook --pylab=inline # pylab in inline plotting mode
70 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
70 ipython notebook --certfile=mycert.pem # use SSL/TLS certificate
71 ipython notebook --port=5555 --ip=* # Listen on port 5555, all interfaces
71 ipython notebook --port=5555 --ip=* # Listen on port 5555, all interfaces
72 """
72 """
73
73
74 #-----------------------------------------------------------------------------
74 #-----------------------------------------------------------------------------
75 # The Tornado web application
75 # The Tornado web application
76 #-----------------------------------------------------------------------------
76 #-----------------------------------------------------------------------------
77
77
78 class NotebookWebApplication(web.Application):
78 class NotebookWebApplication(web.Application):
79
79
80 def __init__(self, ipython_app, kernel_manager, notebook_manager, log):
80 def __init__(self, ipython_app, kernel_manager, notebook_manager, log):
81 handlers = [
81 handlers = [
82 (r"/", NBBrowserHandler),
82 (r"/", NBBrowserHandler),
83 (r"/login", LoginHandler),
83 (r"/new", NewHandler),
84 (r"/new", NewHandler),
84 (r"/%s" % _notebook_id_regex, NamedNotebookHandler),
85 (r"/%s" % _notebook_id_regex, NamedNotebookHandler),
85 (r"/kernels", MainKernelHandler),
86 (r"/kernels", MainKernelHandler),
86 (r"/kernels/%s" % _kernel_id_regex, KernelHandler),
87 (r"/kernels/%s" % _kernel_id_regex, KernelHandler),
87 (r"/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
88 (r"/kernels/%s/%s" % (_kernel_id_regex, _kernel_action_regex), KernelActionHandler),
88 (r"/kernels/%s/iopub" % _kernel_id_regex, IOPubHandler),
89 (r"/kernels/%s/iopub" % _kernel_id_regex, IOPubHandler),
89 (r"/kernels/%s/shell" % _kernel_id_regex, ShellHandler),
90 (r"/kernels/%s/shell" % _kernel_id_regex, ShellHandler),
90 (r"/notebooks", NotebookRootHandler),
91 (r"/notebooks", NotebookRootHandler),
91 (r"/notebooks/%s" % _notebook_id_regex, NotebookHandler),
92 (r"/notebooks/%s" % _notebook_id_regex, NotebookHandler),
92 (r"/rstservice/render", RSTHandler)
93 (r"/rstservice/render", RSTHandler)
93 ]
94 ]
94 settings = dict(
95 settings = dict(
95 template_path=os.path.join(os.path.dirname(__file__), "templates"),
96 template_path=os.path.join(os.path.dirname(__file__), "templates"),
96 static_path=os.path.join(os.path.dirname(__file__), "static"),
97 static_path=os.path.join(os.path.dirname(__file__), "static"),
98 cookie_secret="61oETzKXQAGaYdkL5gEmGeJJFuYh7EQnp2XdTP1o/Vo=",
99 login_url="/login",
97 )
100 )
98 web.Application.__init__(self, handlers, **settings)
101 web.Application.__init__(self, handlers, **settings)
99
102
100 self.kernel_manager = kernel_manager
103 self.kernel_manager = kernel_manager
101 self.log = log
104 self.log = log
102 self.notebook_manager = notebook_manager
105 self.notebook_manager = notebook_manager
103 self.ipython_app = ipython_app
106 self.ipython_app = ipython_app
104
107
105
108
106 #-----------------------------------------------------------------------------
109 #-----------------------------------------------------------------------------
107 # Aliases and Flags
110 # Aliases and Flags
108 #-----------------------------------------------------------------------------
111 #-----------------------------------------------------------------------------
109
112
110 flags = dict(ipkernel_flags)
113 flags = dict(ipkernel_flags)
111
114
112 # the flags that are specific to the frontend
115 # the flags that are specific to the frontend
113 # these must be scrubbed before being passed to the kernel,
116 # these must be scrubbed before being passed to the kernel,
114 # or it will raise an error on unrecognized flags
117 # or it will raise an error on unrecognized flags
115 notebook_flags = []
118 notebook_flags = []
116
119
117 aliases = dict(ipkernel_aliases)
120 aliases = dict(ipkernel_aliases)
118
121
119 aliases.update({
122 aliases.update({
120 'ip': 'IPythonNotebookApp.ip',
123 'ip': 'IPythonNotebookApp.ip',
121 'port': 'IPythonNotebookApp.port',
124 'port': 'IPythonNotebookApp.port',
122 'keyfile': 'IPythonNotebookApp.keyfile',
125 'keyfile': 'IPythonNotebookApp.keyfile',
123 'certfile': 'IPythonNotebookApp.certfile',
126 'certfile': 'IPythonNotebookApp.certfile',
124 'ws-hostname': 'IPythonNotebookApp.ws_hostname',
127 'ws-hostname': 'IPythonNotebookApp.ws_hostname',
125 'notebook-dir': 'NotebookManager.notebook_dir'
128 'notebook-dir': 'NotebookManager.notebook_dir',
129 'keyword' : 'IPythonNotebookApp.keyword'
126 })
130 })
127
131
128 notebook_aliases = [u'port', u'ip', u'keyfile', u'certfile', u'ws-hostname',
132 notebook_aliases = [u'port', u'ip', u'keyfile', u'certfile', u'ws-hostname',
129 u'notebook-dir']
133 u'notebook-dir']
130
134
131 #-----------------------------------------------------------------------------
135 #-----------------------------------------------------------------------------
132 # IPythonNotebookApp
136 # IPythonNotebookApp
133 #-----------------------------------------------------------------------------
137 #-----------------------------------------------------------------------------
134
138
135 class IPythonNotebookApp(BaseIPythonApplication):
139 class IPythonNotebookApp(BaseIPythonApplication):
136
140
137 name = 'ipython-notebook'
141 name = 'ipython-notebook'
138 default_config_file_name='ipython_notebook_config.py'
142 default_config_file_name='ipython_notebook_config.py'
139
143
140 description = """
144 description = """
141 The IPython HTML Notebook.
145 The IPython HTML Notebook.
142
146
143 This launches a Tornado based HTML Notebook Server that serves up an
147 This launches a Tornado based HTML Notebook Server that serves up an
144 HTML5/Javascript Notebook client.
148 HTML5/Javascript Notebook client.
145 """
149 """
146 examples = _examples
150 examples = _examples
147
151
148 classes = [IPKernelApp, ZMQInteractiveShell, ProfileDir, Session,
152 classes = [IPKernelApp, ZMQInteractiveShell, ProfileDir, Session,
149 MappingKernelManager, NotebookManager]
153 MappingKernelManager, NotebookManager]
150 flags = Dict(flags)
154 flags = Dict(flags)
151 aliases = Dict(aliases)
155 aliases = Dict(aliases)
152
156
153 kernel_argv = List(Unicode)
157 kernel_argv = List(Unicode)
154
158
155 log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
159 log_level = Enum((0,10,20,30,40,50,'DEBUG','INFO','WARN','ERROR','CRITICAL'),
156 default_value=logging.INFO,
160 default_value=logging.INFO,
157 config=True,
161 config=True,
158 help="Set the log level by value or name.")
162 help="Set the log level by value or name.")
159
163
160 # Network related information.
164 # Network related information.
161
165
162 ip = Unicode(LOCALHOST, config=True,
166 ip = Unicode(LOCALHOST, config=True,
163 help="The IP address the notebook server will listen on."
167 help="The IP address the notebook server will listen on."
164 )
168 )
165
169
166 def _ip_changed(self, name, old, new):
170 def _ip_changed(self, name, old, new):
167 if new == u'*': self.ip = u''
171 if new == u'*': self.ip = u''
168
172
169 port = Int(8888, config=True,
173 port = Int(8888, config=True,
170 help="The port the notebook server will listen on."
174 help="The port the notebook server will listen on."
171 )
175 )
172
176
173 ws_hostname = Unicode(LOCALHOST, config=True,
177 ws_hostname = Unicode(LOCALHOST, config=True,
174 help="""The FQDN or IP for WebSocket connections. The default will work
178 help="""The FQDN or IP for WebSocket connections. The default will work
175 fine when the server is listening on localhost, but this needs to
179 fine when the server is listening on localhost, but this needs to
176 be set if the ip option is used. It will be used as the hostname part
180 be set if the ip option is used. It will be used as the hostname part
177 of the WebSocket url: ws://hostname/path."""
181 of the WebSocket url: ws://hostname/path."""
178 )
182 )
179
183
180 certfile = Unicode(u'', config=True,
184 certfile = Unicode(u'', config=True,
181 help="""The full path to an SSL/TLS certificate file."""
185 help="""The full path to an SSL/TLS certificate file."""
182 )
186 )
183
187
184 keyfile = Unicode(u'', config=True,
188 keyfile = Unicode(u'', config=True,
185 help="""The full path to a private key file for usage with SSL/TLS."""
189 help="""The full path to a private key file for usage with SSL/TLS."""
186 )
190 )
187
191
192 keyword = Unicode(u'', config=True,
193 help="""Keyword to use for web authentication"""
194 )
195
188 def get_ws_url(self):
196 def get_ws_url(self):
189 """Return the WebSocket URL for this server."""
197 """Return the WebSocket URL for this server."""
190 if self.certfile:
198 if self.certfile:
191 prefix = u'wss://'
199 prefix = u'wss://'
192 else:
200 else:
193 prefix = u'ws://'
201 prefix = u'ws://'
194 return prefix + self.ws_hostname + u':' + unicode(self.port)
202 return prefix + self.ws_hostname + u':' + unicode(self.port)
195
203
196 def parse_command_line(self, argv=None):
204 def parse_command_line(self, argv=None):
197 super(IPythonNotebookApp, self).parse_command_line(argv)
205 super(IPythonNotebookApp, self).parse_command_line(argv)
198 if argv is None:
206 if argv is None:
199 argv = sys.argv[1:]
207 argv = sys.argv[1:]
200
208
201 self.kernel_argv = list(argv) # copy
209 self.kernel_argv = list(argv) # copy
202 # Kernel should inherit default config file from frontend
210 # Kernel should inherit default config file from frontend
203 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
211 self.kernel_argv.append("--KernelApp.parent_appname='%s'"%self.name)
204 # Scrub frontend-specific flags
212 # Scrub frontend-specific flags
205 for a in argv:
213 for a in argv:
206 if a.startswith('-') and a.lstrip('-') in notebook_flags:
214 if a.startswith('-') and a.lstrip('-') in notebook_flags:
207 self.kernel_argv.remove(a)
215 self.kernel_argv.remove(a)
208 for a in argv:
216 for a in argv:
209 if a.startswith('-'):
217 if a.startswith('-'):
210 alias = a.lstrip('-').split('=')[0]
218 alias = a.lstrip('-').split('=')[0]
211 if alias in notebook_aliases:
219 if alias in notebook_aliases:
212 self.kernel_argv.remove(a)
220 self.kernel_argv.remove(a)
213
221
214 def init_configurables(self):
222 def init_configurables(self):
215 # Don't let Qt or ZMQ swallow KeyboardInterupts.
223 # Don't let Qt or ZMQ swallow KeyboardInterupts.
216 signal.signal(signal.SIGINT, signal.SIG_DFL)
224 signal.signal(signal.SIGINT, signal.SIG_DFL)
217
225
218 # Create a KernelManager and start a kernel.
226 # Create a KernelManager and start a kernel.
219 self.kernel_manager = MappingKernelManager(
227 self.kernel_manager = MappingKernelManager(
220 config=self.config, log=self.log, kernel_argv=self.kernel_argv
228 config=self.config, log=self.log, kernel_argv=self.kernel_argv
221 )
229 )
222 self.notebook_manager = NotebookManager(config=self.config, log=self.log)
230 self.notebook_manager = NotebookManager(config=self.config, log=self.log)
223 self.notebook_manager.list_notebooks()
231 self.notebook_manager.list_notebooks()
224
232
225 def init_logging(self):
233 def init_logging(self):
226 super(IPythonNotebookApp, self).init_logging()
234 super(IPythonNotebookApp, self).init_logging()
227 # This prevents double log messages because tornado use a root logger that
235 # This prevents double log messages because tornado use a root logger that
228 # self.log is a child of. The logging module dipatches log messages to a log
236 # self.log is a child of. The logging module dipatches log messages to a log
229 # and all of its ancenstors until propagate is set to False.
237 # and all of its ancenstors until propagate is set to False.
230 self.log.propagate = False
238 self.log.propagate = False
231
239
232 def initialize(self, argv=None):
240 def initialize(self, argv=None):
233 super(IPythonNotebookApp, self).initialize(argv)
241 super(IPythonNotebookApp, self).initialize(argv)
234 self.init_configurables()
242 self.init_configurables()
235 self.web_app = NotebookWebApplication(
243 self.web_app = NotebookWebApplication(
236 self, self.kernel_manager, self.notebook_manager, self.log
244 self, self.kernel_manager, self.notebook_manager, self.log
237 )
245 )
238 if self.certfile:
246 if self.certfile:
239 ssl_options = dict(certfile=self.certfile)
247 ssl_options = dict(certfile=self.certfile)
240 if self.keyfile:
248 if self.keyfile:
241 ssl_options['keyfile'] = self.keyfile
249 ssl_options['keyfile'] = self.keyfile
242 else:
250 else:
243 ssl_options = None
251 ssl_options = None
252 self.web_app.keyword = self.keyword
244 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options)
253 self.http_server = httpserver.HTTPServer(self.web_app, ssl_options=ssl_options)
245 if ssl_options is None and not self.ip:
254 if ssl_options is None and not self.ip:
246 self.log.critical('WARNING: the notebook server is listening on all IP addresses '
255 self.log.critical('WARNING: the notebook server is listening on all IP addresses '
247 'but not using any encryption or authentication. This is highly '
256 'but not using any encryption or authentication. This is highly '
248 'insecure and not recommended.')
257 'insecure and not recommended.')
249
258
250 # Try random ports centered around the default.
259 # Try random ports centered around the default.
251 from random import randint
260 from random import randint
252 n = 50 # Max number of attempts, keep reasonably large.
261 n = 50 # Max number of attempts, keep reasonably large.
253 for port in [self.port] + [self.port + randint(-2*n, 2*n) for i in range(n)]:
262 for port in [self.port] + [self.port + randint(-2*n, 2*n) for i in range(n)]:
254 try:
263 try:
255 self.http_server.listen(port, self.ip)
264 self.http_server.listen(port, self.ip)
256 except socket.error, e:
265 except socket.error, e:
257 if e.errno != errno.EADDRINUSE:
266 if e.errno != errno.EADDRINUSE:
258 raise
267 raise
259 self.log.info('The port %i is already in use, trying another random port.' % port)
268 self.log.info('The port %i is already in use, trying another random port.' % port)
260 else:
269 else:
261 self.port = port
270 self.port = port
262 break
271 break
263
272
264 def start(self):
273 def start(self):
265 ip = self.ip if self.ip else '[all ip addresses on your system]'
274 ip = self.ip if self.ip else '[all ip addresses on your system]'
266 proto = 'https' if self.certfile else 'http'
275 proto = 'https' if self.certfile else 'http'
267 self.log.info("The IPython Notebook is running at: %s://%s:%i" % (proto,
276 self.log.info("The IPython Notebook is running at: %s://%s:%i" % (proto,
268 ip,
277 ip,
269 self.port))
278 self.port))
270 ioloop.IOLoop.instance().start()
279 ioloop.IOLoop.instance().start()
271
280
272 #-----------------------------------------------------------------------------
281 #-----------------------------------------------------------------------------
273 # Main entry point
282 # Main entry point
274 #-----------------------------------------------------------------------------
283 #-----------------------------------------------------------------------------
275
284
276 def launch_new_instance():
285 def launch_new_instance():
277 app = IPythonNotebookApp()
286 app = IPythonNotebookApp()
278 app.initialize()
287 app.initialize()
279 app.start()
288 app.start()
280
289
General Comments 0
You need to be logged in to leave comments. Login now