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