##// END OF EJS Templates
only save on close if autosaving
only save on close if autosaving

File last commit:

r10499:c8b69557
r10511:27b173a7
Show More
handlers.py
928 lines | 30.3 KiB | text/x-python | PythonLexer
Brian E. Granger
More review changes....
r4609 """Tornado handlers for the notebook.
Authors:
* Brian Granger
"""
Brian E. Granger
Work to adapt routers to new Session message protocol.
r4346
#-----------------------------------------------------------------------------
Brian E. Granger
More review changes....
r4609 # 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.
Brian E. Granger
Work to adapt routers to new Session message protocol.
r4346 #-----------------------------------------------------------------------------
Brian E. Granger
More review changes....
r4609 #-----------------------------------------------------------------------------
# Imports
#-----------------------------------------------------------------------------
Brian Granger
Work on the server side of the html notebook.
r4297
MinRK
authenticate Websockets with the session cookie...
r4707 import Cookie
MinRK
add FileFindHandler for serving static files from a search path
r7922 import datetime
import email.utils
import hashlib
import logging
import mimetypes
import os
import stat
MinRK
add missing methods in FindFileHandler for tornado < 2.2.0 compat
r8016 import threading
MinRK
add first_beat delay to notebook heartbeats...
r5812 import time
MinRK
don't present meaningless username option in notebook...
r5101 import uuid
MinRK
authenticate Websockets with the session cookie...
r4707
Cameron Bates
Use the correct tornado import as suggested by @ivanov
r8907 from tornado.escape import url_escape
Brian Granger
Work on the server side of the html notebook.
r4297 from tornado import web
from tornado import websocket
MinRK
hook up proper loggers...
r10360 try:
from tornado.log import app_log
except ImportError:
app_log = logging.getLogger()
Brian E. Granger
Major refactor of kernel connection management in the notebook....
r4545 from zmq.eventloop import ioloop
from zmq.utils import jsonapi
MinRK
hook up proper loggers...
r10360 from IPython.config import Application
MinRK
Allow notebook server to run in read-only mode...
r5191 from IPython.external.decorator import decorator
MinRK
mv IPython.zmq to IPython.kernel.zmq
r9372 from IPython.kernel.zmq.session import Session
Stefan van der Walt
Integrate hashed passwords into the notebook.
r5321 from IPython.lib.security import passwd_check
MinRK
fix date objects in _reserialize_reply
r6870 from IPython.utils.jsonutil import date_default
MinRK
add FileFindHandler for serving static files from a search path
r7922 from IPython.utils.path import filefind
Mikhail Korobov
P3K: fix cookie parsing under Python 3.x (+ duplicate import is removed)
r9110 from IPython.utils.py3compat import PY3
Brian E. Granger
Major refactor of kernel connection management in the notebook....
r4545
Brian E. Granger
Starting work on a Markdown cell.
r4507 try:
from docutils.core import publish_string
except ImportError:
publish_string = None
Fernando Perez
Monkeypatch Tornado 2.1.1 so it works with Google Chrome 16....
r5240 #-----------------------------------------------------------------------------
Fernando Perez
Update version check to work with tornado 2.1.0 or 2.1.1
r5241 # Monkeypatch for Tornado <= 2.1.1 - Remove when no longer necessary!
Fernando Perez
Monkeypatch Tornado 2.1.1 so it works with Google Chrome 16....
r5240 #-----------------------------------------------------------------------------
# Google Chrome, as of release 16, changed its websocket protocol number. The
# parts tornado cares about haven't really changed, so it's OK to continue
# accepting Chrome connections, but as of Tornado 2.1.1 (the currently released
# version as of Oct 30/2011) the version check fails, see the issue report:
# https://github.com/facebook/tornado/issues/385
# This issue has been fixed in Tornado post 2.1.1:
# https://github.com/facebook/tornado/commit/84d7b458f956727c3b0d6710
# Here we manually apply the same patch as above so that users of IPython can
# continue to work with an officially released Tornado. We make the
# monkeypatch version check as narrow as possible to limit its effects; once
# Tornado 2.1.1 is no longer found in the wild we'll delete this code.
import tornado
Fernando Perez
Update version check to work with tornado 2.1.0 or 2.1.1
r5241 if tornado.version_info <= (2,1,1):
Fernando Perez
Monkeypatch Tornado 2.1.1 so it works with Google Chrome 16....
r5240
def _execute(self, transforms, *args, **kwargs):
from tornado.websocket import WebSocketProtocol8, WebSocketProtocol76
self.open_args = args
self.open_kwargs = kwargs
# The difference between version 8 and 13 is that in 8 the
# client sends a "Sec-Websocket-Origin" header and in 13 it's
# simply "Origin".
if self.request.headers.get("Sec-WebSocket-Version") in ("7", "8", "13"):
self.ws_connection = WebSocketProtocol8(self)
self.ws_connection.accept_connection()
elif self.request.headers.get("Sec-WebSocket-Version"):
self.stream.write(tornado.escape.utf8(
"HTTP/1.1 426 Upgrade Required\r\n"
"Sec-WebSocket-Version: 8\r\n\r\n"))
self.stream.close()
else:
self.ws_connection = WebSocketProtocol76(self)
self.ws_connection.accept_connection()
Brian E. Granger
Massive work on the notebook document format....
r4484
Fernando Perez
Monkeypatch Tornado 2.1.1 so it works with Google Chrome 16....
r5240 websocket.WebSocketHandler._execute = _execute
del _execute
MinRK
hook up proper loggers...
r10360
MinRK
Allow notebook server to run in read-only mode...
r5191 #-----------------------------------------------------------------------------
# Decorator for disabling read-only handlers
#-----------------------------------------------------------------------------
@decorator
def not_if_readonly(f, self, *args, **kwargs):
MinRK
cleanup IPython handler settings...
r10355 if self.settings.get('read_only', False):
MinRK
Allow notebook server to run in read-only mode...
r5191 raise web.HTTPError(403, "Notebook server is read-only")
else:
return f(self, *args, **kwargs)
Brian E. Granger
Major refactor of kernel connection management in the notebook....
r4545
MinRK
add read-only view for notebooks...
r5200 @decorator
def authenticate_unless_readonly(f, self, *args, **kwargs):
"""authenticate this page *unless* readonly view is active.
In read-only mode, the notebook list and print view should
be accessible without authentication.
"""
@web.authenticated
def auth_f(self, *args, **kwargs):
return f(self, *args, **kwargs)
MinRK
authenticate local file access...
r5824
MinRK
cleanup IPython handler settings...
r10355 if self.settings.get('read_only', False):
MinRK
add read-only view for notebooks...
r5200 return f(self, *args, **kwargs)
else:
return auth_f(self, *args, **kwargs)
Matthias BUSSONNIER
use redirect for new/copy notebooks...
r8097 def urljoin(*pieces):
MinRK
cleanup IPython handler settings...
r10355 """Join components of url into a relative url
Matthias BUSSONNIER
use redirect for new/copy notebooks...
r8097
Use to prevent double slash when joining subpath
"""
striped = [s.strip('/') for s in pieces]
return '/'.join(s for s in striped if s)
Brian E. Granger
Work to adapt routers to new Session message protocol.
r4346 #-----------------------------------------------------------------------------
Brian E. Granger
Adding kernel/notebook associations.
r4494 # Top-level handlers
Brian E. Granger
Work to adapt routers to new Session message protocol.
r4346 #-----------------------------------------------------------------------------
Brian Granger
Initial draft of HTML5/JS/CSS3 notebook.
r4292
Stefan van der Walt
Use template inheritance.
r5324 class RequestHandler(web.RequestHandler):
"""RequestHandler with default variable setting."""
def render(*args, **kwargs):
kwargs.setdefault('message', '')
return web.RequestHandler.render(*args, **kwargs)
class AuthenticatedHandler(RequestHandler):
MinRK
Authenticate all notebook requests (except websockets)...
r4706 """A RequestHandler with an authenticated user."""
Brian E. Granger
Minor changes to handlers.
r5102
MinRK
cleanup IPython handler settings...
r10355 def clear_login_cookie(self):
self.clear_cookie(self.cookie_name)
Satrajit Ghosh
enh: added authentication ability for webapp
r4690 def get_current_user(self):
MinRK
cleanup IPython handler settings...
r10355 user_id = self.get_secure_cookie(self.cookie_name)
Brian E. Granger
Minor changes to handlers.
r5102 # For now the user_id should not return empty, but it could eventually
MinRK
only store hashed user_id in notebook cookie...
r4724 if user_id == '':
user_id = 'anonymous'
if user_id is None:
# prevent extra Invalid cookie sig warnings:
MinRK
cleanup IPython handler settings...
r10355 self.clear_login_cookie()
if not self.read_only and not self.login_available:
MinRK
only store hashed user_id in notebook cookie...
r4724 user_id = 'anonymous'
return user_id
Stefan van der Walt
Improve three-state read-only logic.
r5721
MinRK
move read_only flag to page-level...
r5213 @property
MinRK
cleanup IPython handler settings...
r10355 def cookie_name(self):
return self.settings.get('cookie_name', '')
@property
def password(self):
"""our password"""
return self.settings.get('password', '')
@property
Stefan van der Walt
Split read-only logic into three functions: read_only, logged_in, and login_available. Move display logic from javascript into templates.
r5722 def logged_in(self):
"""Is a user currently logged in?
"""
user = self.get_current_user()
return (user and not user == 'anonymous')
@property
def login_available(self):
"""May a user proceed to log in?
This returns True if login capability is available, irrespective of
whether the user is already logged in or not.
"""
MinRK
cleanup IPython handler settings...
r10355 return bool(self.settings.get('password', ''))
Stefan van der Walt
Split read-only logic into three functions: read_only, logged_in, and login_available. Move display logic from javascript into templates.
r5722
@property
MinRK
move read_only flag to page-level...
r5213 def read_only(self):
Stefan van der Walt
Improve three-state read-only logic.
r5721 """Is the notebook read-only?
"""
MinRK
cleanup IPython handler settings...
r10355 return self.settings.get('read_only', False)
Stefan van der Walt
Improve three-state read-only logic.
r5721
MinRK
cleanup IPython handler settings...
r10355 class IPythonHandler(AuthenticatedHandler):
"""IPython-specific extensions to authenticated handling
Mostly property shortcuts to IPython-specific settings.
"""
@property
def config(self):
return self.settings.get('config', None)
MinRK
remove superfluous ws-hostname parameter from notebook...
r5252 @property
MinRK
hook up proper loggers...
r10360 def log(self):
"""use the IPython log by default, falling back on tornado's logger"""
if Application.initialized():
return Application.instance().log
else:
return app_log
@property
Bussonnier Matthias
add less flag
r9266 def use_less(self):
Matthias BUSSONNIER
replace tab by space comment print view action
r9296 """Use less instead of css in templates"""
MinRK
cleanup IPython handler settings...
r10355 return self.settings.get('use_less', False)
#---------------------------------------------------------------
# URLs
#---------------------------------------------------------------
MinRK
remove superfluous ws-hostname parameter from notebook...
r5252 @property
def ws_url(self):
"""websocket url matching the current request
Andrew Straw
let websocket server be traited config option
r6006
MinRK
don't use Origin header to determine ws_url
r5300 turns http[s]://host[:port] into
ws[s]://host[:port]
MinRK
remove superfluous ws-hostname parameter from notebook...
r5252 """
MinRK
don't use Origin header to determine ws_url
r5300 proto = self.request.protocol.replace('http', 'ws')
MinRK
cleanup IPython handler settings...
r10355 host = self.settings.get('websocket_host', '')
# default to config value
Andrew Straw
let websocket server be traited config option
r6006 if host == '':
host = self.request.host # get from request
return "%s://%s" % (proto, host)
MinRK
cleanup IPython handler settings...
r10355
@property
def mathjax_url(self):
return self.settings.get('mathjax_url', '')
@property
def base_project_url(self):
return self.settings.get('base_project_url', '/')
@property
def base_kernel_url(self):
return self.settings.get('base_kernel_url', '/')
#---------------------------------------------------------------
# Manager objects
#---------------------------------------------------------------
@property
def kernel_manager(self):
return self.settings['kernel_manager']
@property
def notebook_manager(self):
return self.settings['notebook_manager']
@property
def cluster_manager(self):
return self.settings['cluster_manager']
@property
def project(self):
return self.notebook_manager.notebook_dir
#---------------------------------------------------------------
# template rendering
#---------------------------------------------------------------
def get_template(self, name):
"""Return the jinja template object for a given name"""
return self.settings['jinja2_env'].get_template(name)
def render_template(self, name, **ns):
ns.update(self.template_namespace)
template = self.get_template(name)
return template.render(**ns)
@property
def template_namespace(self):
return dict(
base_project_url=self.base_project_url,
base_kernel_url=self.base_kernel_url,
read_only=self.read_only,
logged_in=self.logged_in,
login_available=self.login_available,
use_less=self.use_less,
)
Satrajit Ghosh
enh: added authentication ability for webapp
r4690
MinRK
cleanup IPython handler settings...
r10355 class AuthenticatedFileHandler(IPythonHandler, web.StaticFileHandler):
MinRK
authenticate local file access...
r5824 """static files should only be accessible when logged in"""
@authenticate_unless_readonly
def get(self, path):
return web.StaticFileHandler.get(self, path)
MinRK
cleanup IPython handler settings...
r10355 class ProjectDashboardHandler(IPythonHandler):
Brian E. Granger
Minor changes to handlers.
r5102
MinRK
add read-only view for notebooks...
r5200 @authenticate_unless_readonly
Brian E. Granger
Implemented basic notebook browser and fixed numerous bugs.
r4488 def get(self):
MinRK
cleanup IPython handler settings...
r10355 self.write(self.render_template('projectdashboard.html',
project=self.project,
project_component=self.project.split('/'),
))
Brian E. Granger
Implemented basic notebook browser and fixed numerous bugs.
r4488
Brian E. Granger
Minor changes to handlers.
r5102
MinRK
cleanup IPython handler settings...
r10355 class LoginHandler(IPythonHandler):
Brian E. Granger
Minor changes to handlers.
r5102
MinRK
cleanup IPython handler settings...
r10355 def _render(self, message=None):
self.write(self.render_template('login.html',
next=url_escape(self.get_argument('next', default=self.base_project_url)),
message=message,
Cameron Bates
Refactor notebook to use Jinja2 instead of tornado templates
r8350 ))
Satrajit Ghosh
enh: added authentication ability for webapp
r4690
Stefan van der Walt
Notify user about invalid password.
r5323 def get(self):
Stefan van der Walt
Only show logout button if logged in.
r5327 if self.current_user:
MinRK
cleanup IPython handler settings...
r10355 self.redirect(self.get_argument('next', default=self.base_project_url))
Stefan van der Walt
Only show logout button if logged in.
r5327 else:
self._render()
Stefan van der Walt
Notify user about invalid password.
r5323
Satrajit Ghosh
enh: added authentication ability for webapp
r4690 def post(self):
Brian E. Granger
Simplifying logic on login page.
r5109 pwd = self.get_argument('password', default=u'')
MinRK
cleanup IPython handler settings...
r10355 if self.login_available:
if passwd_check(self.password, pwd):
self.set_secure_cookie(self.cookie_name, str(uuid.uuid4()))
Stefan van der Walt
Notify user about invalid password.
r5323 else:
Stefan van der Walt
Add info, error and warning message boxes.
r5326 self._render(message={'error': 'Invalid password'})
Stefan van der Walt
Notify user about invalid password.
r5323 return
MinRK
cleanup IPython handler settings...
r10355 self.redirect(self.get_argument('next', default=self.base_project_url))
Brian E. Granger
Implemented basic notebook browser and fixed numerous bugs.
r4488
Brian E. Granger
Minor changes to handlers.
r5102
MinRK
cleanup IPython handler settings...
r10355 class LogoutHandler(IPythonHandler):
Stefan van der Walt
Add logout button.
r5325
def get(self):
MinRK
cleanup IPython handler settings...
r10355 self.clear_login_cookie()
Stefan van der Walt
Split read-only logic into three functions: read_only, logged_in, and login_available. Move display logic from javascript into templates.
r5722 if self.login_available:
message = {'info': 'Successfully logged out.'}
else:
message = {'warning': 'Cannot log out. Notebook authentication '
'is disabled.'}
MinRK
cleanup IPython handler settings...
r10355 self.write(self.render_template('logout.html',
Cameron Bates
Refactor notebook to use Jinja2 instead of tornado templates
r8350 message=message))
Stefan van der Walt
Add logout button.
r5325
MinRK
cleanup IPython handler settings...
r10355 class NewHandler(IPythonHandler):
Brian E. Granger
Minor changes to handlers.
r5102
MinRK
Authenticate all notebook requests (except websockets)...
r4706 @web.authenticated
Brian Granger
Initial draft of HTML5/JS/CSS3 notebook.
r4292 def get(self):
MinRK
cleanup IPython handler settings...
r10355 notebook_id = self.notebook_manager.new_notebook()
self.redirect('/' + urljoin(self.base_project_url, notebook_id))
Brian E. Granger
Massive work on the notebook document format....
r4484
MinRK
cleanup IPython handler settings...
r10355 class NamedNotebookHandler(IPythonHandler):
Brian E. Granger
Minor changes to handlers.
r5102
MinRK
add read-only view for notebooks...
r5200 @authenticate_unless_readonly
Brian E. Granger
Massive work on the notebook document format....
r4484 def get(self, notebook_id):
MinRK
cleanup IPython handler settings...
r10355 nbm = self.notebook_manager
Brian E. Granger
Massive work on the notebook document format....
r4484 if not nbm.notebook_exists(notebook_id):
Cameron Bates
Make template environment a property and fix notebook location
r8792 raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)
MinRK
cleanup IPython handler settings...
r10355 self.write(self.render_template('notebook.html',
project=self.project,
Brian E. Granger
Adding data-project to the body data attribs.
r5105 notebook_id=notebook_id,
MinRK
move read_only flag to page-level...
r5213 kill_kernel=False,
MinRK
cleanup IPython handler settings...
r10355 mathjax_url=self.mathjax_url,
Matthias BUSSONNIER
propagate use_less flag in all pages
r9384 )
Matthias BUSSONNIER
replace tab by space comment print view action
r9296 )
Brian Granger
Initial draft of HTML5/JS/CSS3 notebook.
r4292
Brian E. Granger
Adding kernel/notebook associations.
r4494 #-----------------------------------------------------------------------------
# Kernel handlers
#-----------------------------------------------------------------------------
MinRK
cleanup IPython handler settings...
r10355 class MainKernelHandler(IPythonHandler):
Brian Granger
Work on the server side of the html notebook.
r4297
MinRK
Authenticate all notebook requests (except websockets)...
r4706 @web.authenticated
Brian Granger
Work on the server side of the html notebook.
r4297 def get(self):
MinRK
cleanup IPython handler settings...
r10355 km = self.kernel_manager
Brian E. Granger
General cleanup of kernelmanger.MultiKernelManager.
r9112 self.finish(jsonapi.dumps(km.list_kernel_ids()))
Brian Granger
Basic server for htmlnotebook working.
r4298
MinRK
Authenticate all notebook requests (except websockets)...
r4706 @web.authenticated
Brian Granger
Different clients now share a single zmq session....
r4306 def post(self):
MinRK
cleanup IPython handler settings...
r10355 km = self.kernel_manager
nbm = self.notebook_manager
Brian E. Granger
Adding kernel/notebook associations.
r4494 notebook_id = self.get_argument('notebook', default=None)
MinRK
use notebook-dir as cwd for kernels
r7558 kernel_id = km.start_kernel(notebook_id, cwd=nbm.notebook_dir)
MinRK
remove superfluous ws-hostname parameter from notebook...
r5252 data = {'ws_url':self.ws_url,'kernel_id':kernel_id}
Brian E. Granger
Massive work on the notebook document format....
r4484 self.set_header('Location', '/'+kernel_id)
Brian E. Granger
WebSocket url is now passed to browser when a kernel is started.
r4572 self.finish(jsonapi.dumps(data))
Brian Granger
Work on the server side of the html notebook.
r4297
MinRK
cleanup IPython handler settings...
r10355 class KernelHandler(IPythonHandler):
Brian E. Granger
Adding kernel/notebook associations.
r4494
SUPPORTED_METHODS = ('DELETE')
MinRK
Authenticate all notebook requests (except websockets)...
r4706 @web.authenticated
Brian E. Granger
Adding kernel/notebook associations.
r4494 def delete(self, kernel_id):
MinRK
cleanup IPython handler settings...
r10355 km = self.kernel_manager
MinRK
use shutdown_kernel instead of hard kill in notebook
r7627 km.shutdown_kernel(kernel_id)
Brian E. Granger
Adding kernel/notebook associations.
r4494 self.set_status(204)
self.finish()
MinRK
cleanup IPython handler settings...
r10355 class KernelActionHandler(IPythonHandler):
Brian Granger
Interrupt and restart work for kernels.
r4308
MinRK
Authenticate all notebook requests (except websockets)...
r4706 @web.authenticated
Brian Granger
Cleaned up kernel action interface....
r4309 def post(self, kernel_id, action):
MinRK
cleanup IPython handler settings...
r10355 km = self.kernel_manager
Brian Granger
Cleaned up kernel action interface....
r4309 if action == 'interrupt':
Brian E. Granger
Major refactor of kernel connection management in the notebook....
r4545 km.interrupt_kernel(kernel_id)
Brian E. Granger
Adding kernel/notebook associations.
r4494 self.set_status(204)
Brian Granger
Cleaned up kernel action interface....
r4309 if action == 'restart':
Brian E. Granger
Removing return value of restart_kernel....
r9113 km.restart_kernel(kernel_id)
data = {'ws_url':self.ws_url, 'kernel_id':kernel_id}
self.set_header('Location', '/'+kernel_id)
Brian E. Granger
WebSocket url is now passed to browser when a kernel is started.
r4572 self.write(jsonapi.dumps(data))
Brian E. Granger
Improvements to file uploaded, mime types and .py reader....
r4493 self.finish()
Brian Granger
Interrupt and restart work for kernels.
r4308
Brian Granger
Different clients now share a single zmq session....
r4306 class ZMQStreamHandler(websocket.WebSocketHandler):
MinRK
hook up proper loggers...
r10360
MinRK
define clear_cookie on websocket handler...
r10357 def clear_cookie(self, *args, **kwargs):
"""meaningless for websockets"""
pass
Brian E. Granger
Major refactor of kernel connection management in the notebook....
r4545 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)
Brian E. Granger
Date is properly removed from JSON reply before WebSocket forward....
r4561 try:
msg['header'].pop('date')
except KeyError:
pass
try:
msg['parent_header'].pop('date')
except KeyError:
pass
Brian E. Granger
Major refactor of kernel connection management in the notebook....
r4545 msg.pop('buffers')
MinRK
fix date objects in _reserialize_reply
r6870 return jsonapi.dumps(msg, default=date_default)
Brian E. Granger
Major refactor of kernel connection management in the notebook....
r4545
Brian E. Granger
Date is properly removed from JSON reply before WebSocket forward....
r4561 def _on_zmq_reply(self, msg_list):
Brian Granger
Refactoring kernel restarting.
r10282 # Sometimes this gets triggered when the on_close method is scheduled in the
# eventloop but hasn't been called.
if self.stream.closed(): return
Brian E. Granger
Date is properly removed from JSON reply before WebSocket forward....
r4561 try:
msg = self._reserialize_reply(msg_list)
MinRK
fix date objects in _reserialize_reply
r6870 except Exception:
MinRK
hook up proper loggers...
r10360 self.log.critical("Malformed message: %r" % msg_list, exc_info=True)
Brian E. Granger
Date is properly removed from JSON reply before WebSocket forward....
r4561 else:
self.write_message(msg)
MinRK
allow draft76 websockets (Safari)...
r6070 def allow_draft76(self):
"""Allow draft 76, until browsers such as Safari update to RFC 6455.
This has been disabled by default in tornado in release 2.2.0, and
support will be removed in later versions.
"""
return True
Brian E. Granger
Minor changes to the notebook handlers.
r5114
MinRK
cleanup IPython handler settings...
r10355 class AuthenticatedZMQStreamHandler(ZMQStreamHandler, IPythonHandler):
Brian E. Granger
Minor changes to the notebook handlers.
r5114
MinRK
authenticate Websockets with the session cookie...
r4707 def open(self, kernel_id):
Thomas Kluyver
Fix for notebook in Python 3.
r4839 self.kernel_id = kernel_id.decode('ascii')
MinRK
cleanup IPython handler settings...
r10355 self.session = Session(config=self.config)
MinRK
authenticate Websockets with the session cookie...
r4707 self.save_on_message = self.on_message
self.on_message = self.on_first_message
Bernardo B. Marques
remove all trailling spaces
r4872
MinRK
authenticate Websockets with the session cookie...
r4707 def _inject_cookie_message(self, msg):
"""Inject the first message, which is the document cookie,
for authentication."""
Mikhail Korobov
P3K: fix cookie parsing under Python 3.x (+ duplicate import is removed)
r9110 if not PY3 and isinstance(msg, unicode):
# Cookie constructor doesn't accept unicode strings
# under Python 2.x for some reason
MinRK
authenticate Websockets with the session cookie...
r4707 msg = msg.encode('utf8', 'replace')
try:
MinRK
clarify first ws message names / messages
r10378 identity, msg = msg.split(':', 1)
self.session.session = identity.decode('ascii')
MinRK
specify socket identity from kernel.js...
r10365 except Exception:
MinRK
clarify first ws message names / messages
r10378 logging.error("First ws message didn't have the form 'identity:[cookie]' - %r", msg)
MinRK
specify socket identity from kernel.js...
r10365 try:
Brian E. Granger
Using self.request._cookies in WS handlers.
r5119 self.request._cookies = Cookie.SimpleCookie(msg)
MinRK
authenticate Websockets with the session cookie...
r4707 except:
MinRK
hook up proper loggers...
r10360 self.log.warn("couldn't parse cookie string: %s",msg, exc_info=True)
Bernardo B. Marques
remove all trailling spaces
r4872
MinRK
authenticate Websockets with the session cookie...
r4707 def on_first_message(self, msg):
self._inject_cookie_message(msg)
if self.get_current_user() is None:
MinRK
hook up proper loggers...
r10360 self.log.warn("Couldn't authenticate WebSocket connection")
MinRK
authenticate Websockets with the session cookie...
r4707 raise web.HTTPError(403)
self.on_message = self.save_on_message
Bernardo B. Marques
remove all trailling spaces
r4872
MinRK
authenticate Websockets with the session cookie...
r4707
MinRK
share code between zmq channel handlers
r10363 class ZMQChannelHandler(AuthenticatedZMQStreamHandler):
@property
def max_msg_size(self):
return self.settings.get('max_msg_size', 65535)
def create_stream(self):
km = self.kernel_manager
meth = getattr(km, 'connect_%s' % self.channel)
MinRK
specify socket identity from kernel.js...
r10365 self.zmq_stream = meth(self.kernel_id, identity=self.session.bsession)
MinRK
share code between zmq channel handlers
r10363
Brian E. Granger
Major refactor of kernel connection management in the notebook....
r4545 def initialize(self, *args, **kwargs):
MinRK
share code between zmq channel handlers
r10363 self.zmq_stream = None
MinRK
authenticate Websockets with the session cookie...
r4707 def on_first_message(self, msg):
try:
MinRK
share code between zmq channel handlers
r10363 super(ZMQChannelHandler, self).on_first_message(msg)
MinRK
authenticate Websockets with the session cookie...
r4707 except web.HTTPError:
self.close()
return
Brian E. Granger
WebSocket url is now passed to browser when a kernel is started.
r4572 try:
MinRK
share code between zmq channel handlers
r10363 self.create_stream()
Brian E. Granger
WebSocket url is now passed to browser when a kernel is started.
r4572 except web.HTTPError:
# WebSockets don't response to traditional error codes so we
# close the connection.
if not self.stream.closed():
self.stream.close()
MinRK
authenticate Websockets with the session cookie...
r4707 self.close()
Brian E. Granger
WebSocket url is now passed to browser when a kernel is started.
r4572 else:
MinRK
share code between zmq channel handlers
r10363 self.zmq_stream.on_recv(self._on_zmq_reply)
Bernardo B. Marques
remove all trailling spaces
r4872
MinRK
authenticate Websockets with the session cookie...
r4707 def on_message(self, msg):
MinRK
share code between zmq channel handlers
r10363 if len(msg) < self.max_msg_size:
msg = jsonapi.loads(msg)
self.session.send(self.zmq_stream, msg)
MinRK
send status messages when kernel is auto restarted
r10315
Brian E. Granger
Major refactor of kernel connection management in the notebook....
r4545 def on_close(self):
Bernardo B. Marques
remove all trailling spaces
r4872 # This method can be called twice, once by self.kernel_died and once
Brian E. Granger
WebSocket url is now passed to browser when a kernel is started.
r4572 # from the WebSocket close event. If the WebSocket connection is
# closed before the ZMQ streams are setup, they could be None.
MinRK
share code between zmq channel handlers
r10363 if self.zmq_stream is not None and not self.zmq_stream.closed():
self.zmq_stream.on_recv(None)
self.zmq_stream.close()
class IOPubHandler(ZMQChannelHandler):
channel = 'iopub'
def create_stream(self):
super(IOPubHandler, self).create_stream()
km = self.kernel_manager
km.add_restart_callback(self.kernel_id, self.on_kernel_restarted)
km.add_restart_callback(self.kernel_id, self.on_restart_failed, 'dead')
def on_close(self):
MinRK
cleanup IPython handler settings...
r10355 km = self.kernel_manager
MinRK
handle failed kernel restart in the notebook
r10321 if self.kernel_id in km:
km.remove_restart_callback(
self.kernel_id, self.on_kernel_restarted,
)
km.remove_restart_callback(
self.kernel_id, self.on_restart_failed, 'dead',
)
MinRK
share code between zmq channel handlers
r10363 super(IOPubHandler, self).on_close()
MinRK
cleanup IPython handler settings...
r10355
MinRK
share code between zmq channel handlers
r10363 def _send_status_message(self, status):
msg = self.session.msg("status",
{'execution_state': status}
)
self.write_message(jsonapi.dumps(msg, default=date_default))
Brian Granger
Work on the server side of the html notebook.
r4297
MinRK
share code between zmq channel handlers
r10363 def on_kernel_restarted(self):
logging.warn("kernel %s restarted", self.kernel_id)
self._send_status_message('restarting')
Brian E. Granger
Major refactor of kernel connection management in the notebook....
r4545
MinRK
share code between zmq channel handlers
r10363 def on_restart_failed(self):
logging.error("kernel %s restarted failed!", self.kernel_id)
self._send_status_message('dead')
MinRK
add no-op on_message for iopub
r10372 def on_message(self, msg):
"""IOPub messages make no sense"""
pass
MinRK
share code between zmq channel handlers
r10363 class ShellHandler(ZMQChannelHandler):
channel = 'shell'
Brian Granger
Work on the server side of the html notebook.
r4297
MinRK
share code between zmq channel handlers
r10363 class StdinHandler(ZMQChannelHandler):
channel = 'stdin'
Brian Granger
Work on the server side of the html notebook.
r4297
Brian E. Granger
Adding kernel/notebook associations.
r4494 #-----------------------------------------------------------------------------
# Notebook web service handlers
#-----------------------------------------------------------------------------
MinRK
cleanup IPython handler settings...
r10355 class NotebookRedirectHandler(IPythonHandler):
MinRK
add redirect handler for notebooks by name...
r10008
@authenticate_unless_readonly
def get(self, notebook_name):
MinRK
don't test for .ipynb in redirect handler...
r10187 # strip trailing .ipynb:
notebook_name = os.path.splitext(notebook_name)[0]
MinRK
cleanup IPython handler settings...
r10355 notebook_id = self.notebook_manager.rev_mapping.get(notebook_name, '')
MinRK
add redirect handler for notebooks by name...
r10008 if notebook_id:
url = self.settings.get('base_project_url', '/') + notebook_id
return self.redirect(url)
else:
raise HTTPError(404)
MinRK
cleanup IPython handler settings...
r10355
class NotebookRootHandler(IPythonHandler):
Brian Granger
Server side of file based notebook store implemented.
r4301
MinRK
add read-only view for notebooks...
r5200 @authenticate_unless_readonly
Brian Granger
Server side of file based notebook store implemented.
r4301 def get(self):
MinRK
cleanup IPython handler settings...
r10355 nbm = self.notebook_manager
km = self.kernel_manager
Brian E. Granger
Massive work on the notebook document format....
r4484 files = nbm.list_notebooks()
Matthias BUSSONNIER
kernel status
r6841 for f in files :
Matthias BUSSONNIER
Check for null rather than undefined...
r6848 f['kernel_id'] = km.kernel_for_notebook(f['notebook_id'])
Brian E. Granger
Major refactor of kernel connection management in the notebook....
r4545 self.finish(jsonapi.dumps(files))
Brian Granger
Server side of file based notebook store implemented.
r4301
MinRK
Authenticate all notebook requests (except websockets)...
r4706 @web.authenticated
Brian E. Granger
Massive work on the notebook document format....
r4484 def post(self):
MinRK
cleanup IPython handler settings...
r10355 nbm = self.notebook_manager
Brian E. Granger
Massive work on the notebook document format....
r4484 body = self.request.body.strip()
format = self.get_argument('format', default='json')
Brian E. Granger
File upload/import working from notebook browser.
r4491 name = self.get_argument('name', default=None)
Brian E. Granger
Massive work on the notebook document format....
r4484 if body:
Brian E. Granger
File upload/import working from notebook browser.
r4491 notebook_id = nbm.save_new_notebook(body, name=name, format=format)
Brian E. Granger
Massive work on the notebook document format....
r4484 else:
notebook_id = nbm.new_notebook()
self.set_header('Location', '/'+notebook_id)
Brian E. Granger
Major refactor of kernel connection management in the notebook....
r4545 self.finish(jsonapi.dumps(notebook_id))
Brian Granger
Server side of file based notebook store implemented.
r4301
MinRK
cleanup IPython handler settings...
r10355 class NotebookHandler(IPythonHandler):
Brian Granger
Server side of file based notebook store implemented.
r4301
Brian E. Granger
Massive work on the notebook document format....
r4484 SUPPORTED_METHODS = ('GET', 'PUT', 'DELETE')
MinRK
add read-only view for notebooks...
r5200 @authenticate_unless_readonly
Brian E. Granger
Massive work on the notebook document format....
r4484 def get(self, notebook_id):
MinRK
cleanup IPython handler settings...
r10355 nbm = self.notebook_manager
Brian E. Granger
Massive work on the notebook document format....
r4484 format = self.get_argument('format', default='json')
last_mod, name, data = nbm.get_notebook(notebook_id, format)
MinRK
add read-only view for notebooks...
r5200
Brian E. Granger
Massive work on the notebook document format....
r4484 if format == u'json':
self.set_header('Content-Type', 'application/json')
Brian E. Granger
Converting notebooks to JSON format.
r4634 self.set_header('Content-Disposition','attachment; filename="%s.ipynb"' % name)
Brian E. Granger
Massive work on the notebook document format....
r4484 elif format == u'py':
Brian E. Granger
Improvements to file uploaded, mime types and .py reader....
r4493 self.set_header('Content-Type', 'application/x-python')
Brian E. Granger
Export works with filenames having spaces....
r4558 self.set_header('Content-Disposition','attachment; filename="%s.py"' % name)
Brian E. Granger
Massive work on the notebook document format....
r4484 self.set_header('Last-Modified', last_mod)
self.finish(data)
MinRK
Authenticate all notebook requests (except websockets)...
r4706 @web.authenticated
Brian E. Granger
Massive work on the notebook document format....
r4484 def put(self, notebook_id):
MinRK
cleanup IPython handler settings...
r10355 nbm = self.notebook_manager
Brian E. Granger
Massive work on the notebook document format....
r4484 format = self.get_argument('format', default='json')
Brian E. Granger
File upload/import working from notebook browser.
r4491 name = self.get_argument('name', default=None)
nbm.save_notebook(notebook_id, self.request.body, name=name, format=format)
Brian E. Granger
Massive work on the notebook document format....
r4484 self.set_status(204)
Brian Granger
Server side of file based notebook store implemented.
r4301 self.finish()
MinRK
Authenticate all notebook requests (except websockets)...
r4706 @web.authenticated
Brian E. Granger
Massive work on the notebook document format....
r4484 def delete(self, notebook_id):
MinRK
cleanup IPython handler settings...
r10355 self.notebook_manager.delete_notebook(notebook_id)
Brian Granger
Server side of file based notebook store implemented.
r4301 self.set_status(204)
self.finish()
MinRK
add notebook checkpoint handler
r10498
MinRK
add ModifyCheckpoints handler...
r10499 class NotebookCheckpointsHandler(AuthenticatedHandler):
SUPPORTED_METHODS = ('GET', 'POST')
MinRK
add notebook checkpoint handler
r10498
@web.authenticated
def get(self, notebook_id):
"""get lists checkpoints for a notebook"""
nbm = self.application.notebook_manager
checkpoints = nbm.list_checkpoints(notebook_id)
MinRK
add ModifyCheckpoints handler...
r10499 data = jsonapi.dumps(checkpoints, default=date_default)
self.finish(data)
MinRK
add notebook checkpoint handler
r10498
@web.authenticated
def post(self, notebook_id):
MinRK
add ModifyCheckpoints handler...
r10499 """post creates a new checkpoint"""
nbm = self.application.notebook_manager
checkpoint = nbm.create_checkpoint(notebook_id)
data = jsonapi.dumps(checkpoint, default=date_default)
self.finish(data)
class ModifyNotebookCheckpointsHandler(AuthenticatedHandler):
SUPPORTED_METHODS = ('POST', 'DELETE')
@web.authenticated
def post(self, notebook_id, checkpoint_id):
MinRK
add notebook checkpoint handler
r10498 """post restores a notebook from a checkpoint"""
nbm = self.application.notebook_manager
nbm.restore_checkpoint(notebook_id, checkpoint_id)
self.set_status(204)
self.finish()
@web.authenticated
MinRK
add ModifyCheckpoints handler...
r10499 def delete(self, notebook_id, checkpoint_id):
MinRK
add notebook checkpoint handler
r10498 """delete clears a checkpoint for a given notebook"""
nbm = self.application.notebook_manager
nbm.delte_checkpoint(notebook_id, checkpoint_id)
self.set_status(204)
self.finish()
Brian Granger
Beginning work on notebook duplication.
r5860
MinRK
cleanup IPython handler settings...
r10355 class NotebookCopyHandler(IPythonHandler):
Brian Granger
Beginning work on notebook duplication.
r5860
@web.authenticated
def get(self, notebook_id):
MinRK
cleanup IPython handler settings...
r10355 notebook_id = self.notebook_manager.copy_notebook(notebook_id)
self.redirect('/'+urljoin(self.base_project_url, notebook_id))
Brian Granger
Beginning work on notebook duplication.
r5860
Brian Granger
First version of cluster web service....
r6191
#-----------------------------------------------------------------------------
# Cluster handlers
#-----------------------------------------------------------------------------
MinRK
cleanup IPython handler settings...
r10355 class MainClusterHandler(IPythonHandler):
Brian Granger
First version of cluster web service....
r6191
@web.authenticated
def get(self):
MinRK
cleanup IPython handler settings...
r10355 self.finish(jsonapi.dumps(self.cluster_manager.list_profiles()))
Brian Granger
First version of cluster web service....
r6191
MinRK
cleanup IPython handler settings...
r10355 class ClusterProfileHandler(IPythonHandler):
Brian Granger
First version of cluster web service....
r6191
@web.authenticated
def get(self, profile):
MinRK
cleanup IPython handler settings...
r10355 self.finish(jsonapi.dumps(self.cluster_manager.profile_info(profile)))
Brian Granger
First version of cluster web service....
r6191
MinRK
cleanup IPython handler settings...
r10355 class ClusterActionHandler(IPythonHandler):
Brian Granger
First version of cluster web service....
r6191
@web.authenticated
def post(self, profile, action):
MinRK
cleanup IPython handler settings...
r10355 cm = self.cluster_manager
Brian Granger
First version of cluster web service....
r6191 if action == 'start':
Brian Granger
Notebook cluster manager now uses proper launchers.
r6199 n = self.get_argument('n',default=None)
if n is None:
data = cm.start_cluster(profile)
else:
MinRK
cleanup IPython handler settings...
r10355 data = cm.start_cluster(profile, int(n))
Brian Granger
First version of cluster web service....
r6191 if action == 'stop':
Brian Granger
Cluster management is now working....
r6197 data = cm.stop_cluster(profile)
self.finish(jsonapi.dumps(data))
Brian Granger
First version of cluster web service....
r6191
Brian E. Granger
Starting work on a Markdown cell.
r4507 #-----------------------------------------------------------------------------
Brian E. Granger
Removing old restuctured text handler and web service.
r10392 # File handler
Brian E. Granger
Starting work on a Markdown cell.
r4507 #-----------------------------------------------------------------------------
MinRK
add missing methods in FindFileHandler for tornado < 2.2.0 compat
r8016 # to minimize subclass changes:
HTTPError = web.HTTPError
Brian E. Granger
Starting work on a Markdown cell.
r4507
MinRK
add FileFindHandler for serving static files from a search path
r7922 class FileFindHandler(web.StaticFileHandler):
"""subclass of StaticFileHandler for serving files from a search path"""
_static_paths = {}
MinRK
add missing methods in FindFileHandler for tornado < 2.2.0 compat
r8016 # _lock is needed for tornado < 2.2.0 compat
_lock = threading.Lock() # protects _static_hashes
MinRK
add FileFindHandler for serving static files from a search path
r7922
def initialize(self, path, default_filename=None):
MinRK
handle single static path in FileFindHandler
r7930 if isinstance(path, basestring):
path = [path]
MinRK
add FileFindHandler for serving static files from a search path
r7922 self.roots = tuple(
os.path.abspath(os.path.expanduser(p)) + os.path.sep for p in path
)
self.default_filename = default_filename
@classmethod
def locate_file(cls, path, roots):
"""locate a file to serve on our static file search path"""
with cls._lock:
if path in cls._static_paths:
return cls._static_paths[path]
try:
abspath = os.path.abspath(filefind(path, roots))
except IOError:
# empty string should always give exists=False
return ''
# os.path.abspath strips a trailing /
# it needs to be temporarily added back for requests to root/
if not (abspath + os.path.sep).startswith(roots):
MinRK
add missing methods in FindFileHandler for tornado < 2.2.0 compat
r8016 raise HTTPError(403, "%s is not in root static directory", path)
MinRK
add FileFindHandler for serving static files from a search path
r7922
cls._static_paths[path] = abspath
return abspath
def get(self, path, include_body=True):
path = self.parse_url_path(path)
MinRK
add missing methods in FindFileHandler for tornado < 2.2.0 compat
r8016 # begin subclass override
abspath = self.locate_file(path, self.roots)
# end subclass override
MinRK
add FileFindHandler for serving static files from a search path
r7922
if os.path.isdir(abspath) and self.default_filename is not None:
# need to look at the request.path here for when path is empty
# but there is some prefix to the path that was already
# trimmed by the routing
if not self.request.path.endswith("/"):
self.redirect(self.request.path + "/")
return
abspath = os.path.join(abspath, self.default_filename)
if not os.path.exists(abspath):
MinRK
add missing methods in FindFileHandler for tornado < 2.2.0 compat
r8016 raise HTTPError(404)
MinRK
add FileFindHandler for serving static files from a search path
r7922 if not os.path.isfile(abspath):
MinRK
add missing methods in FindFileHandler for tornado < 2.2.0 compat
r8016 raise HTTPError(403, "%s is not a file", path)
MinRK
add FileFindHandler for serving static files from a search path
r7922
stat_result = os.stat(abspath)
MinRK
backport If-Modified-Since fix from tornado...
r10228 modified = datetime.datetime.utcfromtimestamp(stat_result[stat.ST_MTIME])
MinRK
add FileFindHandler for serving static files from a search path
r7922
self.set_header("Last-Modified", modified)
mime_type, encoding = mimetypes.guess_type(abspath)
if mime_type:
self.set_header("Content-Type", mime_type)
cache_time = self.get_cache_time(path, modified, mime_type)
if cache_time > 0:
self.set_header("Expires", datetime.datetime.utcnow() + \
datetime.timedelta(seconds=cache_time))
self.set_header("Cache-Control", "max-age=" + str(cache_time))
else:
self.set_header("Cache-Control", "public")
self.set_extra_headers(path)
# Check the If-Modified-Since, and don't send the result if the
# content has not been modified
ims_value = self.request.headers.get("If-Modified-Since")
if ims_value is not None:
date_tuple = email.utils.parsedate(ims_value)
MinRK
backport If-Modified-Since fix from tornado...
r10228 if_since = datetime.datetime(*date_tuple[:6])
MinRK
add FileFindHandler for serving static files from a search path
r7922 if if_since >= modified:
self.set_status(304)
return
with open(abspath, "rb") as file:
data = file.read()
hasher = hashlib.sha1()
hasher.update(data)
self.set_header("Etag", '"%s"' % hasher.hexdigest())
if include_body:
self.write(data)
else:
assert self.request.method == "HEAD"
self.set_header("Content-Length", len(data))
@classmethod
def get_version(cls, settings, path):
"""Generate the version string to be used in static URLs.
This method may be overridden in subclasses (but note that it
is a class method rather than a static method). The default
implementation uses a hash of the file's contents.
``settings`` is the `Application.settings` dictionary and ``path``
is the relative location of the requested asset on the filesystem.
The returned value should be a string, or ``None`` if no version
could be determined.
"""
# begin subclass override:
MinRK
handle single static path in FileFindHandler
r7930 static_paths = settings['static_path']
if isinstance(static_paths, basestring):
static_paths = [static_paths]
MinRK
add FileFindHandler for serving static files from a search path
r7922 roots = tuple(
MinRK
handle single static path in FileFindHandler
r7930 os.path.abspath(os.path.expanduser(p)) + os.path.sep for p in static_paths
MinRK
add FileFindHandler for serving static files from a search path
r7922 )
try:
abs_path = filefind(path, roots)
MinRK
handle single static path in FileFindHandler
r7930 except IOError:
MinRK
hook up proper loggers...
r10360 app_log.error("Could not find static file %r", path)
MinRK
add FileFindHandler for serving static files from a search path
r7922 return None
# end subclass override
with cls._lock:
hashes = cls._static_hashes
if abs_path not in hashes:
try:
f = open(abs_path, "rb")
hashes[abs_path] = hashlib.md5(f.read()).hexdigest()
f.close()
except Exception:
MinRK
hook up proper loggers...
r10360 app_log.error("Could not open static file %r", path)
MinRK
add FileFindHandler for serving static files from a search path
r7922 hashes[abs_path] = None
hsh = hashes.get(abs_path)
if hsh:
return hsh[:5]
return None
MinRK
add missing methods in FindFileHandler for tornado < 2.2.0 compat
r8016
def parse_url_path(self, url_path):
"""Converts a static URL path into a filesystem path.
``url_path`` is the path component of the URL with
``static_url_prefix`` removed. The return value should be
filesystem path relative to ``static_path``.
"""
if os.path.sep != "/":
url_path = url_path.replace("/", os.path.sep)
return url_path