##// END OF EJS Templates
Kernel/notebook mapping is removed when a kernel dies....
Brian E. Granger -
Show More
@@ -1,296 +1,297 b''
1 """Tornado handlers for the notebook."""
1 """Tornado handlers for the notebook."""
2
2
3 #-----------------------------------------------------------------------------
3 #-----------------------------------------------------------------------------
4 # Imports
4 # Imports
5 #-----------------------------------------------------------------------------
5 #-----------------------------------------------------------------------------
6
6
7
7
8 from tornado import web
8 from tornado import web
9 from tornado import websocket
9 from tornado import websocket
10
10
11 from zmq.eventloop import ioloop
11 from zmq.eventloop import ioloop
12 from zmq.utils import jsonapi
12 from zmq.utils import jsonapi
13
13
14 from IPython.zmq.session import Session
14 from IPython.zmq.session import Session
15
15
16 try:
16 try:
17 from docutils.core import publish_string
17 from docutils.core import publish_string
18 except ImportError:
18 except ImportError:
19 publish_string = None
19 publish_string = None
20
20
21
21
22
22
23 #-----------------------------------------------------------------------------
23 #-----------------------------------------------------------------------------
24 # Top-level handlers
24 # Top-level handlers
25 #-----------------------------------------------------------------------------
25 #-----------------------------------------------------------------------------
26
26
27
27
28 class NBBrowserHandler(web.RequestHandler):
28 class NBBrowserHandler(web.RequestHandler):
29 def get(self):
29 def get(self):
30 nbm = self.application.notebook_manager
30 nbm = self.application.notebook_manager
31 project = nbm.notebook_dir
31 project = nbm.notebook_dir
32 self.render('nbbrowser.html', project=project)
32 self.render('nbbrowser.html', project=project)
33
33
34
34
35 class NewHandler(web.RequestHandler):
35 class NewHandler(web.RequestHandler):
36 def get(self):
36 def get(self):
37 notebook_id = self.application.notebook_manager.new_notebook()
37 notebook_id = self.application.notebook_manager.new_notebook()
38 self.render('notebook.html', notebook_id=notebook_id)
38 self.render('notebook.html', notebook_id=notebook_id)
39
39
40
40
41 class NamedNotebookHandler(web.RequestHandler):
41 class NamedNotebookHandler(web.RequestHandler):
42 def get(self, notebook_id):
42 def get(self, notebook_id):
43 nbm = self.application.notebook_manager
43 nbm = self.application.notebook_manager
44 if not nbm.notebook_exists(notebook_id):
44 if not nbm.notebook_exists(notebook_id):
45 raise web.HTTPError(404)
45 raise web.HTTPError(404)
46 self.render('notebook.html', notebook_id=notebook_id)
46 self.render('notebook.html', notebook_id=notebook_id)
47
47
48
48
49 #-----------------------------------------------------------------------------
49 #-----------------------------------------------------------------------------
50 # Kernel handlers
50 # Kernel handlers
51 #-----------------------------------------------------------------------------
51 #-----------------------------------------------------------------------------
52
52
53
53
54 class MainKernelHandler(web.RequestHandler):
54 class MainKernelHandler(web.RequestHandler):
55
55
56 def get(self):
56 def get(self):
57 km = self.application.kernel_manager
57 km = self.application.kernel_manager
58 self.finish(jsonapi.dumps(km.kernel_ids))
58 self.finish(jsonapi.dumps(km.kernel_ids))
59
59
60 def post(self):
60 def post(self):
61 km = self.application.kernel_manager
61 km = self.application.kernel_manager
62 notebook_id = self.get_argument('notebook', default=None)
62 notebook_id = self.get_argument('notebook', default=None)
63 kernel_id = km.start_kernel(notebook_id)
63 kernel_id = km.start_kernel(notebook_id)
64 self.set_header('Location', '/'+kernel_id)
64 self.set_header('Location', '/'+kernel_id)
65 self.finish(jsonapi.dumps(kernel_id))
65 self.finish(jsonapi.dumps(kernel_id))
66
66
67
67
68 class KernelHandler(web.RequestHandler):
68 class KernelHandler(web.RequestHandler):
69
69
70 SUPPORTED_METHODS = ('DELETE')
70 SUPPORTED_METHODS = ('DELETE')
71
71
72 def delete(self, kernel_id):
72 def delete(self, kernel_id):
73 km = self.application.kernel_manager
73 km = self.application.kernel_manager
74 km.kill_kernel(kernel_id)
74 km.kill_kernel(kernel_id)
75 self.set_status(204)
75 self.set_status(204)
76 self.finish()
76 self.finish()
77
77
78
78
79 class KernelActionHandler(web.RequestHandler):
79 class KernelActionHandler(web.RequestHandler):
80
80
81 def post(self, kernel_id, action):
81 def post(self, kernel_id, action):
82 km = self.application.kernel_manager
82 km = self.application.kernel_manager
83 if action == 'interrupt':
83 if action == 'interrupt':
84 km.interrupt_kernel(kernel_id)
84 km.interrupt_kernel(kernel_id)
85 self.set_status(204)
85 self.set_status(204)
86 if action == 'restart':
86 if action == 'restart':
87 new_kernel_id = km.restart_kernel(kernel_id)
87 new_kernel_id = km.restart_kernel(kernel_id)
88 self.write(jsonapi.dumps(new_kernel_id))
88 self.write(jsonapi.dumps(new_kernel_id))
89 self.finish()
89 self.finish()
90
90
91
91
92 class ZMQStreamHandler(websocket.WebSocketHandler):
92 class ZMQStreamHandler(websocket.WebSocketHandler):
93
93
94 def _reserialize_reply(self, msg_list):
94 def _reserialize_reply(self, msg_list):
95 """Reserialize a reply message using JSON.
95 """Reserialize a reply message using JSON.
96
96
97 This takes the msg list from the ZMQ socket, unserializes it using
97 This takes the msg list from the ZMQ socket, unserializes it using
98 self.session and then serializes the result using JSON. This method
98 self.session and then serializes the result using JSON. This method
99 should be used by self._on_zmq_reply to build messages that can
99 should be used by self._on_zmq_reply to build messages that can
100 be sent back to the browser.
100 be sent back to the browser.
101 """
101 """
102 idents, msg_list = self.session.feed_identities(msg_list)
102 idents, msg_list = self.session.feed_identities(msg_list)
103 msg = self.session.unserialize(msg_list)
103 msg = self.session.unserialize(msg_list)
104 try:
104 try:
105 msg['header'].pop('date')
105 msg['header'].pop('date')
106 except KeyError:
106 except KeyError:
107 pass
107 pass
108 try:
108 try:
109 msg['parent_header'].pop('date')
109 msg['parent_header'].pop('date')
110 except KeyError:
110 except KeyError:
111 pass
111 pass
112 msg.pop('buffers')
112 msg.pop('buffers')
113 return jsonapi.dumps(msg)
113 return jsonapi.dumps(msg)
114
114
115 def _on_zmq_reply(self, msg_list):
115 def _on_zmq_reply(self, msg_list):
116 try:
116 try:
117 msg = self._reserialize_reply(msg_list)
117 msg = self._reserialize_reply(msg_list)
118 except:
118 except:
119 self.application.kernel_manager.log.critical("Malformed message: %r" % msg_list)
119 self.application.kernel_manager.log.critical("Malformed message: %r" % msg_list)
120 else:
120 else:
121 self.write_message(msg)
121 self.write_message(msg)
122
122
123
123
124 class IOPubHandler(ZMQStreamHandler):
124 class IOPubHandler(ZMQStreamHandler):
125
125
126 def initialize(self, *args, **kwargs):
126 def initialize(self, *args, **kwargs):
127 self._kernel_alive = True
127 self._kernel_alive = True
128 self._beating = False
128 self._beating = False
129
129
130 def open(self, kernel_id):
130 def open(self, kernel_id):
131 km = self.application.kernel_manager
131 km = self.application.kernel_manager
132 self.kernel_id = kernel_id
132 self.kernel_id = kernel_id
133 self.session = Session()
133 self.session = Session()
134 self.time_to_dead = km.time_to_dead
134 self.time_to_dead = km.time_to_dead
135 self.iopub_stream = km.create_iopub_stream(kernel_id)
135 self.iopub_stream = km.create_iopub_stream(kernel_id)
136 self.hb_stream = km.create_hb_stream(kernel_id)
136 self.hb_stream = km.create_hb_stream(kernel_id)
137 self.iopub_stream.on_recv(self._on_zmq_reply)
137 self.iopub_stream.on_recv(self._on_zmq_reply)
138 self.start_hb(self.kernel_died)
138 self.start_hb(self.kernel_died)
139
139
140 def on_close(self):
140 def on_close(self):
141 self.stop_hb()
141 self.stop_hb()
142 self.iopub_stream.close()
142 self.iopub_stream.close()
143 self.hb_stream.close()
143 self.hb_stream.close()
144
144
145 def start_hb(self, callback):
145 def start_hb(self, callback):
146 """Start the heartbeating and call the callback if the kernel dies."""
146 """Start the heartbeating and call the callback if the kernel dies."""
147 if not self._beating:
147 if not self._beating:
148 self._kernel_alive = True
148 self._kernel_alive = True
149
149
150 def ping_or_dead():
150 def ping_or_dead():
151 if self._kernel_alive:
151 if self._kernel_alive:
152 self._kernel_alive = False
152 self._kernel_alive = False
153 self.hb_stream.send(b'ping')
153 self.hb_stream.send(b'ping')
154 else:
154 else:
155 try:
155 try:
156 callback()
156 callback()
157 except:
157 except:
158 pass
158 pass
159 finally:
159 finally:
160 self._hb_periodic_callback.stop()
160 self._hb_periodic_callback.stop()
161
161
162 def beat_received(msg):
162 def beat_received(msg):
163 self._kernel_alive = True
163 self._kernel_alive = True
164
164
165 self.hb_stream.on_recv(beat_received)
165 self.hb_stream.on_recv(beat_received)
166 self._hb_periodic_callback = ioloop.PeriodicCallback(ping_or_dead, self.time_to_dead*1000)
166 self._hb_periodic_callback = ioloop.PeriodicCallback(ping_or_dead, self.time_to_dead*1000)
167 self._hb_periodic_callback.start()
167 self._hb_periodic_callback.start()
168 self._beating= True
168 self._beating= True
169
169
170 def stop_hb(self):
170 def stop_hb(self):
171 """Stop the heartbeating and cancel all related callbacks."""
171 """Stop the heartbeating and cancel all related callbacks."""
172 if self._beating:
172 if self._beating:
173 self._hb_periodic_callback.stop()
173 self._hb_periodic_callback.stop()
174 if not self.hb_stream.closed():
174 if not self.hb_stream.closed():
175 self.hb_stream.on_recv(None)
175 self.hb_stream.on_recv(None)
176
176
177 def kernel_died(self):
177 def kernel_died(self):
178 self.application.kernel_manager.delete_mapping_for_kernel(self.kernel_id)
178 self.write_message(
179 self.write_message(
179 {'header': {'msg_type': 'status'},
180 {'header': {'msg_type': 'status'},
180 'parent_header': {},
181 'parent_header': {},
181 'content': {'execution_state':'dead'}
182 'content': {'execution_state':'dead'}
182 }
183 }
183 )
184 )
184 self.on_close()
185 self.on_close()
185
186
186
187
187 class ShellHandler(ZMQStreamHandler):
188 class ShellHandler(ZMQStreamHandler):
188
189
189 def initialize(self, *args, **kwargs):
190 def initialize(self, *args, **kwargs):
190 pass
191 pass
191
192
192 def open(self, kernel_id):
193 def open(self, kernel_id):
193 km = self.application.kernel_manager
194 km = self.application.kernel_manager
194 self.max_msg_size = km.max_msg_size
195 self.max_msg_size = km.max_msg_size
195 self.kernel_id = kernel_id
196 self.kernel_id = kernel_id
196 self.session = Session()
197 self.session = Session()
197 self.shell_stream = self.application.kernel_manager.create_shell_stream(kernel_id)
198 self.shell_stream = self.application.kernel_manager.create_shell_stream(kernel_id)
198 self.shell_stream.on_recv(self._on_zmq_reply)
199 self.shell_stream.on_recv(self._on_zmq_reply)
199
200
200 def on_message(self, msg):
201 def on_message(self, msg):
201 if len(msg) < self.max_msg_size:
202 if len(msg) < self.max_msg_size:
202 msg = jsonapi.loads(msg)
203 msg = jsonapi.loads(msg)
203 self.session.send(self.shell_stream, msg)
204 self.session.send(self.shell_stream, msg)
204
205
205 def on_close(self):
206 def on_close(self):
206 self.shell_stream.close()
207 self.shell_stream.close()
207
208
208
209
209 #-----------------------------------------------------------------------------
210 #-----------------------------------------------------------------------------
210 # Notebook web service handlers
211 # Notebook web service handlers
211 #-----------------------------------------------------------------------------
212 #-----------------------------------------------------------------------------
212
213
213 class NotebookRootHandler(web.RequestHandler):
214 class NotebookRootHandler(web.RequestHandler):
214
215
215 def get(self):
216 def get(self):
216 nbm = self.application.notebook_manager
217 nbm = self.application.notebook_manager
217 files = nbm.list_notebooks()
218 files = nbm.list_notebooks()
218 self.finish(jsonapi.dumps(files))
219 self.finish(jsonapi.dumps(files))
219
220
220 def post(self):
221 def post(self):
221 nbm = self.application.notebook_manager
222 nbm = self.application.notebook_manager
222 body = self.request.body.strip()
223 body = self.request.body.strip()
223 format = self.get_argument('format', default='json')
224 format = self.get_argument('format', default='json')
224 name = self.get_argument('name', default=None)
225 name = self.get_argument('name', default=None)
225 if body:
226 if body:
226 notebook_id = nbm.save_new_notebook(body, name=name, format=format)
227 notebook_id = nbm.save_new_notebook(body, name=name, format=format)
227 else:
228 else:
228 notebook_id = nbm.new_notebook()
229 notebook_id = nbm.new_notebook()
229 self.set_header('Location', '/'+notebook_id)
230 self.set_header('Location', '/'+notebook_id)
230 self.finish(jsonapi.dumps(notebook_id))
231 self.finish(jsonapi.dumps(notebook_id))
231
232
232
233
233 class NotebookHandler(web.RequestHandler):
234 class NotebookHandler(web.RequestHandler):
234
235
235 SUPPORTED_METHODS = ('GET', 'PUT', 'DELETE')
236 SUPPORTED_METHODS = ('GET', 'PUT', 'DELETE')
236
237
237 def get(self, notebook_id):
238 def get(self, notebook_id):
238 nbm = self.application.notebook_manager
239 nbm = self.application.notebook_manager
239 format = self.get_argument('format', default='json')
240 format = self.get_argument('format', default='json')
240 last_mod, name, data = nbm.get_notebook(notebook_id, format)
241 last_mod, name, data = nbm.get_notebook(notebook_id, format)
241 if format == u'json':
242 if format == u'json':
242 self.set_header('Content-Type', 'application/json')
243 self.set_header('Content-Type', 'application/json')
243 self.set_header('Content-Disposition','attachment; filename="%s.json"' % name)
244 self.set_header('Content-Disposition','attachment; filename="%s.json"' % name)
244 elif format == u'xml':
245 elif format == u'xml':
245 self.set_header('Content-Type', 'application/xml')
246 self.set_header('Content-Type', 'application/xml')
246 self.set_header('Content-Disposition','attachment; filename="%s.ipynb"' % name)
247 self.set_header('Content-Disposition','attachment; filename="%s.ipynb"' % name)
247 elif format == u'py':
248 elif format == u'py':
248 self.set_header('Content-Type', 'application/x-python')
249 self.set_header('Content-Type', 'application/x-python')
249 self.set_header('Content-Disposition','attachment; filename="%s.py"' % name)
250 self.set_header('Content-Disposition','attachment; filename="%s.py"' % name)
250 self.set_header('Last-Modified', last_mod)
251 self.set_header('Last-Modified', last_mod)
251 self.finish(data)
252 self.finish(data)
252
253
253 def put(self, notebook_id):
254 def put(self, notebook_id):
254 nbm = self.application.notebook_manager
255 nbm = self.application.notebook_manager
255 format = self.get_argument('format', default='json')
256 format = self.get_argument('format', default='json')
256 name = self.get_argument('name', default=None)
257 name = self.get_argument('name', default=None)
257 nbm.save_notebook(notebook_id, self.request.body, name=name, format=format)
258 nbm.save_notebook(notebook_id, self.request.body, name=name, format=format)
258 self.set_status(204)
259 self.set_status(204)
259 self.finish()
260 self.finish()
260
261
261 def delete(self, notebook_id):
262 def delete(self, notebook_id):
262 nbm = self.application.notebook_manager
263 nbm = self.application.notebook_manager
263 nbm.delete_notebook(notebook_id)
264 nbm.delete_notebook(notebook_id)
264 self.set_status(204)
265 self.set_status(204)
265 self.finish()
266 self.finish()
266
267
267 #-----------------------------------------------------------------------------
268 #-----------------------------------------------------------------------------
268 # RST web service handlers
269 # RST web service handlers
269 #-----------------------------------------------------------------------------
270 #-----------------------------------------------------------------------------
270
271
271
272
272 class RSTHandler(web.RequestHandler):
273 class RSTHandler(web.RequestHandler):
273
274
274 def post(self):
275 def post(self):
275 if publish_string is None:
276 if publish_string is None:
276 raise web.HTTPError(503)
277 raise web.HTTPError(503)
277 body = self.request.body.strip()
278 body = self.request.body.strip()
278 source = body
279 source = body
279 # template_path=os.path.join(os.path.dirname(__file__), u'templates', u'rst_template.html')
280 # template_path=os.path.join(os.path.dirname(__file__), u'templates', u'rst_template.html')
280 defaults = {'file_insertion_enabled': 0,
281 defaults = {'file_insertion_enabled': 0,
281 'raw_enabled': 0,
282 'raw_enabled': 0,
282 '_disable_config': 1,
283 '_disable_config': 1,
283 'stylesheet_path': 0
284 'stylesheet_path': 0
284 # 'template': template_path
285 # 'template': template_path
285 }
286 }
286 try:
287 try:
287 html = publish_string(source, writer_name='html',
288 html = publish_string(source, writer_name='html',
288 settings_overrides=defaults
289 settings_overrides=defaults
289 )
290 )
290 except:
291 except:
291 raise web.HTTPError(400)
292 raise web.HTTPError(400)
292 print html
293 print html
293 self.set_header('Content-Type', 'text/html')
294 self.set_header('Content-Type', 'text/html')
294 self.finish(html)
295 self.finish(html)
295
296
296
297
General Comments 0
You need to be logged in to leave comments. Login now