##// END OF EJS Templates
Monkeypatch Tornado 2.1.1 so it works with Google Chrome 16....
Fernando Perez -
Show More
@@ -1,496 +1,545 b''
1 """Tornado handlers for the notebook.
1 """Tornado handlers for the notebook.
2
2
3 Authors:
3 Authors:
4
4
5 * Brian Granger
5 * Brian Granger
6 """
6 """
7
7
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9 # Copyright (C) 2008-2011 The IPython Development Team
9 # Copyright (C) 2008-2011 The IPython Development Team
10 #
10 #
11 # Distributed under the terms of the BSD License. The full license is in
11 # Distributed under the terms of the BSD License. The full license is in
12 # the file COPYING, distributed as part of this software.
12 # the file COPYING, distributed as part of this software.
13 #-----------------------------------------------------------------------------
13 #-----------------------------------------------------------------------------
14
14
15 #-----------------------------------------------------------------------------
15 #-----------------------------------------------------------------------------
16 # Imports
16 # Imports
17 #-----------------------------------------------------------------------------
17 #-----------------------------------------------------------------------------
18
18
19 import logging
19 import logging
20 import Cookie
20 import Cookie
21 import uuid
21 import uuid
22
22
23 from tornado import web
23 from tornado import web
24 from tornado import websocket
24 from tornado import websocket
25
25
26 from zmq.eventloop import ioloop
26 from zmq.eventloop import ioloop
27 from zmq.utils import jsonapi
27 from zmq.utils import jsonapi
28
28
29 from IPython.external.decorator import decorator
29 from IPython.external.decorator import decorator
30 from IPython.zmq.session import Session
30 from IPython.zmq.session import Session
31
31
32 try:
32 try:
33 from docutils.core import publish_string
33 from docutils.core import publish_string
34 except ImportError:
34 except ImportError:
35 publish_string = None
35 publish_string = None
36
36
37 #-----------------------------------------------------------------------------
38 # Monkeypatch for Tornado 2.1.1 - Remove when no longer necessary!
39 #-----------------------------------------------------------------------------
40
41 # Google Chrome, as of release 16, changed its websocket protocol number. The
42 # parts tornado cares about haven't really changed, so it's OK to continue
43 # accepting Chrome connections, but as of Tornado 2.1.1 (the currently released
44 # version as of Oct 30/2011) the version check fails, see the issue report:
45
46 # https://github.com/facebook/tornado/issues/385
47
48 # This issue has been fixed in Tornado post 2.1.1:
49
50 # https://github.com/facebook/tornado/commit/84d7b458f956727c3b0d6710
51
52 # Here we manually apply the same patch as above so that users of IPython can
53 # continue to work with an officially released Tornado. We make the
54 # monkeypatch version check as narrow as possible to limit its effects; once
55 # Tornado 2.1.1 is no longer found in the wild we'll delete this code.
56
57 import tornado
58
59 if tornado.version == '2.1.1':
60
61 def _execute(self, transforms, *args, **kwargs):
62 from tornado.websocket import WebSocketProtocol8, WebSocketProtocol76
63
64 self.open_args = args
65 self.open_kwargs = kwargs
66
67 # The difference between version 8 and 13 is that in 8 the
68 # client sends a "Sec-Websocket-Origin" header and in 13 it's
69 # simply "Origin".
70 if self.request.headers.get("Sec-WebSocket-Version") in ("7", "8", "13"):
71 self.ws_connection = WebSocketProtocol8(self)
72 self.ws_connection.accept_connection()
73
74 elif self.request.headers.get("Sec-WebSocket-Version"):
75 self.stream.write(tornado.escape.utf8(
76 "HTTP/1.1 426 Upgrade Required\r\n"
77 "Sec-WebSocket-Version: 8\r\n\r\n"))
78 self.stream.close()
79
80 else:
81 self.ws_connection = WebSocketProtocol76(self)
82 self.ws_connection.accept_connection()
83
84 websocket.WebSocketHandler._execute = _execute
85 del _execute
37
86
38 #-----------------------------------------------------------------------------
87 #-----------------------------------------------------------------------------
39 # Decorator for disabling read-only handlers
88 # Decorator for disabling read-only handlers
40 #-----------------------------------------------------------------------------
89 #-----------------------------------------------------------------------------
41
90
42 @decorator
91 @decorator
43 def not_if_readonly(f, self, *args, **kwargs):
92 def not_if_readonly(f, self, *args, **kwargs):
44 if self.application.read_only:
93 if self.application.read_only:
45 raise web.HTTPError(403, "Notebook server is read-only")
94 raise web.HTTPError(403, "Notebook server is read-only")
46 else:
95 else:
47 return f(self, *args, **kwargs)
96 return f(self, *args, **kwargs)
48
97
49 @decorator
98 @decorator
50 def authenticate_unless_readonly(f, self, *args, **kwargs):
99 def authenticate_unless_readonly(f, self, *args, **kwargs):
51 """authenticate this page *unless* readonly view is active.
100 """authenticate this page *unless* readonly view is active.
52
101
53 In read-only mode, the notebook list and print view should
102 In read-only mode, the notebook list and print view should
54 be accessible without authentication.
103 be accessible without authentication.
55 """
104 """
56
105
57 @web.authenticated
106 @web.authenticated
58 def auth_f(self, *args, **kwargs):
107 def auth_f(self, *args, **kwargs):
59 return f(self, *args, **kwargs)
108 return f(self, *args, **kwargs)
60 if self.application.read_only:
109 if self.application.read_only:
61 return f(self, *args, **kwargs)
110 return f(self, *args, **kwargs)
62 else:
111 else:
63 return auth_f(self, *args, **kwargs)
112 return auth_f(self, *args, **kwargs)
64
113
65 #-----------------------------------------------------------------------------
114 #-----------------------------------------------------------------------------
66 # Top-level handlers
115 # Top-level handlers
67 #-----------------------------------------------------------------------------
116 #-----------------------------------------------------------------------------
68
117
69 class AuthenticatedHandler(web.RequestHandler):
118 class AuthenticatedHandler(web.RequestHandler):
70 """A RequestHandler with an authenticated user."""
119 """A RequestHandler with an authenticated user."""
71
120
72 def get_current_user(self):
121 def get_current_user(self):
73 user_id = self.get_secure_cookie("username")
122 user_id = self.get_secure_cookie("username")
74 # For now the user_id should not return empty, but it could eventually
123 # For now the user_id should not return empty, but it could eventually
75 if user_id == '':
124 if user_id == '':
76 user_id = 'anonymous'
125 user_id = 'anonymous'
77 if user_id is None:
126 if user_id is None:
78 # prevent extra Invalid cookie sig warnings:
127 # prevent extra Invalid cookie sig warnings:
79 self.clear_cookie('username')
128 self.clear_cookie('username')
80 if not self.application.password and not self.application.read_only:
129 if not self.application.password and not self.application.read_only:
81 user_id = 'anonymous'
130 user_id = 'anonymous'
82 return user_id
131 return user_id
83
132
84 @property
133 @property
85 def read_only(self):
134 def read_only(self):
86 if self.application.read_only:
135 if self.application.read_only:
87 if self.application.password:
136 if self.application.password:
88 return self.get_current_user() is None
137 return self.get_current_user() is None
89 else:
138 else:
90 return True
139 return True
91 else:
140 else:
92 return False
141 return False
93
142
94
143
95
144
96 class ProjectDashboardHandler(AuthenticatedHandler):
145 class ProjectDashboardHandler(AuthenticatedHandler):
97
146
98 @authenticate_unless_readonly
147 @authenticate_unless_readonly
99 def get(self):
148 def get(self):
100 nbm = self.application.notebook_manager
149 nbm = self.application.notebook_manager
101 project = nbm.notebook_dir
150 project = nbm.notebook_dir
102 self.render(
151 self.render(
103 'projectdashboard.html', project=project,
152 'projectdashboard.html', project=project,
104 base_project_url=u'/', base_kernel_url=u'/',
153 base_project_url=u'/', base_kernel_url=u'/',
105 read_only=self.read_only,
154 read_only=self.read_only,
106 )
155 )
107
156
108
157
109 class LoginHandler(AuthenticatedHandler):
158 class LoginHandler(AuthenticatedHandler):
110
159
111 def get(self):
160 def get(self):
112 self.render('login.html',
161 self.render('login.html',
113 next=self.get_argument('next', default='/'),
162 next=self.get_argument('next', default='/'),
114 read_only=self.read_only,
163 read_only=self.read_only,
115 )
164 )
116
165
117 def post(self):
166 def post(self):
118 pwd = self.get_argument('password', default=u'')
167 pwd = self.get_argument('password', default=u'')
119 if self.application.password and pwd == self.application.password:
168 if self.application.password and pwd == self.application.password:
120 self.set_secure_cookie('username', str(uuid.uuid4()))
169 self.set_secure_cookie('username', str(uuid.uuid4()))
121 self.redirect(self.get_argument('next', default='/'))
170 self.redirect(self.get_argument('next', default='/'))
122
171
123
172
124 class NewHandler(AuthenticatedHandler):
173 class NewHandler(AuthenticatedHandler):
125
174
126 @web.authenticated
175 @web.authenticated
127 def get(self):
176 def get(self):
128 nbm = self.application.notebook_manager
177 nbm = self.application.notebook_manager
129 project = nbm.notebook_dir
178 project = nbm.notebook_dir
130 notebook_id = nbm.new_notebook()
179 notebook_id = nbm.new_notebook()
131 self.render(
180 self.render(
132 'notebook.html', project=project,
181 'notebook.html', project=project,
133 notebook_id=notebook_id,
182 notebook_id=notebook_id,
134 base_project_url=u'/', base_kernel_url=u'/',
183 base_project_url=u'/', base_kernel_url=u'/',
135 kill_kernel=False,
184 kill_kernel=False,
136 read_only=False,
185 read_only=False,
137 )
186 )
138
187
139
188
140 class NamedNotebookHandler(AuthenticatedHandler):
189 class NamedNotebookHandler(AuthenticatedHandler):
141
190
142 @authenticate_unless_readonly
191 @authenticate_unless_readonly
143 def get(self, notebook_id):
192 def get(self, notebook_id):
144 nbm = self.application.notebook_manager
193 nbm = self.application.notebook_manager
145 project = nbm.notebook_dir
194 project = nbm.notebook_dir
146 if not nbm.notebook_exists(notebook_id):
195 if not nbm.notebook_exists(notebook_id):
147 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
196 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
148
197
149 self.render(
198 self.render(
150 'notebook.html', project=project,
199 'notebook.html', project=project,
151 notebook_id=notebook_id,
200 notebook_id=notebook_id,
152 base_project_url=u'/', base_kernel_url=u'/',
201 base_project_url=u'/', base_kernel_url=u'/',
153 kill_kernel=False,
202 kill_kernel=False,
154 read_only=self.read_only,
203 read_only=self.read_only,
155 )
204 )
156
205
157
206
158 #-----------------------------------------------------------------------------
207 #-----------------------------------------------------------------------------
159 # Kernel handlers
208 # Kernel handlers
160 #-----------------------------------------------------------------------------
209 #-----------------------------------------------------------------------------
161
210
162
211
163 class MainKernelHandler(AuthenticatedHandler):
212 class MainKernelHandler(AuthenticatedHandler):
164
213
165 @web.authenticated
214 @web.authenticated
166 def get(self):
215 def get(self):
167 km = self.application.kernel_manager
216 km = self.application.kernel_manager
168 self.finish(jsonapi.dumps(km.kernel_ids))
217 self.finish(jsonapi.dumps(km.kernel_ids))
169
218
170 @web.authenticated
219 @web.authenticated
171 def post(self):
220 def post(self):
172 km = self.application.kernel_manager
221 km = self.application.kernel_manager
173 notebook_id = self.get_argument('notebook', default=None)
222 notebook_id = self.get_argument('notebook', default=None)
174 kernel_id = km.start_kernel(notebook_id)
223 kernel_id = km.start_kernel(notebook_id)
175 ws_url = self.application.ipython_app.get_ws_url()
224 ws_url = self.application.ipython_app.get_ws_url()
176 data = {'ws_url':ws_url,'kernel_id':kernel_id}
225 data = {'ws_url':ws_url,'kernel_id':kernel_id}
177 self.set_header('Location', '/'+kernel_id)
226 self.set_header('Location', '/'+kernel_id)
178 self.finish(jsonapi.dumps(data))
227 self.finish(jsonapi.dumps(data))
179
228
180
229
181 class KernelHandler(AuthenticatedHandler):
230 class KernelHandler(AuthenticatedHandler):
182
231
183 SUPPORTED_METHODS = ('DELETE')
232 SUPPORTED_METHODS = ('DELETE')
184
233
185 @web.authenticated
234 @web.authenticated
186 def delete(self, kernel_id):
235 def delete(self, kernel_id):
187 km = self.application.kernel_manager
236 km = self.application.kernel_manager
188 km.kill_kernel(kernel_id)
237 km.kill_kernel(kernel_id)
189 self.set_status(204)
238 self.set_status(204)
190 self.finish()
239 self.finish()
191
240
192
241
193 class KernelActionHandler(AuthenticatedHandler):
242 class KernelActionHandler(AuthenticatedHandler):
194
243
195 @web.authenticated
244 @web.authenticated
196 def post(self, kernel_id, action):
245 def post(self, kernel_id, action):
197 km = self.application.kernel_manager
246 km = self.application.kernel_manager
198 if action == 'interrupt':
247 if action == 'interrupt':
199 km.interrupt_kernel(kernel_id)
248 km.interrupt_kernel(kernel_id)
200 self.set_status(204)
249 self.set_status(204)
201 if action == 'restart':
250 if action == 'restart':
202 new_kernel_id = km.restart_kernel(kernel_id)
251 new_kernel_id = km.restart_kernel(kernel_id)
203 ws_url = self.application.ipython_app.get_ws_url()
252 ws_url = self.application.ipython_app.get_ws_url()
204 data = {'ws_url':ws_url,'kernel_id':new_kernel_id}
253 data = {'ws_url':ws_url,'kernel_id':new_kernel_id}
205 self.set_header('Location', '/'+new_kernel_id)
254 self.set_header('Location', '/'+new_kernel_id)
206 self.write(jsonapi.dumps(data))
255 self.write(jsonapi.dumps(data))
207 self.finish()
256 self.finish()
208
257
209
258
210 class ZMQStreamHandler(websocket.WebSocketHandler):
259 class ZMQStreamHandler(websocket.WebSocketHandler):
211
260
212 def _reserialize_reply(self, msg_list):
261 def _reserialize_reply(self, msg_list):
213 """Reserialize a reply message using JSON.
262 """Reserialize a reply message using JSON.
214
263
215 This takes the msg list from the ZMQ socket, unserializes it using
264 This takes the msg list from the ZMQ socket, unserializes it using
216 self.session and then serializes the result using JSON. This method
265 self.session and then serializes the result using JSON. This method
217 should be used by self._on_zmq_reply to build messages that can
266 should be used by self._on_zmq_reply to build messages that can
218 be sent back to the browser.
267 be sent back to the browser.
219 """
268 """
220 idents, msg_list = self.session.feed_identities(msg_list)
269 idents, msg_list = self.session.feed_identities(msg_list)
221 msg = self.session.unserialize(msg_list)
270 msg = self.session.unserialize(msg_list)
222 try:
271 try:
223 msg['header'].pop('date')
272 msg['header'].pop('date')
224 except KeyError:
273 except KeyError:
225 pass
274 pass
226 try:
275 try:
227 msg['parent_header'].pop('date')
276 msg['parent_header'].pop('date')
228 except KeyError:
277 except KeyError:
229 pass
278 pass
230 msg.pop('buffers')
279 msg.pop('buffers')
231 return jsonapi.dumps(msg)
280 return jsonapi.dumps(msg)
232
281
233 def _on_zmq_reply(self, msg_list):
282 def _on_zmq_reply(self, msg_list):
234 try:
283 try:
235 msg = self._reserialize_reply(msg_list)
284 msg = self._reserialize_reply(msg_list)
236 except:
285 except:
237 self.application.log.critical("Malformed message: %r" % msg_list)
286 self.application.log.critical("Malformed message: %r" % msg_list)
238 else:
287 else:
239 self.write_message(msg)
288 self.write_message(msg)
240
289
241
290
242 class AuthenticatedZMQStreamHandler(ZMQStreamHandler):
291 class AuthenticatedZMQStreamHandler(ZMQStreamHandler):
243
292
244 def open(self, kernel_id):
293 def open(self, kernel_id):
245 self.kernel_id = kernel_id.decode('ascii')
294 self.kernel_id = kernel_id.decode('ascii')
246 try:
295 try:
247 cfg = self.application.ipython_app.config
296 cfg = self.application.ipython_app.config
248 except AttributeError:
297 except AttributeError:
249 # protect from the case where this is run from something other than
298 # protect from the case where this is run from something other than
250 # the notebook app:
299 # the notebook app:
251 cfg = None
300 cfg = None
252 self.session = Session(config=cfg)
301 self.session = Session(config=cfg)
253 self.save_on_message = self.on_message
302 self.save_on_message = self.on_message
254 self.on_message = self.on_first_message
303 self.on_message = self.on_first_message
255
304
256 def get_current_user(self):
305 def get_current_user(self):
257 user_id = self.get_secure_cookie("username")
306 user_id = self.get_secure_cookie("username")
258 if user_id == '' or (user_id is None and not self.application.password):
307 if user_id == '' or (user_id is None and not self.application.password):
259 user_id = 'anonymous'
308 user_id = 'anonymous'
260 return user_id
309 return user_id
261
310
262 def _inject_cookie_message(self, msg):
311 def _inject_cookie_message(self, msg):
263 """Inject the first message, which is the document cookie,
312 """Inject the first message, which is the document cookie,
264 for authentication."""
313 for authentication."""
265 if isinstance(msg, unicode):
314 if isinstance(msg, unicode):
266 # Cookie can't constructor doesn't accept unicode strings for some reason
315 # Cookie can't constructor doesn't accept unicode strings for some reason
267 msg = msg.encode('utf8', 'replace')
316 msg = msg.encode('utf8', 'replace')
268 try:
317 try:
269 self.request._cookies = Cookie.SimpleCookie(msg)
318 self.request._cookies = Cookie.SimpleCookie(msg)
270 except:
319 except:
271 logging.warn("couldn't parse cookie string: %s",msg, exc_info=True)
320 logging.warn("couldn't parse cookie string: %s",msg, exc_info=True)
272
321
273 def on_first_message(self, msg):
322 def on_first_message(self, msg):
274 self._inject_cookie_message(msg)
323 self._inject_cookie_message(msg)
275 if self.get_current_user() is None:
324 if self.get_current_user() is None:
276 logging.warn("Couldn't authenticate WebSocket connection")
325 logging.warn("Couldn't authenticate WebSocket connection")
277 raise web.HTTPError(403)
326 raise web.HTTPError(403)
278 self.on_message = self.save_on_message
327 self.on_message = self.save_on_message
279
328
280
329
281 class IOPubHandler(AuthenticatedZMQStreamHandler):
330 class IOPubHandler(AuthenticatedZMQStreamHandler):
282
331
283 def initialize(self, *args, **kwargs):
332 def initialize(self, *args, **kwargs):
284 self._kernel_alive = True
333 self._kernel_alive = True
285 self._beating = False
334 self._beating = False
286 self.iopub_stream = None
335 self.iopub_stream = None
287 self.hb_stream = None
336 self.hb_stream = None
288
337
289 def on_first_message(self, msg):
338 def on_first_message(self, msg):
290 try:
339 try:
291 super(IOPubHandler, self).on_first_message(msg)
340 super(IOPubHandler, self).on_first_message(msg)
292 except web.HTTPError:
341 except web.HTTPError:
293 self.close()
342 self.close()
294 return
343 return
295 km = self.application.kernel_manager
344 km = self.application.kernel_manager
296 self.time_to_dead = km.time_to_dead
345 self.time_to_dead = km.time_to_dead
297 kernel_id = self.kernel_id
346 kernel_id = self.kernel_id
298 try:
347 try:
299 self.iopub_stream = km.create_iopub_stream(kernel_id)
348 self.iopub_stream = km.create_iopub_stream(kernel_id)
300 self.hb_stream = km.create_hb_stream(kernel_id)
349 self.hb_stream = km.create_hb_stream(kernel_id)
301 except web.HTTPError:
350 except web.HTTPError:
302 # WebSockets don't response to traditional error codes so we
351 # WebSockets don't response to traditional error codes so we
303 # close the connection.
352 # close the connection.
304 if not self.stream.closed():
353 if not self.stream.closed():
305 self.stream.close()
354 self.stream.close()
306 self.close()
355 self.close()
307 else:
356 else:
308 self.iopub_stream.on_recv(self._on_zmq_reply)
357 self.iopub_stream.on_recv(self._on_zmq_reply)
309 self.start_hb(self.kernel_died)
358 self.start_hb(self.kernel_died)
310
359
311 def on_message(self, msg):
360 def on_message(self, msg):
312 pass
361 pass
313
362
314 def on_close(self):
363 def on_close(self):
315 # This method can be called twice, once by self.kernel_died and once
364 # This method can be called twice, once by self.kernel_died and once
316 # from the WebSocket close event. If the WebSocket connection is
365 # from the WebSocket close event. If the WebSocket connection is
317 # closed before the ZMQ streams are setup, they could be None.
366 # closed before the ZMQ streams are setup, they could be None.
318 self.stop_hb()
367 self.stop_hb()
319 if self.iopub_stream is not None and not self.iopub_stream.closed():
368 if self.iopub_stream is not None and not self.iopub_stream.closed():
320 self.iopub_stream.on_recv(None)
369 self.iopub_stream.on_recv(None)
321 self.iopub_stream.close()
370 self.iopub_stream.close()
322 if self.hb_stream is not None and not self.hb_stream.closed():
371 if self.hb_stream is not None and not self.hb_stream.closed():
323 self.hb_stream.close()
372 self.hb_stream.close()
324
373
325 def start_hb(self, callback):
374 def start_hb(self, callback):
326 """Start the heartbeating and call the callback if the kernel dies."""
375 """Start the heartbeating and call the callback if the kernel dies."""
327 if not self._beating:
376 if not self._beating:
328 self._kernel_alive = True
377 self._kernel_alive = True
329
378
330 def ping_or_dead():
379 def ping_or_dead():
331 if self._kernel_alive:
380 if self._kernel_alive:
332 self._kernel_alive = False
381 self._kernel_alive = False
333 self.hb_stream.send(b'ping')
382 self.hb_stream.send(b'ping')
334 else:
383 else:
335 try:
384 try:
336 callback()
385 callback()
337 except:
386 except:
338 pass
387 pass
339 finally:
388 finally:
340 self._hb_periodic_callback.stop()
389 self._hb_periodic_callback.stop()
341
390
342 def beat_received(msg):
391 def beat_received(msg):
343 self._kernel_alive = True
392 self._kernel_alive = True
344
393
345 self.hb_stream.on_recv(beat_received)
394 self.hb_stream.on_recv(beat_received)
346 self._hb_periodic_callback = ioloop.PeriodicCallback(ping_or_dead, self.time_to_dead*1000)
395 self._hb_periodic_callback = ioloop.PeriodicCallback(ping_or_dead, self.time_to_dead*1000)
347 self._hb_periodic_callback.start()
396 self._hb_periodic_callback.start()
348 self._beating= True
397 self._beating= True
349
398
350 def stop_hb(self):
399 def stop_hb(self):
351 """Stop the heartbeating and cancel all related callbacks."""
400 """Stop the heartbeating and cancel all related callbacks."""
352 if self._beating:
401 if self._beating:
353 self._hb_periodic_callback.stop()
402 self._hb_periodic_callback.stop()
354 if not self.hb_stream.closed():
403 if not self.hb_stream.closed():
355 self.hb_stream.on_recv(None)
404 self.hb_stream.on_recv(None)
356
405
357 def kernel_died(self):
406 def kernel_died(self):
358 self.application.kernel_manager.delete_mapping_for_kernel(self.kernel_id)
407 self.application.kernel_manager.delete_mapping_for_kernel(self.kernel_id)
359 self.write_message(
408 self.write_message(
360 {'header': {'msg_type': 'status'},
409 {'header': {'msg_type': 'status'},
361 'parent_header': {},
410 'parent_header': {},
362 'content': {'execution_state':'dead'}
411 'content': {'execution_state':'dead'}
363 }
412 }
364 )
413 )
365 self.on_close()
414 self.on_close()
366
415
367
416
368 class ShellHandler(AuthenticatedZMQStreamHandler):
417 class ShellHandler(AuthenticatedZMQStreamHandler):
369
418
370 def initialize(self, *args, **kwargs):
419 def initialize(self, *args, **kwargs):
371 self.shell_stream = None
420 self.shell_stream = None
372
421
373 def on_first_message(self, msg):
422 def on_first_message(self, msg):
374 try:
423 try:
375 super(ShellHandler, self).on_first_message(msg)
424 super(ShellHandler, self).on_first_message(msg)
376 except web.HTTPError:
425 except web.HTTPError:
377 self.close()
426 self.close()
378 return
427 return
379 km = self.application.kernel_manager
428 km = self.application.kernel_manager
380 self.max_msg_size = km.max_msg_size
429 self.max_msg_size = km.max_msg_size
381 kernel_id = self.kernel_id
430 kernel_id = self.kernel_id
382 try:
431 try:
383 self.shell_stream = km.create_shell_stream(kernel_id)
432 self.shell_stream = km.create_shell_stream(kernel_id)
384 except web.HTTPError:
433 except web.HTTPError:
385 # WebSockets don't response to traditional error codes so we
434 # WebSockets don't response to traditional error codes so we
386 # close the connection.
435 # close the connection.
387 if not self.stream.closed():
436 if not self.stream.closed():
388 self.stream.close()
437 self.stream.close()
389 self.close()
438 self.close()
390 else:
439 else:
391 self.shell_stream.on_recv(self._on_zmq_reply)
440 self.shell_stream.on_recv(self._on_zmq_reply)
392
441
393 def on_message(self, msg):
442 def on_message(self, msg):
394 if len(msg) < self.max_msg_size:
443 if len(msg) < self.max_msg_size:
395 msg = jsonapi.loads(msg)
444 msg = jsonapi.loads(msg)
396 self.session.send(self.shell_stream, msg)
445 self.session.send(self.shell_stream, msg)
397
446
398 def on_close(self):
447 def on_close(self):
399 # Make sure the stream exists and is not already closed.
448 # Make sure the stream exists and is not already closed.
400 if self.shell_stream is not None and not self.shell_stream.closed():
449 if self.shell_stream is not None and not self.shell_stream.closed():
401 self.shell_stream.close()
450 self.shell_stream.close()
402
451
403
452
404 #-----------------------------------------------------------------------------
453 #-----------------------------------------------------------------------------
405 # Notebook web service handlers
454 # Notebook web service handlers
406 #-----------------------------------------------------------------------------
455 #-----------------------------------------------------------------------------
407
456
408 class NotebookRootHandler(AuthenticatedHandler):
457 class NotebookRootHandler(AuthenticatedHandler):
409
458
410 @authenticate_unless_readonly
459 @authenticate_unless_readonly
411 def get(self):
460 def get(self):
412
461
413 nbm = self.application.notebook_manager
462 nbm = self.application.notebook_manager
414 files = nbm.list_notebooks()
463 files = nbm.list_notebooks()
415 self.finish(jsonapi.dumps(files))
464 self.finish(jsonapi.dumps(files))
416
465
417 @web.authenticated
466 @web.authenticated
418 def post(self):
467 def post(self):
419 nbm = self.application.notebook_manager
468 nbm = self.application.notebook_manager
420 body = self.request.body.strip()
469 body = self.request.body.strip()
421 format = self.get_argument('format', default='json')
470 format = self.get_argument('format', default='json')
422 name = self.get_argument('name', default=None)
471 name = self.get_argument('name', default=None)
423 if body:
472 if body:
424 notebook_id = nbm.save_new_notebook(body, name=name, format=format)
473 notebook_id = nbm.save_new_notebook(body, name=name, format=format)
425 else:
474 else:
426 notebook_id = nbm.new_notebook()
475 notebook_id = nbm.new_notebook()
427 self.set_header('Location', '/'+notebook_id)
476 self.set_header('Location', '/'+notebook_id)
428 self.finish(jsonapi.dumps(notebook_id))
477 self.finish(jsonapi.dumps(notebook_id))
429
478
430
479
431 class NotebookHandler(AuthenticatedHandler):
480 class NotebookHandler(AuthenticatedHandler):
432
481
433 SUPPORTED_METHODS = ('GET', 'PUT', 'DELETE')
482 SUPPORTED_METHODS = ('GET', 'PUT', 'DELETE')
434
483
435 @authenticate_unless_readonly
484 @authenticate_unless_readonly
436 def get(self, notebook_id):
485 def get(self, notebook_id):
437 nbm = self.application.notebook_manager
486 nbm = self.application.notebook_manager
438 format = self.get_argument('format', default='json')
487 format = self.get_argument('format', default='json')
439 last_mod, name, data = nbm.get_notebook(notebook_id, format)
488 last_mod, name, data = nbm.get_notebook(notebook_id, format)
440
489
441 if format == u'json':
490 if format == u'json':
442 self.set_header('Content-Type', 'application/json')
491 self.set_header('Content-Type', 'application/json')
443 self.set_header('Content-Disposition','attachment; filename="%s.ipynb"' % name)
492 self.set_header('Content-Disposition','attachment; filename="%s.ipynb"' % name)
444 elif format == u'py':
493 elif format == u'py':
445 self.set_header('Content-Type', 'application/x-python')
494 self.set_header('Content-Type', 'application/x-python')
446 self.set_header('Content-Disposition','attachment; filename="%s.py"' % name)
495 self.set_header('Content-Disposition','attachment; filename="%s.py"' % name)
447 self.set_header('Last-Modified', last_mod)
496 self.set_header('Last-Modified', last_mod)
448 self.finish(data)
497 self.finish(data)
449
498
450 @web.authenticated
499 @web.authenticated
451 def put(self, notebook_id):
500 def put(self, notebook_id):
452 nbm = self.application.notebook_manager
501 nbm = self.application.notebook_manager
453 format = self.get_argument('format', default='json')
502 format = self.get_argument('format', default='json')
454 name = self.get_argument('name', default=None)
503 name = self.get_argument('name', default=None)
455 nbm.save_notebook(notebook_id, self.request.body, name=name, format=format)
504 nbm.save_notebook(notebook_id, self.request.body, name=name, format=format)
456 self.set_status(204)
505 self.set_status(204)
457 self.finish()
506 self.finish()
458
507
459 @web.authenticated
508 @web.authenticated
460 def delete(self, notebook_id):
509 def delete(self, notebook_id):
461 nbm = self.application.notebook_manager
510 nbm = self.application.notebook_manager
462 nbm.delete_notebook(notebook_id)
511 nbm.delete_notebook(notebook_id)
463 self.set_status(204)
512 self.set_status(204)
464 self.finish()
513 self.finish()
465
514
466 #-----------------------------------------------------------------------------
515 #-----------------------------------------------------------------------------
467 # RST web service handlers
516 # RST web service handlers
468 #-----------------------------------------------------------------------------
517 #-----------------------------------------------------------------------------
469
518
470
519
471 class RSTHandler(AuthenticatedHandler):
520 class RSTHandler(AuthenticatedHandler):
472
521
473 @web.authenticated
522 @web.authenticated
474 def post(self):
523 def post(self):
475 if publish_string is None:
524 if publish_string is None:
476 raise web.HTTPError(503, u'docutils not available')
525 raise web.HTTPError(503, u'docutils not available')
477 body = self.request.body.strip()
526 body = self.request.body.strip()
478 source = body
527 source = body
479 # template_path=os.path.join(os.path.dirname(__file__), u'templates', u'rst_template.html')
528 # template_path=os.path.join(os.path.dirname(__file__), u'templates', u'rst_template.html')
480 defaults = {'file_insertion_enabled': 0,
529 defaults = {'file_insertion_enabled': 0,
481 'raw_enabled': 0,
530 'raw_enabled': 0,
482 '_disable_config': 1,
531 '_disable_config': 1,
483 'stylesheet_path': 0
532 'stylesheet_path': 0
484 # 'template': template_path
533 # 'template': template_path
485 }
534 }
486 try:
535 try:
487 html = publish_string(source, writer_name='html',
536 html = publish_string(source, writer_name='html',
488 settings_overrides=defaults
537 settings_overrides=defaults
489 )
538 )
490 except:
539 except:
491 raise web.HTTPError(400, u'Invalid RST')
540 raise web.HTTPError(400, u'Invalid RST')
492 print html
541 print html
493 self.set_header('Content-Type', 'text/html')
542 self.set_header('Content-Type', 'text/html')
494 self.finish(html)
543 self.finish(html)
495
544
496
545
General Comments 0
You need to be logged in to leave comments. Login now