##// END OF EJS Templates
Authenticate all notebook requests (except websockets)...
Authenticate all notebook requests (except websockets) * BaseHandler renamed AuthenticatedHandler * also clears cookies if invalid, to prevent repeated 'Invalid cookie signature' warning messages.

File last commit:

r4706:4d5cfe6c
r4706:4d5cfe6c
Show More
handlers.py
369 lines | 12.5 KiB | text/x-python | PythonLexer
"""Tornado handlers for the notebook.
Authors:
* Brian Granger
"""
#-----------------------------------------------------------------------------
# Copyright (C) 2008-2011 The IPython Development Team
#
# Distributed under the terms of the BSD License. The full license is in
# the file COPYING, distributed as part of this software.
#-----------------------------------------------------------------------------
#-----------------------------------------------------------------------------
# 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 AuthenticatedHandler(web.RequestHandler):
"""A RequestHandler with an authenticated user."""
def get_current_user(self):
password = self.get_secure_cookie("password")
if password is None:
# cookie doesn't exist, or is invalid. Clear to prevent repeated
# 'Invalid cookie signature' warnings.
self.clear_cookie('password')
self.clear_cookie("user_id")
if self.application.password and self.application.password != password:
return None
return self.get_secure_cookie("user") or 'anonymous'
class NBBrowserHandler(AuthenticatedHandler):
@web.authenticated
def get(self):
nbm = self.application.notebook_manager
project = nbm.notebook_dir
self.render('nbbrowser.html', project=project)
class LoginHandler(AuthenticatedHandler):
def get(self):
user_id = self.get_secure_cookie("user") or ''
self.render('login.html', user_id=user_id)
def post(self):
self.set_secure_cookie("user", self.get_argument("name", default=u''))
self.set_secure_cookie("password", self.get_argument("password", default=u''))
url = self.get_argument("next", default="/")
self.redirect(url)
class NewHandler(AuthenticatedHandler):
@web.authenticated
def get(self):
notebook_id = self.application.notebook_manager.new_notebook()
self.render('notebook.html', notebook_id=notebook_id)
class NamedNotebookHandler(AuthenticatedHandler):
@web.authenticated
def get(self, notebook_id):
nbm = self.application.notebook_manager
if not nbm.notebook_exists(notebook_id):
raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
self.render('notebook.html', notebook_id=notebook_id)
#-----------------------------------------------------------------------------
# Kernel handlers
#-----------------------------------------------------------------------------
class MainKernelHandler(AuthenticatedHandler):
@web.authenticated
def get(self):
km = self.application.kernel_manager
self.finish(jsonapi.dumps(km.kernel_ids))
@web.authenticated
def post(self):
km = self.application.kernel_manager
notebook_id = self.get_argument('notebook', default=None)
kernel_id = km.start_kernel(notebook_id)
ws_url = self.application.ipython_app.get_ws_url()
data = {'ws_url':ws_url,'kernel_id':kernel_id}
self.set_header('Location', '/'+kernel_id)
self.finish(jsonapi.dumps(data))
class KernelHandler(AuthenticatedHandler):
SUPPORTED_METHODS = ('DELETE')
@web.authenticated
def delete(self, kernel_id):
km = self.application.kernel_manager
km.kill_kernel(kernel_id)
self.set_status(204)
self.finish()
class KernelActionHandler(AuthenticatedHandler):
@web.authenticated
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)
ws_url = self.application.ipython_app.get_ws_url()
data = {'ws_url':ws_url,'kernel_id':new_kernel_id}
self.set_header('Location', '/'+new_kernel_id)
self.write(jsonapi.dumps(data))
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
self.iopub_stream = None
self.hb_stream = None
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
try:
self.iopub_stream = km.create_iopub_stream(kernel_id)
self.hb_stream = km.create_hb_stream(kernel_id)
except web.HTTPError:
# WebSockets don't response to traditional error codes so we
# close the connection.
if not self.stream.closed():
self.stream.close()
else:
self.iopub_stream.on_recv(self._on_zmq_reply)
self.start_hb(self.kernel_died)
def on_close(self):
# This method can be called twice, once by self.kernel_died and once
# from the WebSocket close event. If the WebSocket connection is
# closed before the ZMQ streams are setup, they could be None.
self.stop_hb()
if self.iopub_stream is not None and not self.iopub_stream.closed():
self.iopub_stream.on_recv(None)
self.iopub_stream.close()
if self.hb_stream is not None and not self.hb_stream.closed():
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.application.kernel_manager.delete_mapping_for_kernel(self.kernel_id)
self.write_message(
{'header': {'msg_type': 'status'},
'parent_header': {},
'content': {'execution_state':'dead'}
}
)
self.on_close()
class ShellHandler(ZMQStreamHandler):
def initialize(self, *args, **kwargs):
self.shell_stream = None
def open(self, kernel_id):
km = self.application.kernel_manager
self.max_msg_size = km.max_msg_size
self.kernel_id = kernel_id
try:
self.shell_stream = km.create_shell_stream(kernel_id)
except web.HTTPError:
# WebSockets don't response to traditional error codes so we
# close the connection.
if not self.stream.closed():
self.stream.close()
else:
self.session = Session()
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):
# Make sure the stream exists and is not already closed.
if self.shell_stream is not None and not self.shell_stream.closed():
self.shell_stream.close()
#-----------------------------------------------------------------------------
# Notebook web service handlers
#-----------------------------------------------------------------------------
class NotebookRootHandler(AuthenticatedHandler):
@web.authenticated
def get(self):
nbm = self.application.notebook_manager
files = nbm.list_notebooks()
self.finish(jsonapi.dumps(files))
@web.authenticated
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(AuthenticatedHandler):
SUPPORTED_METHODS = ('GET', 'PUT', 'DELETE')
@web.authenticated
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.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)
@web.authenticated
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()
@web.authenticated
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(AuthenticatedHandler):
@web.authenticated
def post(self):
if publish_string is None:
raise web.HTTPError(503, u'docutils not available')
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, u'Invalid RST')
print html
self.set_header('Content-Type', 'text/html')
self.finish(html)