##// END OF EJS Templates
Date is properly removed from JSON reply before WebSocket forward....
Date is properly removed from JSON reply before WebSocket forward. * Both the header and parent_header have a date field that cannot be json serialized. This field is just removed for now, but in the future, we will covert the date to a ISO8601 field. * Better error handling around this code has also been added to prevent the server from crashing due to malformed messages.

File last commit:

r4561:b796f247
r4561:b796f247
Show More
handlers.py
296 lines | 9.5 KiB | text/x-python | PythonLexer
"""Tornado handlers for the notebook."""
#-----------------------------------------------------------------------------
# Imports
#-----------------------------------------------------------------------------
from tornado import web
from tornado import websocket
from zmq.eventloop import ioloop
from zmq.utils import jsonapi
from IPython.zmq.session import Session
try:
from docutils.core import publish_string
except ImportError:
publish_string = None
#-----------------------------------------------------------------------------
# Top-level handlers
#-----------------------------------------------------------------------------
class NBBrowserHandler(web.RequestHandler):
def get(self):
nbm = self.application.notebook_manager
project = nbm.notebook_dir
self.render('nbbrowser.html', project=project)
class NewHandler(web.RequestHandler):
def get(self):
notebook_id = self.application.notebook_manager.new_notebook()
self.render('notebook.html', notebook_id=notebook_id)
class NamedNotebookHandler(web.RequestHandler):
def get(self, notebook_id):
nbm = self.application.notebook_manager
if not nbm.notebook_exists(notebook_id):
raise web.HTTPError(404)
self.render('notebook.html', notebook_id=notebook_id)
#-----------------------------------------------------------------------------
# Kernel handlers
#-----------------------------------------------------------------------------
class MainKernelHandler(web.RequestHandler):
def get(self):
km = self.application.kernel_manager
self.finish(jsonapi.dumps(km.kernel_ids))
def post(self):
km = self.application.kernel_manager
notebook_id = self.get_argument('notebook', default=None)
kernel_id = km.start_kernel(notebook_id)
self.set_header('Location', '/'+kernel_id)
self.finish(jsonapi.dumps(kernel_id))
class KernelHandler(web.RequestHandler):
SUPPORTED_METHODS = ('DELETE')
def delete(self, kernel_id):
km = self.application.kernel_manager
km.kill_kernel(kernel_id)
self.set_status(204)
self.finish()
class KernelActionHandler(web.RequestHandler):
def post(self, kernel_id, action):
km = self.application.kernel_manager
if action == 'interrupt':
km.interrupt_kernel(kernel_id)
self.set_status(204)
if action == 'restart':
new_kernel_id = km.restart_kernel(kernel_id)
self.write(jsonapi.dumps(new_kernel_id))
self.finish()
class ZMQStreamHandler(websocket.WebSocketHandler):
def _reserialize_reply(self, msg_list):
"""Reserialize a reply message using JSON.
This takes the msg list from the ZMQ socket, unserializes it using
self.session and then serializes the result using JSON. This method
should be used by self._on_zmq_reply to build messages that can
be sent back to the browser.
"""
idents, msg_list = self.session.feed_identities(msg_list)
msg = self.session.unserialize(msg_list)
try:
msg['header'].pop('date')
except KeyError:
pass
try:
msg['parent_header'].pop('date')
except KeyError:
pass
msg.pop('buffers')
return jsonapi.dumps(msg)
def _on_zmq_reply(self, msg_list):
try:
msg = self._reserialize_reply(msg_list)
except:
self.application.kernel_manager.log.critical("Malformed message: %r" % msg_list)
else:
self.write_message(msg)
class IOPubHandler(ZMQStreamHandler):
def initialize(self, *args, **kwargs):
self._kernel_alive = True
self._beating = False
def open(self, kernel_id):
km = self.application.kernel_manager
self.kernel_id = kernel_id
self.session = Session()
self.time_to_dead = km.time_to_dead
self.iopub_stream = km.create_iopub_stream(kernel_id)
self.hb_stream = km.create_hb_stream(kernel_id)
self.iopub_stream.on_recv(self._on_zmq_reply)
self.start_hb(self.kernel_died)
def on_close(self):
self.stop_hb()
self.iopub_stream.close()
self.hb_stream.close()
def start_hb(self, callback):
"""Start the heartbeating and call the callback if the kernel dies."""
if not self._beating:
self._kernel_alive = True
def ping_or_dead():
if self._kernel_alive:
self._kernel_alive = False
self.hb_stream.send(b'ping')
else:
try:
callback()
except:
pass
finally:
self._hb_periodic_callback.stop()
def beat_received(msg):
self._kernel_alive = True
self.hb_stream.on_recv(beat_received)
self._hb_periodic_callback = ioloop.PeriodicCallback(ping_or_dead, self.time_to_dead*1000)
self._hb_periodic_callback.start()
self._beating= True
def stop_hb(self):
"""Stop the heartbeating and cancel all related callbacks."""
if self._beating:
self._hb_periodic_callback.stop()
if not self.hb_stream.closed():
self.hb_stream.on_recv(None)
def kernel_died(self):
self.write_message(
{'header': {'msg_type': 'status'},
'parent_header': {},
'content': {'execution_state':'dead'}
}
)
self.on_close()
class ShellHandler(ZMQStreamHandler):
def initialize(self, *args, **kwargs):
pass
def open(self, kernel_id):
km = self.application.kernel_manager
self.max_msg_size = km.max_msg_size
self.kernel_id = kernel_id
self.session = Session()
self.shell_stream = self.application.kernel_manager.create_shell_stream(kernel_id)
self.shell_stream.on_recv(self._on_zmq_reply)
def on_message(self, msg):
if len(msg) < self.max_msg_size:
msg = jsonapi.loads(msg)
self.session.send(self.shell_stream, msg)
def on_close(self):
self.shell_stream.close()
#-----------------------------------------------------------------------------
# Notebook web service handlers
#-----------------------------------------------------------------------------
class NotebookRootHandler(web.RequestHandler):
def get(self):
nbm = self.application.notebook_manager
files = nbm.list_notebooks()
self.finish(jsonapi.dumps(files))
def post(self):
nbm = self.application.notebook_manager
body = self.request.body.strip()
format = self.get_argument('format', default='json')
name = self.get_argument('name', default=None)
if body:
notebook_id = nbm.save_new_notebook(body, name=name, format=format)
else:
notebook_id = nbm.new_notebook()
self.set_header('Location', '/'+notebook_id)
self.finish(jsonapi.dumps(notebook_id))
class NotebookHandler(web.RequestHandler):
SUPPORTED_METHODS = ('GET', 'PUT', 'DELETE')
def get(self, notebook_id):
nbm = self.application.notebook_manager
format = self.get_argument('format', default='json')
last_mod, name, data = nbm.get_notebook(notebook_id, format)
if format == u'json':
self.set_header('Content-Type', 'application/json')
self.set_header('Content-Disposition','attachment; filename="%s.json"' % name)
elif format == u'xml':
self.set_header('Content-Type', 'application/xml')
self.set_header('Content-Disposition','attachment; filename="%s.ipynb"' % name)
elif format == u'py':
self.set_header('Content-Type', 'application/x-python')
self.set_header('Content-Disposition','attachment; filename="%s.py"' % name)
self.set_header('Last-Modified', last_mod)
self.finish(data)
def put(self, notebook_id):
nbm = self.application.notebook_manager
format = self.get_argument('format', default='json')
name = self.get_argument('name', default=None)
nbm.save_notebook(notebook_id, self.request.body, name=name, format=format)
self.set_status(204)
self.finish()
def delete(self, notebook_id):
nbm = self.application.notebook_manager
nbm.delete_notebook(notebook_id)
self.set_status(204)
self.finish()
#-----------------------------------------------------------------------------
# RST web service handlers
#-----------------------------------------------------------------------------
class RSTHandler(web.RequestHandler):
def post(self):
if publish_string is None:
raise web.HTTPError(503)
body = self.request.body.strip()
source = body
# template_path=os.path.join(os.path.dirname(__file__), u'templates', u'rst_template.html')
defaults = {'file_insertion_enabled': 0,
'raw_enabled': 0,
'_disable_config': 1,
'stylesheet_path': 0
# 'template': template_path
}
try:
html = publish_string(source, writer_name='html',
settings_overrides=defaults
)
except:
raise web.HTTPError(400)
print html
self.set_header('Content-Type', 'text/html')
self.finish(html)