diff --git a/IPython/frontend/html/notebook/handlers/base.py b/IPython/frontend/html/notebook/handlers/base.py
index 32ec552..5d42ea2 100644
--- a/IPython/frontend/html/notebook/handlers/base.py
+++ b/IPython/frontend/html/notebook/handlers/base.py
@@ -1,4 +1,4 @@
-"""Tornado handlers for the notebook.
+"""Base Tornado handlers for the notebook.
 
 Authors:
 
@@ -6,7 +6,7 @@ Authors:
 """
 
 #-----------------------------------------------------------------------------
-#  Copyright (C) 2008-2011  The IPython Development Team
+#  Copyright (C) 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.
@@ -16,19 +16,8 @@ Authors:
 # Imports
 #-----------------------------------------------------------------------------
 
-import Cookie
-import datetime
-import email.utils
-import hashlib
 import logging
-import mimetypes
-import os
-import stat
-import threading
-import time
-import uuid
-
-from tornado.escape import url_escape
+
 from tornado import web
 from tornado import websocket
 
@@ -37,21 +26,8 @@ try:
 except ImportError:
     app_log = logging.getLogger()
 
-from zmq.eventloop import ioloop
-from zmq.utils import jsonapi
-
 from IPython.config import Application
 from IPython.external.decorator import decorator
-from IPython.kernel.zmq.session import Session
-from IPython.lib.security import passwd_check
-from IPython.utils.jsonutil import date_default
-from IPython.utils.path import filefind
-from IPython.utils.py3compat import PY3
-
-try:
-    from docutils.core import publish_string
-except ImportError:
-    publish_string = None
 
 #-----------------------------------------------------------------------------
 # Monkeypatch for Tornado <= 2.1.1 - Remove when no longer necessary!
@@ -131,14 +107,6 @@ def authenticate_unless_readonly(f, self, *args, **kwargs):
     else:
         return auth_f(self, *args, **kwargs)
 
-def urljoin(*pieces):
-    """Join components of url into a relative url
-
-    Use to prevent double slash when joining subpath
-    """
-    striped = [s.strip('/') for s in pieces]
-    return '/'.join(s for s in striped if s)
-
 #-----------------------------------------------------------------------------
 # Top-level handlers
 #-----------------------------------------------------------------------------
@@ -306,626 +274,3 @@ class AuthenticatedFileHandler(IPythonHandler, web.StaticFileHandler):
     @authenticate_unless_readonly
     def get(self, path):
         return web.StaticFileHandler.get(self, path)
-
-
-class ProjectDashboardHandler(IPythonHandler):
-
-    @authenticate_unless_readonly
-    def get(self):
-        self.write(self.render_template('projectdashboard.html',
-            project=self.project,
-            project_component=self.project.split('/'),
-        ))
-
-
-class LoginHandler(IPythonHandler):
-
-    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,
-        ))
-
-    def get(self):
-        if self.current_user:
-            self.redirect(self.get_argument('next', default=self.base_project_url))
-        else:
-            self._render()
-
-    def post(self):
-        pwd = self.get_argument('password', default=u'')
-        if self.login_available:
-            if passwd_check(self.password, pwd):
-                self.set_secure_cookie(self.cookie_name, str(uuid.uuid4()))
-            else:
-                self._render(message={'error': 'Invalid password'})
-                return
-
-        self.redirect(self.get_argument('next', default=self.base_project_url))
-
-
-class LogoutHandler(IPythonHandler):
-
-    def get(self):
-        self.clear_login_cookie()
-        if self.login_available:
-            message = {'info': 'Successfully logged out.'}
-        else:
-            message = {'warning': 'Cannot log out.  Notebook authentication '
-                       'is disabled.'}
-        self.write(self.render_template('logout.html',
-                    message=message))
-
-
-class NewHandler(IPythonHandler):
-
-    @web.authenticated
-    def get(self):
-        notebook_id = self.notebook_manager.new_notebook()
-        self.redirect('/' + urljoin(self.base_project_url, notebook_id))
-
-class NamedNotebookHandler(IPythonHandler):
-
-    @authenticate_unless_readonly
-    def get(self, notebook_id):
-        nbm = self.notebook_manager
-        if not nbm.notebook_exists(notebook_id):
-            raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)       
-        self.write(self.render_template('notebook.html',
-            project=self.project,
-            notebook_id=notebook_id,
-            kill_kernel=False,
-            mathjax_url=self.mathjax_url,
-            )
-        )
-
-
-#-----------------------------------------------------------------------------
-# Kernel handlers
-#-----------------------------------------------------------------------------
-
-
-class MainKernelHandler(IPythonHandler):
-
-    @web.authenticated
-    def get(self):
-        km = self.kernel_manager
-        self.finish(jsonapi.dumps(km.list_kernel_ids()))
-
-    @web.authenticated
-    def post(self):
-        km = self.kernel_manager
-        nbm = self.notebook_manager
-        notebook_id = self.get_argument('notebook', default=None)
-        kernel_id = km.start_kernel(notebook_id, cwd=nbm.notebook_dir)
-        data = {'ws_url':self.ws_url,'kernel_id':kernel_id}
-        self.set_header('Location', '{0}kernels/{1}'.format(self.base_kernel_url, kernel_id))
-        self.finish(jsonapi.dumps(data))
-
-
-class KernelHandler(IPythonHandler):
-
-    SUPPORTED_METHODS = ('DELETE')
-
-    @web.authenticated
-    def delete(self, kernel_id):
-        km = self.kernel_manager
-        km.shutdown_kernel(kernel_id)
-        self.set_status(204)
-        self.finish()
-
-
-class KernelActionHandler(IPythonHandler):
-
-    @web.authenticated
-    def post(self, kernel_id, action):
-        km = self.kernel_manager
-        if action == 'interrupt':
-            km.interrupt_kernel(kernel_id)
-            self.set_status(204)
-        if action == 'restart':
-            km.restart_kernel(kernel_id)
-            data = {'ws_url':self.ws_url, 'kernel_id':kernel_id}
-            self.set_header('Location', '{0}kernels/{1}'.format(self.base_kernel_url, kernel_id))
-            self.write(jsonapi.dumps(data))
-        self.finish()
-
-
-class ZMQStreamHandler(websocket.WebSocketHandler):
-    
-    def clear_cookie(self, *args, **kwargs):
-        """meaningless for websockets"""
-        pass
-
-    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, default=date_default)
-
-    def _on_zmq_reply(self, msg_list):
-        # Sometimes this gets triggered when the on_close method is scheduled in the
-        # eventloop but hasn't been called.
-        if self.stream.closed(): return
-        try:
-            msg = self._reserialize_reply(msg_list)
-        except Exception:
-            self.log.critical("Malformed message: %r" % msg_list, exc_info=True)
-        else:
-            self.write_message(msg)
-
-    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
-
-
-class AuthenticatedZMQStreamHandler(ZMQStreamHandler, IPythonHandler):
-
-    def open(self, kernel_id):
-        self.kernel_id = kernel_id.decode('ascii')
-        self.session = Session(config=self.config)
-        self.save_on_message = self.on_message
-        self.on_message = self.on_first_message
-
-    def _inject_cookie_message(self, msg):
-        """Inject the first message, which is the document cookie,
-        for authentication."""
-        if not PY3 and isinstance(msg, unicode):
-            # Cookie constructor doesn't accept unicode strings
-            # under Python 2.x for some reason
-            msg = msg.encode('utf8', 'replace')
-        try:
-            identity, msg = msg.split(':', 1)
-            self.session.session = identity.decode('ascii')
-        except Exception:
-            logging.error("First ws message didn't have the form 'identity:[cookie]' - %r", msg)
-        
-        try:
-            self.request._cookies = Cookie.SimpleCookie(msg)
-        except:
-            self.log.warn("couldn't parse cookie string: %s",msg, exc_info=True)
-
-    def on_first_message(self, msg):
-        self._inject_cookie_message(msg)
-        if self.get_current_user() is None:
-            self.log.warn("Couldn't authenticate WebSocket connection")
-            raise web.HTTPError(403)
-        self.on_message = self.save_on_message
-
-
-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)
-        self.zmq_stream = meth(self.kernel_id, identity=self.session.bsession)
-    
-    def initialize(self, *args, **kwargs):
-        self.zmq_stream = None
-    
-    def on_first_message(self, msg):
-        try:
-            super(ZMQChannelHandler, self).on_first_message(msg)
-        except web.HTTPError:
-            self.close()
-            return
-        try:
-            self.create_stream()
-        except web.HTTPError:
-            # WebSockets don't response to traditional error codes so we
-            # close the connection.
-            if not self.stream.closed():
-                self.stream.close()
-            self.close()
-        else:
-            self.zmq_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.zmq_stream, msg)
-
-    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.
-        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):
-        km = self.kernel_manager
-        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',
-            )
-        super(IOPubHandler, self).on_close()
-    
-    def _send_status_message(self, status):
-        msg = self.session.msg("status",
-            {'execution_state': status}
-        )
-        self.write_message(jsonapi.dumps(msg, default=date_default))
-
-    def on_kernel_restarted(self):
-        logging.warn("kernel %s restarted", self.kernel_id)
-        self._send_status_message('restarting')
-
-    def on_restart_failed(self):
-        logging.error("kernel %s restarted failed!", self.kernel_id)
-        self._send_status_message('dead')
-    
-    def on_message(self, msg):
-        """IOPub messages make no sense"""
-        pass
-
-class ShellHandler(ZMQChannelHandler):
-    channel = 'shell'
-
-class StdinHandler(ZMQChannelHandler):
-    channel = 'stdin'
-
-
-#-----------------------------------------------------------------------------
-# Notebook web service handlers
-#-----------------------------------------------------------------------------
-
-class NotebookRedirectHandler(IPythonHandler):
-    
-    @authenticate_unless_readonly
-    def get(self, notebook_name):
-        # strip trailing .ipynb:
-        notebook_name = os.path.splitext(notebook_name)[0]
-        notebook_id = self.notebook_manager.rev_mapping.get(notebook_name, '')
-        if notebook_id:
-            url = self.settings.get('base_project_url', '/') + notebook_id
-            return self.redirect(url)
-        else:
-            raise HTTPError(404)
-
-
-class NotebookRootHandler(IPythonHandler):
-
-    @authenticate_unless_readonly
-    def get(self):
-        nbm = self.notebook_manager
-        km = self.kernel_manager
-        files = nbm.list_notebooks()
-        for f in files :
-            f['kernel_id'] = km.kernel_for_notebook(f['notebook_id'])
-        self.finish(jsonapi.dumps(files))
-
-    @web.authenticated
-    def post(self):
-        nbm = self.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', '{0}notebooks/{1}'.format(self.base_project_url, notebook_id))
-        self.finish(jsonapi.dumps(notebook_id))
-
-
-class NotebookHandler(IPythonHandler):
-
-    SUPPORTED_METHODS = ('GET', 'PUT', 'DELETE')
-
-    @authenticate_unless_readonly
-    def get(self, notebook_id):
-        nbm = self.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.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):
-        self.notebook_manager.delete_notebook(notebook_id)
-        self.set_status(204)
-        self.finish()
-
-
-class NotebookCheckpointsHandler(IPythonHandler):
-    
-    SUPPORTED_METHODS = ('GET', 'POST')
-    
-    @web.authenticated
-    def get(self, notebook_id):
-        """get lists checkpoints for a notebook"""
-        nbm = self.notebook_manager
-        checkpoints = nbm.list_checkpoints(notebook_id)
-        data = jsonapi.dumps(checkpoints, default=date_default)
-        self.finish(data)
-    
-    @web.authenticated
-    def post(self, notebook_id):
-        """post creates a new checkpoint"""
-        nbm = self.notebook_manager
-        checkpoint = nbm.create_checkpoint(notebook_id)
-        data = jsonapi.dumps(checkpoint, default=date_default)
-        self.set_header('Location', '{0}notebooks/{1}/checkpoints/{2}'.format(
-            self.base_project_url, notebook_id, checkpoint['checkpoint_id']
-        ))
-        
-        self.finish(data)
-
-
-class ModifyNotebookCheckpointsHandler(IPythonHandler):
-    
-    SUPPORTED_METHODS = ('POST', 'DELETE')
-    
-    @web.authenticated
-    def post(self, notebook_id, checkpoint_id):
-        """post restores a notebook from a checkpoint"""
-        nbm = self.notebook_manager
-        nbm.restore_checkpoint(notebook_id, checkpoint_id)
-        self.set_status(204)
-        self.finish()
-    
-    @web.authenticated
-    def delete(self, notebook_id, checkpoint_id):
-        """delete clears a checkpoint for a given notebook"""
-        nbm = self.notebook_manager
-        nbm.delte_checkpoint(notebook_id, checkpoint_id)
-        self.set_status(204)
-        self.finish()
-
-
-class NotebookCopyHandler(IPythonHandler):
-
-    @web.authenticated
-    def get(self, notebook_id):
-        notebook_id = self.notebook_manager.copy_notebook(notebook_id)
-        self.redirect('/'+urljoin(self.base_project_url, notebook_id))
-
-
-#-----------------------------------------------------------------------------
-# Cluster handlers
-#-----------------------------------------------------------------------------
-
-
-class MainClusterHandler(IPythonHandler):
-
-    @web.authenticated
-    def get(self):
-        self.finish(jsonapi.dumps(self.cluster_manager.list_profiles()))
-
-
-class ClusterProfileHandler(IPythonHandler):
-
-    @web.authenticated
-    def get(self, profile):
-        self.finish(jsonapi.dumps(self.cluster_manager.profile_info(profile)))
-
-
-class ClusterActionHandler(IPythonHandler):
-
-    @web.authenticated
-    def post(self, profile, action):
-        cm = self.cluster_manager
-        if action == 'start':
-            n = self.get_argument('n',default=None)
-            if n is None:
-                data = cm.start_cluster(profile)
-            else:
-                data = cm.start_cluster(profile, int(n))
-        if action == 'stop':
-            data = cm.stop_cluster(profile)
-        self.finish(jsonapi.dumps(data))
-
-
-#-----------------------------------------------------------------------------
-# File handler
-#-----------------------------------------------------------------------------
-
-# to minimize subclass changes:
-HTTPError = web.HTTPError
-
-class FileFindHandler(web.StaticFileHandler):
-    """subclass of StaticFileHandler for serving files from a search path"""
-    
-    _static_paths = {}
-    # _lock is needed for tornado < 2.2.0 compat
-    _lock = threading.Lock()  # protects _static_hashes
-    
-    def initialize(self, path, default_filename=None):
-        if isinstance(path, basestring):
-            path = [path]
-        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):
-                raise HTTPError(403, "%s is not in root static directory", path)
-        
-            cls._static_paths[path] = abspath
-            return abspath
-    
-    def get(self, path, include_body=True):
-        path = self.parse_url_path(path)
-        
-        # begin subclass override
-        abspath = self.locate_file(path, self.roots)
-        # end subclass override
-        
-        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):
-            raise HTTPError(404)
-        if not os.path.isfile(abspath):
-            raise HTTPError(403, "%s is not a file", path)
-
-        stat_result = os.stat(abspath)
-        modified = datetime.datetime.utcfromtimestamp(stat_result[stat.ST_MTIME])
-
-        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)
-            if_since = datetime.datetime(*date_tuple[:6])
-            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:
-        static_paths = settings['static_path']
-        if isinstance(static_paths, basestring):
-            static_paths = [static_paths]
-        roots = tuple(
-            os.path.abspath(os.path.expanduser(p)) + os.path.sep for p in static_paths
-        )
-
-        try:
-            abs_path = filefind(path, roots)
-        except IOError:
-            app_log.error("Could not find static file %r", path)
-            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:
-                    app_log.error("Could not open static file %r", path)
-                    hashes[abs_path] = None
-            hsh = hashes.get(abs_path)
-            if hsh:
-                return hsh[:5]
-        return None
-
-
-    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
-
-
diff --git a/IPython/frontend/html/notebook/handlers/clustersapi.py b/IPython/frontend/html/notebook/handlers/clustersapi.py
index 32ec552..25b35d7 100644
--- a/IPython/frontend/html/notebook/handlers/clustersapi.py
+++ b/IPython/frontend/html/notebook/handlers/clustersapi.py
@@ -1,4 +1,4 @@
-"""Tornado handlers for the notebook.
+"""Tornado handlers for cluster web service.
 
 Authors:
 
@@ -6,7 +6,7 @@ Authors:
 """
 
 #-----------------------------------------------------------------------------
-#  Copyright (C) 2008-2011  The IPython Development Team
+#  Copyright (C) 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.
@@ -16,723 +16,11 @@ Authors:
 # Imports
 #-----------------------------------------------------------------------------
 
-import Cookie
-import datetime
-import email.utils
-import hashlib
-import logging
-import mimetypes
-import os
-import stat
-import threading
-import time
-import uuid
-
-from tornado.escape import url_escape
 from tornado import web
-from tornado import websocket
-
-try:
-    from tornado.log import app_log
-except ImportError:
-    app_log = logging.getLogger()
 
-from zmq.eventloop import ioloop
 from zmq.utils import jsonapi
 
-from IPython.config import Application
-from IPython.external.decorator import decorator
-from IPython.kernel.zmq.session import Session
-from IPython.lib.security import passwd_check
-from IPython.utils.jsonutil import date_default
-from IPython.utils.path import filefind
-from IPython.utils.py3compat import PY3
-
-try:
-    from docutils.core import publish_string
-except ImportError:
-    publish_string = None
-
-#-----------------------------------------------------------------------------
-# Monkeypatch for Tornado <= 2.1.1 - Remove when no longer necessary!
-#-----------------------------------------------------------------------------
-
-# 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
-
-if tornado.version_info <= (2,1,1):
-
-    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()
-
-    websocket.WebSocketHandler._execute = _execute
-    del _execute
-
-#-----------------------------------------------------------------------------
-# Decorator for disabling read-only handlers
-#-----------------------------------------------------------------------------
-
-@decorator
-def not_if_readonly(f, self, *args, **kwargs):
-    if self.settings.get('read_only', False):
-        raise web.HTTPError(403, "Notebook server is read-only")
-    else:
-        return f(self, *args, **kwargs)
-
-@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)
-
-    if self.settings.get('read_only', False):
-        return f(self, *args, **kwargs)
-    else:
-        return auth_f(self, *args, **kwargs)
-
-def urljoin(*pieces):
-    """Join components of url into a relative url
-
-    Use to prevent double slash when joining subpath
-    """
-    striped = [s.strip('/') for s in pieces]
-    return '/'.join(s for s in striped if s)
-
-#-----------------------------------------------------------------------------
-# Top-level handlers
-#-----------------------------------------------------------------------------
-
-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):
-    """A RequestHandler with an authenticated user."""
-
-    def clear_login_cookie(self):
-        self.clear_cookie(self.cookie_name)
-    
-    def get_current_user(self):
-        user_id = self.get_secure_cookie(self.cookie_name)
-        # For now the user_id should not return empty, but it could eventually
-        if user_id == '':
-            user_id = 'anonymous'
-        if user_id is None:
-            # prevent extra Invalid cookie sig warnings:
-            self.clear_login_cookie()
-            if not self.read_only and not self.login_available:
-                user_id = 'anonymous'
-        return user_id
-
-    @property
-    def cookie_name(self):
-        return self.settings.get('cookie_name', '')
-    
-    @property
-    def password(self):
-        """our password"""
-        return self.settings.get('password', '')
-    
-    @property
-    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.
-
-        """
-        return bool(self.settings.get('password', ''))
-
-    @property
-    def read_only(self):
-        """Is the notebook read-only?
-
-        """
-        return self.settings.get('read_only', False)
-
-
-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)
-    
-    @property
-    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
-    def use_less(self):
-        """Use less instead of css in templates"""
-        return self.settings.get('use_less', False)
-    
-    #---------------------------------------------------------------
-    # URLs
-    #---------------------------------------------------------------
-    
-    @property
-    def ws_url(self):
-        """websocket url matching the current request
-
-        turns http[s]://host[:port] into
-                ws[s]://host[:port]
-        """
-        proto = self.request.protocol.replace('http', 'ws')
-        host = self.settings.get('websocket_host', '')
-        # default to config value
-        if host == '':
-            host = self.request.host # get from request
-        return "%s://%s" % (proto, host)
-    
-    @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,
-        )
-
-class AuthenticatedFileHandler(IPythonHandler, web.StaticFileHandler):
-    """static files should only be accessible when logged in"""
-
-    @authenticate_unless_readonly
-    def get(self, path):
-        return web.StaticFileHandler.get(self, path)
-
-
-class ProjectDashboardHandler(IPythonHandler):
-
-    @authenticate_unless_readonly
-    def get(self):
-        self.write(self.render_template('projectdashboard.html',
-            project=self.project,
-            project_component=self.project.split('/'),
-        ))
-
-
-class LoginHandler(IPythonHandler):
-
-    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,
-        ))
-
-    def get(self):
-        if self.current_user:
-            self.redirect(self.get_argument('next', default=self.base_project_url))
-        else:
-            self._render()
-
-    def post(self):
-        pwd = self.get_argument('password', default=u'')
-        if self.login_available:
-            if passwd_check(self.password, pwd):
-                self.set_secure_cookie(self.cookie_name, str(uuid.uuid4()))
-            else:
-                self._render(message={'error': 'Invalid password'})
-                return
-
-        self.redirect(self.get_argument('next', default=self.base_project_url))
-
-
-class LogoutHandler(IPythonHandler):
-
-    def get(self):
-        self.clear_login_cookie()
-        if self.login_available:
-            message = {'info': 'Successfully logged out.'}
-        else:
-            message = {'warning': 'Cannot log out.  Notebook authentication '
-                       'is disabled.'}
-        self.write(self.render_template('logout.html',
-                    message=message))
-
-
-class NewHandler(IPythonHandler):
-
-    @web.authenticated
-    def get(self):
-        notebook_id = self.notebook_manager.new_notebook()
-        self.redirect('/' + urljoin(self.base_project_url, notebook_id))
-
-class NamedNotebookHandler(IPythonHandler):
-
-    @authenticate_unless_readonly
-    def get(self, notebook_id):
-        nbm = self.notebook_manager
-        if not nbm.notebook_exists(notebook_id):
-            raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)       
-        self.write(self.render_template('notebook.html',
-            project=self.project,
-            notebook_id=notebook_id,
-            kill_kernel=False,
-            mathjax_url=self.mathjax_url,
-            )
-        )
-
-
-#-----------------------------------------------------------------------------
-# Kernel handlers
-#-----------------------------------------------------------------------------
-
-
-class MainKernelHandler(IPythonHandler):
-
-    @web.authenticated
-    def get(self):
-        km = self.kernel_manager
-        self.finish(jsonapi.dumps(km.list_kernel_ids()))
-
-    @web.authenticated
-    def post(self):
-        km = self.kernel_manager
-        nbm = self.notebook_manager
-        notebook_id = self.get_argument('notebook', default=None)
-        kernel_id = km.start_kernel(notebook_id, cwd=nbm.notebook_dir)
-        data = {'ws_url':self.ws_url,'kernel_id':kernel_id}
-        self.set_header('Location', '{0}kernels/{1}'.format(self.base_kernel_url, kernel_id))
-        self.finish(jsonapi.dumps(data))
-
-
-class KernelHandler(IPythonHandler):
-
-    SUPPORTED_METHODS = ('DELETE')
-
-    @web.authenticated
-    def delete(self, kernel_id):
-        km = self.kernel_manager
-        km.shutdown_kernel(kernel_id)
-        self.set_status(204)
-        self.finish()
-
-
-class KernelActionHandler(IPythonHandler):
-
-    @web.authenticated
-    def post(self, kernel_id, action):
-        km = self.kernel_manager
-        if action == 'interrupt':
-            km.interrupt_kernel(kernel_id)
-            self.set_status(204)
-        if action == 'restart':
-            km.restart_kernel(kernel_id)
-            data = {'ws_url':self.ws_url, 'kernel_id':kernel_id}
-            self.set_header('Location', '{0}kernels/{1}'.format(self.base_kernel_url, kernel_id))
-            self.write(jsonapi.dumps(data))
-        self.finish()
-
-
-class ZMQStreamHandler(websocket.WebSocketHandler):
-    
-    def clear_cookie(self, *args, **kwargs):
-        """meaningless for websockets"""
-        pass
-
-    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, default=date_default)
-
-    def _on_zmq_reply(self, msg_list):
-        # Sometimes this gets triggered when the on_close method is scheduled in the
-        # eventloop but hasn't been called.
-        if self.stream.closed(): return
-        try:
-            msg = self._reserialize_reply(msg_list)
-        except Exception:
-            self.log.critical("Malformed message: %r" % msg_list, exc_info=True)
-        else:
-            self.write_message(msg)
-
-    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
-
-
-class AuthenticatedZMQStreamHandler(ZMQStreamHandler, IPythonHandler):
-
-    def open(self, kernel_id):
-        self.kernel_id = kernel_id.decode('ascii')
-        self.session = Session(config=self.config)
-        self.save_on_message = self.on_message
-        self.on_message = self.on_first_message
-
-    def _inject_cookie_message(self, msg):
-        """Inject the first message, which is the document cookie,
-        for authentication."""
-        if not PY3 and isinstance(msg, unicode):
-            # Cookie constructor doesn't accept unicode strings
-            # under Python 2.x for some reason
-            msg = msg.encode('utf8', 'replace')
-        try:
-            identity, msg = msg.split(':', 1)
-            self.session.session = identity.decode('ascii')
-        except Exception:
-            logging.error("First ws message didn't have the form 'identity:[cookie]' - %r", msg)
-        
-        try:
-            self.request._cookies = Cookie.SimpleCookie(msg)
-        except:
-            self.log.warn("couldn't parse cookie string: %s",msg, exc_info=True)
-
-    def on_first_message(self, msg):
-        self._inject_cookie_message(msg)
-        if self.get_current_user() is None:
-            self.log.warn("Couldn't authenticate WebSocket connection")
-            raise web.HTTPError(403)
-        self.on_message = self.save_on_message
-
-
-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)
-        self.zmq_stream = meth(self.kernel_id, identity=self.session.bsession)
-    
-    def initialize(self, *args, **kwargs):
-        self.zmq_stream = None
-    
-    def on_first_message(self, msg):
-        try:
-            super(ZMQChannelHandler, self).on_first_message(msg)
-        except web.HTTPError:
-            self.close()
-            return
-        try:
-            self.create_stream()
-        except web.HTTPError:
-            # WebSockets don't response to traditional error codes so we
-            # close the connection.
-            if not self.stream.closed():
-                self.stream.close()
-            self.close()
-        else:
-            self.zmq_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.zmq_stream, msg)
-
-    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.
-        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):
-        km = self.kernel_manager
-        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',
-            )
-        super(IOPubHandler, self).on_close()
-    
-    def _send_status_message(self, status):
-        msg = self.session.msg("status",
-            {'execution_state': status}
-        )
-        self.write_message(jsonapi.dumps(msg, default=date_default))
-
-    def on_kernel_restarted(self):
-        logging.warn("kernel %s restarted", self.kernel_id)
-        self._send_status_message('restarting')
-
-    def on_restart_failed(self):
-        logging.error("kernel %s restarted failed!", self.kernel_id)
-        self._send_status_message('dead')
-    
-    def on_message(self, msg):
-        """IOPub messages make no sense"""
-        pass
-
-class ShellHandler(ZMQChannelHandler):
-    channel = 'shell'
-
-class StdinHandler(ZMQChannelHandler):
-    channel = 'stdin'
-
-
-#-----------------------------------------------------------------------------
-# Notebook web service handlers
-#-----------------------------------------------------------------------------
-
-class NotebookRedirectHandler(IPythonHandler):
-    
-    @authenticate_unless_readonly
-    def get(self, notebook_name):
-        # strip trailing .ipynb:
-        notebook_name = os.path.splitext(notebook_name)[0]
-        notebook_id = self.notebook_manager.rev_mapping.get(notebook_name, '')
-        if notebook_id:
-            url = self.settings.get('base_project_url', '/') + notebook_id
-            return self.redirect(url)
-        else:
-            raise HTTPError(404)
-
-
-class NotebookRootHandler(IPythonHandler):
-
-    @authenticate_unless_readonly
-    def get(self):
-        nbm = self.notebook_manager
-        km = self.kernel_manager
-        files = nbm.list_notebooks()
-        for f in files :
-            f['kernel_id'] = km.kernel_for_notebook(f['notebook_id'])
-        self.finish(jsonapi.dumps(files))
-
-    @web.authenticated
-    def post(self):
-        nbm = self.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', '{0}notebooks/{1}'.format(self.base_project_url, notebook_id))
-        self.finish(jsonapi.dumps(notebook_id))
-
-
-class NotebookHandler(IPythonHandler):
-
-    SUPPORTED_METHODS = ('GET', 'PUT', 'DELETE')
-
-    @authenticate_unless_readonly
-    def get(self, notebook_id):
-        nbm = self.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.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):
-        self.notebook_manager.delete_notebook(notebook_id)
-        self.set_status(204)
-        self.finish()
-
-
-class NotebookCheckpointsHandler(IPythonHandler):
-    
-    SUPPORTED_METHODS = ('GET', 'POST')
-    
-    @web.authenticated
-    def get(self, notebook_id):
-        """get lists checkpoints for a notebook"""
-        nbm = self.notebook_manager
-        checkpoints = nbm.list_checkpoints(notebook_id)
-        data = jsonapi.dumps(checkpoints, default=date_default)
-        self.finish(data)
-    
-    @web.authenticated
-    def post(self, notebook_id):
-        """post creates a new checkpoint"""
-        nbm = self.notebook_manager
-        checkpoint = nbm.create_checkpoint(notebook_id)
-        data = jsonapi.dumps(checkpoint, default=date_default)
-        self.set_header('Location', '{0}notebooks/{1}/checkpoints/{2}'.format(
-            self.base_project_url, notebook_id, checkpoint['checkpoint_id']
-        ))
-        
-        self.finish(data)
-
-
-class ModifyNotebookCheckpointsHandler(IPythonHandler):
-    
-    SUPPORTED_METHODS = ('POST', 'DELETE')
-    
-    @web.authenticated
-    def post(self, notebook_id, checkpoint_id):
-        """post restores a notebook from a checkpoint"""
-        nbm = self.notebook_manager
-        nbm.restore_checkpoint(notebook_id, checkpoint_id)
-        self.set_status(204)
-        self.finish()
-    
-    @web.authenticated
-    def delete(self, notebook_id, checkpoint_id):
-        """delete clears a checkpoint for a given notebook"""
-        nbm = self.notebook_manager
-        nbm.delte_checkpoint(notebook_id, checkpoint_id)
-        self.set_status(204)
-        self.finish()
-
-
-class NotebookCopyHandler(IPythonHandler):
-
-    @web.authenticated
-    def get(self, notebook_id):
-        notebook_id = self.notebook_manager.copy_notebook(notebook_id)
-        self.redirect('/'+urljoin(self.base_project_url, notebook_id))
-
+from .base import IPythonHandler
 
 #-----------------------------------------------------------------------------
 # Cluster handlers
@@ -767,165 +55,3 @@ class ClusterActionHandler(IPythonHandler):
         if action == 'stop':
             data = cm.stop_cluster(profile)
         self.finish(jsonapi.dumps(data))
-
-
-#-----------------------------------------------------------------------------
-# File handler
-#-----------------------------------------------------------------------------
-
-# to minimize subclass changes:
-HTTPError = web.HTTPError
-
-class FileFindHandler(web.StaticFileHandler):
-    """subclass of StaticFileHandler for serving files from a search path"""
-    
-    _static_paths = {}
-    # _lock is needed for tornado < 2.2.0 compat
-    _lock = threading.Lock()  # protects _static_hashes
-    
-    def initialize(self, path, default_filename=None):
-        if isinstance(path, basestring):
-            path = [path]
-        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):
-                raise HTTPError(403, "%s is not in root static directory", path)
-        
-            cls._static_paths[path] = abspath
-            return abspath
-    
-    def get(self, path, include_body=True):
-        path = self.parse_url_path(path)
-        
-        # begin subclass override
-        abspath = self.locate_file(path, self.roots)
-        # end subclass override
-        
-        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):
-            raise HTTPError(404)
-        if not os.path.isfile(abspath):
-            raise HTTPError(403, "%s is not a file", path)
-
-        stat_result = os.stat(abspath)
-        modified = datetime.datetime.utcfromtimestamp(stat_result[stat.ST_MTIME])
-
-        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)
-            if_since = datetime.datetime(*date_tuple[:6])
-            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:
-        static_paths = settings['static_path']
-        if isinstance(static_paths, basestring):
-            static_paths = [static_paths]
-        roots = tuple(
-            os.path.abspath(os.path.expanduser(p)) + os.path.sep for p in static_paths
-        )
-
-        try:
-            abs_path = filefind(path, roots)
-        except IOError:
-            app_log.error("Could not find static file %r", path)
-            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:
-                    app_log.error("Could not open static file %r", path)
-                    hashes[abs_path] = None
-            hsh = hashes.get(abs_path)
-            if hsh:
-                return hsh[:5]
-        return None
-
-
-    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
-
-
diff --git a/IPython/frontend/html/notebook/handlers/files.py b/IPython/frontend/html/notebook/handlers/files.py
new file mode 100644
index 0000000..d17518b
--- /dev/null
+++ b/IPython/frontend/html/notebook/handlers/files.py
@@ -0,0 +1,196 @@
+"""Tornado handlers handling general files.
+
+Authors:
+
+* Brian Granger
+"""
+
+#-----------------------------------------------------------------------------
+#  Copyright (C) 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
+#-----------------------------------------------------------------------------
+
+import datetime
+import email.utils
+import hashlib
+import logging
+import mimetypes
+import os
+import stat
+import threading
+
+from tornado import web
+
+try:
+    from tornado.log import app_log
+except ImportError:
+    app_log = logging.getLogger()
+
+from IPython.utils.path import filefind
+
+#-----------------------------------------------------------------------------
+# File handler
+#-----------------------------------------------------------------------------
+
+# to minimize subclass changes:
+HTTPError = web.HTTPError
+
+class FileFindHandler(web.StaticFileHandler):
+    """subclass of StaticFileHandler for serving files from a search path"""
+    
+    _static_paths = {}
+    # _lock is needed for tornado < 2.2.0 compat
+    _lock = threading.Lock()  # protects _static_hashes
+    
+    def initialize(self, path, default_filename=None):
+        if isinstance(path, basestring):
+            path = [path]
+        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):
+                raise HTTPError(403, "%s is not in root static directory", path)
+        
+            cls._static_paths[path] = abspath
+            return abspath
+    
+    def get(self, path, include_body=True):
+        path = self.parse_url_path(path)
+        
+        # begin subclass override
+        abspath = self.locate_file(path, self.roots)
+        # end subclass override
+        
+        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):
+            raise HTTPError(404)
+        if not os.path.isfile(abspath):
+            raise HTTPError(403, "%s is not a file", path)
+
+        stat_result = os.stat(abspath)
+        modified = datetime.datetime.utcfromtimestamp(stat_result[stat.ST_MTIME])
+
+        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)
+            if_since = datetime.datetime(*date_tuple[:6])
+            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:
+        static_paths = settings['static_path']
+        if isinstance(static_paths, basestring):
+            static_paths = [static_paths]
+        roots = tuple(
+            os.path.abspath(os.path.expanduser(p)) + os.path.sep for p in static_paths
+        )
+
+        try:
+            abs_path = filefind(path, roots)
+        except IOError:
+            app_log.error("Could not find static file %r", path)
+            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:
+                    app_log.error("Could not open static file %r", path)
+                    hashes[abs_path] = None
+            hsh = hashes.get(abs_path)
+            if hsh:
+                return hsh[:5]
+        return None
+
+
+    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
+
+
diff --git a/IPython/frontend/html/notebook/handlers/handlers.py b/IPython/frontend/html/notebook/handlers/handlers.py
deleted file mode 100644
index 32ec552..0000000
--- a/IPython/frontend/html/notebook/handlers/handlers.py
+++ /dev/null
@@ -1,931 +0,0 @@
-"""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
-#-----------------------------------------------------------------------------
-
-import Cookie
-import datetime
-import email.utils
-import hashlib
-import logging
-import mimetypes
-import os
-import stat
-import threading
-import time
-import uuid
-
-from tornado.escape import url_escape
-from tornado import web
-from tornado import websocket
-
-try:
-    from tornado.log import app_log
-except ImportError:
-    app_log = logging.getLogger()
-
-from zmq.eventloop import ioloop
-from zmq.utils import jsonapi
-
-from IPython.config import Application
-from IPython.external.decorator import decorator
-from IPython.kernel.zmq.session import Session
-from IPython.lib.security import passwd_check
-from IPython.utils.jsonutil import date_default
-from IPython.utils.path import filefind
-from IPython.utils.py3compat import PY3
-
-try:
-    from docutils.core import publish_string
-except ImportError:
-    publish_string = None
-
-#-----------------------------------------------------------------------------
-# Monkeypatch for Tornado <= 2.1.1 - Remove when no longer necessary!
-#-----------------------------------------------------------------------------
-
-# 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
-
-if tornado.version_info <= (2,1,1):
-
-    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()
-
-    websocket.WebSocketHandler._execute = _execute
-    del _execute
-
-#-----------------------------------------------------------------------------
-# Decorator for disabling read-only handlers
-#-----------------------------------------------------------------------------
-
-@decorator
-def not_if_readonly(f, self, *args, **kwargs):
-    if self.settings.get('read_only', False):
-        raise web.HTTPError(403, "Notebook server is read-only")
-    else:
-        return f(self, *args, **kwargs)
-
-@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)
-
-    if self.settings.get('read_only', False):
-        return f(self, *args, **kwargs)
-    else:
-        return auth_f(self, *args, **kwargs)
-
-def urljoin(*pieces):
-    """Join components of url into a relative url
-
-    Use to prevent double slash when joining subpath
-    """
-    striped = [s.strip('/') for s in pieces]
-    return '/'.join(s for s in striped if s)
-
-#-----------------------------------------------------------------------------
-# Top-level handlers
-#-----------------------------------------------------------------------------
-
-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):
-    """A RequestHandler with an authenticated user."""
-
-    def clear_login_cookie(self):
-        self.clear_cookie(self.cookie_name)
-    
-    def get_current_user(self):
-        user_id = self.get_secure_cookie(self.cookie_name)
-        # For now the user_id should not return empty, but it could eventually
-        if user_id == '':
-            user_id = 'anonymous'
-        if user_id is None:
-            # prevent extra Invalid cookie sig warnings:
-            self.clear_login_cookie()
-            if not self.read_only and not self.login_available:
-                user_id = 'anonymous'
-        return user_id
-
-    @property
-    def cookie_name(self):
-        return self.settings.get('cookie_name', '')
-    
-    @property
-    def password(self):
-        """our password"""
-        return self.settings.get('password', '')
-    
-    @property
-    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.
-
-        """
-        return bool(self.settings.get('password', ''))
-
-    @property
-    def read_only(self):
-        """Is the notebook read-only?
-
-        """
-        return self.settings.get('read_only', False)
-
-
-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)
-    
-    @property
-    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
-    def use_less(self):
-        """Use less instead of css in templates"""
-        return self.settings.get('use_less', False)
-    
-    #---------------------------------------------------------------
-    # URLs
-    #---------------------------------------------------------------
-    
-    @property
-    def ws_url(self):
-        """websocket url matching the current request
-
-        turns http[s]://host[:port] into
-                ws[s]://host[:port]
-        """
-        proto = self.request.protocol.replace('http', 'ws')
-        host = self.settings.get('websocket_host', '')
-        # default to config value
-        if host == '':
-            host = self.request.host # get from request
-        return "%s://%s" % (proto, host)
-    
-    @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,
-        )
-
-class AuthenticatedFileHandler(IPythonHandler, web.StaticFileHandler):
-    """static files should only be accessible when logged in"""
-
-    @authenticate_unless_readonly
-    def get(self, path):
-        return web.StaticFileHandler.get(self, path)
-
-
-class ProjectDashboardHandler(IPythonHandler):
-
-    @authenticate_unless_readonly
-    def get(self):
-        self.write(self.render_template('projectdashboard.html',
-            project=self.project,
-            project_component=self.project.split('/'),
-        ))
-
-
-class LoginHandler(IPythonHandler):
-
-    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,
-        ))
-
-    def get(self):
-        if self.current_user:
-            self.redirect(self.get_argument('next', default=self.base_project_url))
-        else:
-            self._render()
-
-    def post(self):
-        pwd = self.get_argument('password', default=u'')
-        if self.login_available:
-            if passwd_check(self.password, pwd):
-                self.set_secure_cookie(self.cookie_name, str(uuid.uuid4()))
-            else:
-                self._render(message={'error': 'Invalid password'})
-                return
-
-        self.redirect(self.get_argument('next', default=self.base_project_url))
-
-
-class LogoutHandler(IPythonHandler):
-
-    def get(self):
-        self.clear_login_cookie()
-        if self.login_available:
-            message = {'info': 'Successfully logged out.'}
-        else:
-            message = {'warning': 'Cannot log out.  Notebook authentication '
-                       'is disabled.'}
-        self.write(self.render_template('logout.html',
-                    message=message))
-
-
-class NewHandler(IPythonHandler):
-
-    @web.authenticated
-    def get(self):
-        notebook_id = self.notebook_manager.new_notebook()
-        self.redirect('/' + urljoin(self.base_project_url, notebook_id))
-
-class NamedNotebookHandler(IPythonHandler):
-
-    @authenticate_unless_readonly
-    def get(self, notebook_id):
-        nbm = self.notebook_manager
-        if not nbm.notebook_exists(notebook_id):
-            raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)       
-        self.write(self.render_template('notebook.html',
-            project=self.project,
-            notebook_id=notebook_id,
-            kill_kernel=False,
-            mathjax_url=self.mathjax_url,
-            )
-        )
-
-
-#-----------------------------------------------------------------------------
-# Kernel handlers
-#-----------------------------------------------------------------------------
-
-
-class MainKernelHandler(IPythonHandler):
-
-    @web.authenticated
-    def get(self):
-        km = self.kernel_manager
-        self.finish(jsonapi.dumps(km.list_kernel_ids()))
-
-    @web.authenticated
-    def post(self):
-        km = self.kernel_manager
-        nbm = self.notebook_manager
-        notebook_id = self.get_argument('notebook', default=None)
-        kernel_id = km.start_kernel(notebook_id, cwd=nbm.notebook_dir)
-        data = {'ws_url':self.ws_url,'kernel_id':kernel_id}
-        self.set_header('Location', '{0}kernels/{1}'.format(self.base_kernel_url, kernel_id))
-        self.finish(jsonapi.dumps(data))
-
-
-class KernelHandler(IPythonHandler):
-
-    SUPPORTED_METHODS = ('DELETE')
-
-    @web.authenticated
-    def delete(self, kernel_id):
-        km = self.kernel_manager
-        km.shutdown_kernel(kernel_id)
-        self.set_status(204)
-        self.finish()
-
-
-class KernelActionHandler(IPythonHandler):
-
-    @web.authenticated
-    def post(self, kernel_id, action):
-        km = self.kernel_manager
-        if action == 'interrupt':
-            km.interrupt_kernel(kernel_id)
-            self.set_status(204)
-        if action == 'restart':
-            km.restart_kernel(kernel_id)
-            data = {'ws_url':self.ws_url, 'kernel_id':kernel_id}
-            self.set_header('Location', '{0}kernels/{1}'.format(self.base_kernel_url, kernel_id))
-            self.write(jsonapi.dumps(data))
-        self.finish()
-
-
-class ZMQStreamHandler(websocket.WebSocketHandler):
-    
-    def clear_cookie(self, *args, **kwargs):
-        """meaningless for websockets"""
-        pass
-
-    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, default=date_default)
-
-    def _on_zmq_reply(self, msg_list):
-        # Sometimes this gets triggered when the on_close method is scheduled in the
-        # eventloop but hasn't been called.
-        if self.stream.closed(): return
-        try:
-            msg = self._reserialize_reply(msg_list)
-        except Exception:
-            self.log.critical("Malformed message: %r" % msg_list, exc_info=True)
-        else:
-            self.write_message(msg)
-
-    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
-
-
-class AuthenticatedZMQStreamHandler(ZMQStreamHandler, IPythonHandler):
-
-    def open(self, kernel_id):
-        self.kernel_id = kernel_id.decode('ascii')
-        self.session = Session(config=self.config)
-        self.save_on_message = self.on_message
-        self.on_message = self.on_first_message
-
-    def _inject_cookie_message(self, msg):
-        """Inject the first message, which is the document cookie,
-        for authentication."""
-        if not PY3 and isinstance(msg, unicode):
-            # Cookie constructor doesn't accept unicode strings
-            # under Python 2.x for some reason
-            msg = msg.encode('utf8', 'replace')
-        try:
-            identity, msg = msg.split(':', 1)
-            self.session.session = identity.decode('ascii')
-        except Exception:
-            logging.error("First ws message didn't have the form 'identity:[cookie]' - %r", msg)
-        
-        try:
-            self.request._cookies = Cookie.SimpleCookie(msg)
-        except:
-            self.log.warn("couldn't parse cookie string: %s",msg, exc_info=True)
-
-    def on_first_message(self, msg):
-        self._inject_cookie_message(msg)
-        if self.get_current_user() is None:
-            self.log.warn("Couldn't authenticate WebSocket connection")
-            raise web.HTTPError(403)
-        self.on_message = self.save_on_message
-
-
-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)
-        self.zmq_stream = meth(self.kernel_id, identity=self.session.bsession)
-    
-    def initialize(self, *args, **kwargs):
-        self.zmq_stream = None
-    
-    def on_first_message(self, msg):
-        try:
-            super(ZMQChannelHandler, self).on_first_message(msg)
-        except web.HTTPError:
-            self.close()
-            return
-        try:
-            self.create_stream()
-        except web.HTTPError:
-            # WebSockets don't response to traditional error codes so we
-            # close the connection.
-            if not self.stream.closed():
-                self.stream.close()
-            self.close()
-        else:
-            self.zmq_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.zmq_stream, msg)
-
-    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.
-        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):
-        km = self.kernel_manager
-        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',
-            )
-        super(IOPubHandler, self).on_close()
-    
-    def _send_status_message(self, status):
-        msg = self.session.msg("status",
-            {'execution_state': status}
-        )
-        self.write_message(jsonapi.dumps(msg, default=date_default))
-
-    def on_kernel_restarted(self):
-        logging.warn("kernel %s restarted", self.kernel_id)
-        self._send_status_message('restarting')
-
-    def on_restart_failed(self):
-        logging.error("kernel %s restarted failed!", self.kernel_id)
-        self._send_status_message('dead')
-    
-    def on_message(self, msg):
-        """IOPub messages make no sense"""
-        pass
-
-class ShellHandler(ZMQChannelHandler):
-    channel = 'shell'
-
-class StdinHandler(ZMQChannelHandler):
-    channel = 'stdin'
-
-
-#-----------------------------------------------------------------------------
-# Notebook web service handlers
-#-----------------------------------------------------------------------------
-
-class NotebookRedirectHandler(IPythonHandler):
-    
-    @authenticate_unless_readonly
-    def get(self, notebook_name):
-        # strip trailing .ipynb:
-        notebook_name = os.path.splitext(notebook_name)[0]
-        notebook_id = self.notebook_manager.rev_mapping.get(notebook_name, '')
-        if notebook_id:
-            url = self.settings.get('base_project_url', '/') + notebook_id
-            return self.redirect(url)
-        else:
-            raise HTTPError(404)
-
-
-class NotebookRootHandler(IPythonHandler):
-
-    @authenticate_unless_readonly
-    def get(self):
-        nbm = self.notebook_manager
-        km = self.kernel_manager
-        files = nbm.list_notebooks()
-        for f in files :
-            f['kernel_id'] = km.kernel_for_notebook(f['notebook_id'])
-        self.finish(jsonapi.dumps(files))
-
-    @web.authenticated
-    def post(self):
-        nbm = self.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', '{0}notebooks/{1}'.format(self.base_project_url, notebook_id))
-        self.finish(jsonapi.dumps(notebook_id))
-
-
-class NotebookHandler(IPythonHandler):
-
-    SUPPORTED_METHODS = ('GET', 'PUT', 'DELETE')
-
-    @authenticate_unless_readonly
-    def get(self, notebook_id):
-        nbm = self.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.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):
-        self.notebook_manager.delete_notebook(notebook_id)
-        self.set_status(204)
-        self.finish()
-
-
-class NotebookCheckpointsHandler(IPythonHandler):
-    
-    SUPPORTED_METHODS = ('GET', 'POST')
-    
-    @web.authenticated
-    def get(self, notebook_id):
-        """get lists checkpoints for a notebook"""
-        nbm = self.notebook_manager
-        checkpoints = nbm.list_checkpoints(notebook_id)
-        data = jsonapi.dumps(checkpoints, default=date_default)
-        self.finish(data)
-    
-    @web.authenticated
-    def post(self, notebook_id):
-        """post creates a new checkpoint"""
-        nbm = self.notebook_manager
-        checkpoint = nbm.create_checkpoint(notebook_id)
-        data = jsonapi.dumps(checkpoint, default=date_default)
-        self.set_header('Location', '{0}notebooks/{1}/checkpoints/{2}'.format(
-            self.base_project_url, notebook_id, checkpoint['checkpoint_id']
-        ))
-        
-        self.finish(data)
-
-
-class ModifyNotebookCheckpointsHandler(IPythonHandler):
-    
-    SUPPORTED_METHODS = ('POST', 'DELETE')
-    
-    @web.authenticated
-    def post(self, notebook_id, checkpoint_id):
-        """post restores a notebook from a checkpoint"""
-        nbm = self.notebook_manager
-        nbm.restore_checkpoint(notebook_id, checkpoint_id)
-        self.set_status(204)
-        self.finish()
-    
-    @web.authenticated
-    def delete(self, notebook_id, checkpoint_id):
-        """delete clears a checkpoint for a given notebook"""
-        nbm = self.notebook_manager
-        nbm.delte_checkpoint(notebook_id, checkpoint_id)
-        self.set_status(204)
-        self.finish()
-
-
-class NotebookCopyHandler(IPythonHandler):
-
-    @web.authenticated
-    def get(self, notebook_id):
-        notebook_id = self.notebook_manager.copy_notebook(notebook_id)
-        self.redirect('/'+urljoin(self.base_project_url, notebook_id))
-
-
-#-----------------------------------------------------------------------------
-# Cluster handlers
-#-----------------------------------------------------------------------------
-
-
-class MainClusterHandler(IPythonHandler):
-
-    @web.authenticated
-    def get(self):
-        self.finish(jsonapi.dumps(self.cluster_manager.list_profiles()))
-
-
-class ClusterProfileHandler(IPythonHandler):
-
-    @web.authenticated
-    def get(self, profile):
-        self.finish(jsonapi.dumps(self.cluster_manager.profile_info(profile)))
-
-
-class ClusterActionHandler(IPythonHandler):
-
-    @web.authenticated
-    def post(self, profile, action):
-        cm = self.cluster_manager
-        if action == 'start':
-            n = self.get_argument('n',default=None)
-            if n is None:
-                data = cm.start_cluster(profile)
-            else:
-                data = cm.start_cluster(profile, int(n))
-        if action == 'stop':
-            data = cm.stop_cluster(profile)
-        self.finish(jsonapi.dumps(data))
-
-
-#-----------------------------------------------------------------------------
-# File handler
-#-----------------------------------------------------------------------------
-
-# to minimize subclass changes:
-HTTPError = web.HTTPError
-
-class FileFindHandler(web.StaticFileHandler):
-    """subclass of StaticFileHandler for serving files from a search path"""
-    
-    _static_paths = {}
-    # _lock is needed for tornado < 2.2.0 compat
-    _lock = threading.Lock()  # protects _static_hashes
-    
-    def initialize(self, path, default_filename=None):
-        if isinstance(path, basestring):
-            path = [path]
-        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):
-                raise HTTPError(403, "%s is not in root static directory", path)
-        
-            cls._static_paths[path] = abspath
-            return abspath
-    
-    def get(self, path, include_body=True):
-        path = self.parse_url_path(path)
-        
-        # begin subclass override
-        abspath = self.locate_file(path, self.roots)
-        # end subclass override
-        
-        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):
-            raise HTTPError(404)
-        if not os.path.isfile(abspath):
-            raise HTTPError(403, "%s is not a file", path)
-
-        stat_result = os.stat(abspath)
-        modified = datetime.datetime.utcfromtimestamp(stat_result[stat.ST_MTIME])
-
-        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)
-            if_since = datetime.datetime(*date_tuple[:6])
-            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:
-        static_paths = settings['static_path']
-        if isinstance(static_paths, basestring):
-            static_paths = [static_paths]
-        roots = tuple(
-            os.path.abspath(os.path.expanduser(p)) + os.path.sep for p in static_paths
-        )
-
-        try:
-            abs_path = filefind(path, roots)
-        except IOError:
-            app_log.error("Could not find static file %r", path)
-            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:
-                    app_log.error("Could not open static file %r", path)
-                    hashes[abs_path] = None
-            hsh = hashes.get(abs_path)
-            if hsh:
-                return hsh[:5]
-        return None
-
-
-    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
-
-
diff --git a/IPython/frontend/html/notebook/handlers/kernelsapi.py b/IPython/frontend/html/notebook/handlers/kernelsapi.py
index 32ec552..c1519d3 100644
--- a/IPython/frontend/html/notebook/handlers/kernelsapi.py
+++ b/IPython/frontend/html/notebook/handlers/kernelsapi.py
@@ -17,368 +17,17 @@ Authors:
 #-----------------------------------------------------------------------------
 
 import Cookie
-import datetime
-import email.utils
-import hashlib
 import logging
-import mimetypes
-import os
-import stat
-import threading
-import time
-import uuid
-
-from tornado.escape import url_escape
 from tornado import web
 from tornado import websocket
 
-try:
-    from tornado.log import app_log
-except ImportError:
-    app_log = logging.getLogger()
-
-from zmq.eventloop import ioloop
 from zmq.utils import jsonapi
 
-from IPython.config import Application
-from IPython.external.decorator import decorator
 from IPython.kernel.zmq.session import Session
-from IPython.lib.security import passwd_check
 from IPython.utils.jsonutil import date_default
-from IPython.utils.path import filefind
 from IPython.utils.py3compat import PY3
 
-try:
-    from docutils.core import publish_string
-except ImportError:
-    publish_string = None
-
-#-----------------------------------------------------------------------------
-# Monkeypatch for Tornado <= 2.1.1 - Remove when no longer necessary!
-#-----------------------------------------------------------------------------
-
-# 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
-
-if tornado.version_info <= (2,1,1):
-
-    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()
-
-    websocket.WebSocketHandler._execute = _execute
-    del _execute
-
-#-----------------------------------------------------------------------------
-# Decorator for disabling read-only handlers
-#-----------------------------------------------------------------------------
-
-@decorator
-def not_if_readonly(f, self, *args, **kwargs):
-    if self.settings.get('read_only', False):
-        raise web.HTTPError(403, "Notebook server is read-only")
-    else:
-        return f(self, *args, **kwargs)
-
-@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)
-
-    if self.settings.get('read_only', False):
-        return f(self, *args, **kwargs)
-    else:
-        return auth_f(self, *args, **kwargs)
-
-def urljoin(*pieces):
-    """Join components of url into a relative url
-
-    Use to prevent double slash when joining subpath
-    """
-    striped = [s.strip('/') for s in pieces]
-    return '/'.join(s for s in striped if s)
-
-#-----------------------------------------------------------------------------
-# Top-level handlers
-#-----------------------------------------------------------------------------
-
-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):
-    """A RequestHandler with an authenticated user."""
-
-    def clear_login_cookie(self):
-        self.clear_cookie(self.cookie_name)
-    
-    def get_current_user(self):
-        user_id = self.get_secure_cookie(self.cookie_name)
-        # For now the user_id should not return empty, but it could eventually
-        if user_id == '':
-            user_id = 'anonymous'
-        if user_id is None:
-            # prevent extra Invalid cookie sig warnings:
-            self.clear_login_cookie()
-            if not self.read_only and not self.login_available:
-                user_id = 'anonymous'
-        return user_id
-
-    @property
-    def cookie_name(self):
-        return self.settings.get('cookie_name', '')
-    
-    @property
-    def password(self):
-        """our password"""
-        return self.settings.get('password', '')
-    
-    @property
-    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.
-
-        """
-        return bool(self.settings.get('password', ''))
-
-    @property
-    def read_only(self):
-        """Is the notebook read-only?
-
-        """
-        return self.settings.get('read_only', False)
-
-
-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)
-    
-    @property
-    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
-    def use_less(self):
-        """Use less instead of css in templates"""
-        return self.settings.get('use_less', False)
-    
-    #---------------------------------------------------------------
-    # URLs
-    #---------------------------------------------------------------
-    
-    @property
-    def ws_url(self):
-        """websocket url matching the current request
-
-        turns http[s]://host[:port] into
-                ws[s]://host[:port]
-        """
-        proto = self.request.protocol.replace('http', 'ws')
-        host = self.settings.get('websocket_host', '')
-        # default to config value
-        if host == '':
-            host = self.request.host # get from request
-        return "%s://%s" % (proto, host)
-    
-    @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,
-        )
-
-class AuthenticatedFileHandler(IPythonHandler, web.StaticFileHandler):
-    """static files should only be accessible when logged in"""
-
-    @authenticate_unless_readonly
-    def get(self, path):
-        return web.StaticFileHandler.get(self, path)
-
-
-class ProjectDashboardHandler(IPythonHandler):
-
-    @authenticate_unless_readonly
-    def get(self):
-        self.write(self.render_template('projectdashboard.html',
-            project=self.project,
-            project_component=self.project.split('/'),
-        ))
-
-
-class LoginHandler(IPythonHandler):
-
-    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,
-        ))
-
-    def get(self):
-        if self.current_user:
-            self.redirect(self.get_argument('next', default=self.base_project_url))
-        else:
-            self._render()
-
-    def post(self):
-        pwd = self.get_argument('password', default=u'')
-        if self.login_available:
-            if passwd_check(self.password, pwd):
-                self.set_secure_cookie(self.cookie_name, str(uuid.uuid4()))
-            else:
-                self._render(message={'error': 'Invalid password'})
-                return
-
-        self.redirect(self.get_argument('next', default=self.base_project_url))
-
-
-class LogoutHandler(IPythonHandler):
-
-    def get(self):
-        self.clear_login_cookie()
-        if self.login_available:
-            message = {'info': 'Successfully logged out.'}
-        else:
-            message = {'warning': 'Cannot log out.  Notebook authentication '
-                       'is disabled.'}
-        self.write(self.render_template('logout.html',
-                    message=message))
-
-
-class NewHandler(IPythonHandler):
-
-    @web.authenticated
-    def get(self):
-        notebook_id = self.notebook_manager.new_notebook()
-        self.redirect('/' + urljoin(self.base_project_url, notebook_id))
-
-class NamedNotebookHandler(IPythonHandler):
-
-    @authenticate_unless_readonly
-    def get(self, notebook_id):
-        nbm = self.notebook_manager
-        if not nbm.notebook_exists(notebook_id):
-            raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)       
-        self.write(self.render_template('notebook.html',
-            project=self.project,
-            notebook_id=notebook_id,
-            kill_kernel=False,
-            mathjax_url=self.mathjax_url,
-            )
-        )
-
+from .base import IPythonHandler
 
 #-----------------------------------------------------------------------------
 # Kernel handlers
@@ -600,332 +249,3 @@ class ShellHandler(ZMQChannelHandler):
 
 class StdinHandler(ZMQChannelHandler):
     channel = 'stdin'
-
-
-#-----------------------------------------------------------------------------
-# Notebook web service handlers
-#-----------------------------------------------------------------------------
-
-class NotebookRedirectHandler(IPythonHandler):
-    
-    @authenticate_unless_readonly
-    def get(self, notebook_name):
-        # strip trailing .ipynb:
-        notebook_name = os.path.splitext(notebook_name)[0]
-        notebook_id = self.notebook_manager.rev_mapping.get(notebook_name, '')
-        if notebook_id:
-            url = self.settings.get('base_project_url', '/') + notebook_id
-            return self.redirect(url)
-        else:
-            raise HTTPError(404)
-
-
-class NotebookRootHandler(IPythonHandler):
-
-    @authenticate_unless_readonly
-    def get(self):
-        nbm = self.notebook_manager
-        km = self.kernel_manager
-        files = nbm.list_notebooks()
-        for f in files :
-            f['kernel_id'] = km.kernel_for_notebook(f['notebook_id'])
-        self.finish(jsonapi.dumps(files))
-
-    @web.authenticated
-    def post(self):
-        nbm = self.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', '{0}notebooks/{1}'.format(self.base_project_url, notebook_id))
-        self.finish(jsonapi.dumps(notebook_id))
-
-
-class NotebookHandler(IPythonHandler):
-
-    SUPPORTED_METHODS = ('GET', 'PUT', 'DELETE')
-
-    @authenticate_unless_readonly
-    def get(self, notebook_id):
-        nbm = self.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.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):
-        self.notebook_manager.delete_notebook(notebook_id)
-        self.set_status(204)
-        self.finish()
-
-
-class NotebookCheckpointsHandler(IPythonHandler):
-    
-    SUPPORTED_METHODS = ('GET', 'POST')
-    
-    @web.authenticated
-    def get(self, notebook_id):
-        """get lists checkpoints for a notebook"""
-        nbm = self.notebook_manager
-        checkpoints = nbm.list_checkpoints(notebook_id)
-        data = jsonapi.dumps(checkpoints, default=date_default)
-        self.finish(data)
-    
-    @web.authenticated
-    def post(self, notebook_id):
-        """post creates a new checkpoint"""
-        nbm = self.notebook_manager
-        checkpoint = nbm.create_checkpoint(notebook_id)
-        data = jsonapi.dumps(checkpoint, default=date_default)
-        self.set_header('Location', '{0}notebooks/{1}/checkpoints/{2}'.format(
-            self.base_project_url, notebook_id, checkpoint['checkpoint_id']
-        ))
-        
-        self.finish(data)
-
-
-class ModifyNotebookCheckpointsHandler(IPythonHandler):
-    
-    SUPPORTED_METHODS = ('POST', 'DELETE')
-    
-    @web.authenticated
-    def post(self, notebook_id, checkpoint_id):
-        """post restores a notebook from a checkpoint"""
-        nbm = self.notebook_manager
-        nbm.restore_checkpoint(notebook_id, checkpoint_id)
-        self.set_status(204)
-        self.finish()
-    
-    @web.authenticated
-    def delete(self, notebook_id, checkpoint_id):
-        """delete clears a checkpoint for a given notebook"""
-        nbm = self.notebook_manager
-        nbm.delte_checkpoint(notebook_id, checkpoint_id)
-        self.set_status(204)
-        self.finish()
-
-
-class NotebookCopyHandler(IPythonHandler):
-
-    @web.authenticated
-    def get(self, notebook_id):
-        notebook_id = self.notebook_manager.copy_notebook(notebook_id)
-        self.redirect('/'+urljoin(self.base_project_url, notebook_id))
-
-
-#-----------------------------------------------------------------------------
-# Cluster handlers
-#-----------------------------------------------------------------------------
-
-
-class MainClusterHandler(IPythonHandler):
-
-    @web.authenticated
-    def get(self):
-        self.finish(jsonapi.dumps(self.cluster_manager.list_profiles()))
-
-
-class ClusterProfileHandler(IPythonHandler):
-
-    @web.authenticated
-    def get(self, profile):
-        self.finish(jsonapi.dumps(self.cluster_manager.profile_info(profile)))
-
-
-class ClusterActionHandler(IPythonHandler):
-
-    @web.authenticated
-    def post(self, profile, action):
-        cm = self.cluster_manager
-        if action == 'start':
-            n = self.get_argument('n',default=None)
-            if n is None:
-                data = cm.start_cluster(profile)
-            else:
-                data = cm.start_cluster(profile, int(n))
-        if action == 'stop':
-            data = cm.stop_cluster(profile)
-        self.finish(jsonapi.dumps(data))
-
-
-#-----------------------------------------------------------------------------
-# File handler
-#-----------------------------------------------------------------------------
-
-# to minimize subclass changes:
-HTTPError = web.HTTPError
-
-class FileFindHandler(web.StaticFileHandler):
-    """subclass of StaticFileHandler for serving files from a search path"""
-    
-    _static_paths = {}
-    # _lock is needed for tornado < 2.2.0 compat
-    _lock = threading.Lock()  # protects _static_hashes
-    
-    def initialize(self, path, default_filename=None):
-        if isinstance(path, basestring):
-            path = [path]
-        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):
-                raise HTTPError(403, "%s is not in root static directory", path)
-        
-            cls._static_paths[path] = abspath
-            return abspath
-    
-    def get(self, path, include_body=True):
-        path = self.parse_url_path(path)
-        
-        # begin subclass override
-        abspath = self.locate_file(path, self.roots)
-        # end subclass override
-        
-        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):
-            raise HTTPError(404)
-        if not os.path.isfile(abspath):
-            raise HTTPError(403, "%s is not a file", path)
-
-        stat_result = os.stat(abspath)
-        modified = datetime.datetime.utcfromtimestamp(stat_result[stat.ST_MTIME])
-
-        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)
-            if_since = datetime.datetime(*date_tuple[:6])
-            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:
-        static_paths = settings['static_path']
-        if isinstance(static_paths, basestring):
-            static_paths = [static_paths]
-        roots = tuple(
-            os.path.abspath(os.path.expanduser(p)) + os.path.sep for p in static_paths
-        )
-
-        try:
-            abs_path = filefind(path, roots)
-        except IOError:
-            app_log.error("Could not find static file %r", path)
-            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:
-                    app_log.error("Could not open static file %r", path)
-                    hashes[abs_path] = None
-            hsh = hashes.get(abs_path)
-            if hsh:
-                return hsh[:5]
-        return None
-
-
-    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
-
-
diff --git a/IPython/frontend/html/notebook/handlers/login.py b/IPython/frontend/html/notebook/handlers/login.py
index 32ec552..27d953c 100644
--- a/IPython/frontend/html/notebook/handlers/login.py
+++ b/IPython/frontend/html/notebook/handlers/login.py
@@ -1,4 +1,4 @@
-"""Tornado handlers for the notebook.
+"""Tornado handlers logging into the notebook.
 
 Authors:
 
@@ -6,7 +6,7 @@ Authors:
 """
 
 #-----------------------------------------------------------------------------
-#  Copyright (C) 2008-2011  The IPython Development Team
+#  Copyright (C) 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.
@@ -16,307 +16,18 @@ Authors:
 # Imports
 #-----------------------------------------------------------------------------
 
-import Cookie
-import datetime
-import email.utils
-import hashlib
-import logging
-import mimetypes
-import os
-import stat
-import threading
-import time
 import uuid
 
 from tornado.escape import url_escape
-from tornado import web
-from tornado import websocket
 
-try:
-    from tornado.log import app_log
-except ImportError:
-    app_log = logging.getLogger()
-
-from zmq.eventloop import ioloop
-from zmq.utils import jsonapi
-
-from IPython.config import Application
-from IPython.external.decorator import decorator
-from IPython.kernel.zmq.session import Session
 from IPython.lib.security import passwd_check
-from IPython.utils.jsonutil import date_default
-from IPython.utils.path import filefind
-from IPython.utils.py3compat import PY3
-
-try:
-    from docutils.core import publish_string
-except ImportError:
-    publish_string = None
-
-#-----------------------------------------------------------------------------
-# Monkeypatch for Tornado <= 2.1.1 - Remove when no longer necessary!
-#-----------------------------------------------------------------------------
-
-# 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
-
-if tornado.version_info <= (2,1,1):
-
-    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()
-
-    websocket.WebSocketHandler._execute = _execute
-    del _execute
-
-#-----------------------------------------------------------------------------
-# Decorator for disabling read-only handlers
-#-----------------------------------------------------------------------------
-
-@decorator
-def not_if_readonly(f, self, *args, **kwargs):
-    if self.settings.get('read_only', False):
-        raise web.HTTPError(403, "Notebook server is read-only")
-    else:
-        return f(self, *args, **kwargs)
 
-@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)
-
-    if self.settings.get('read_only', False):
-        return f(self, *args, **kwargs)
-    else:
-        return auth_f(self, *args, **kwargs)
-
-def urljoin(*pieces):
-    """Join components of url into a relative url
-
-    Use to prevent double slash when joining subpath
-    """
-    striped = [s.strip('/') for s in pieces]
-    return '/'.join(s for s in striped if s)
+from .base import IPythonHandler
 
 #-----------------------------------------------------------------------------
-# Top-level handlers
+# Handler
 #-----------------------------------------------------------------------------
 
-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):
-    """A RequestHandler with an authenticated user."""
-
-    def clear_login_cookie(self):
-        self.clear_cookie(self.cookie_name)
-    
-    def get_current_user(self):
-        user_id = self.get_secure_cookie(self.cookie_name)
-        # For now the user_id should not return empty, but it could eventually
-        if user_id == '':
-            user_id = 'anonymous'
-        if user_id is None:
-            # prevent extra Invalid cookie sig warnings:
-            self.clear_login_cookie()
-            if not self.read_only and not self.login_available:
-                user_id = 'anonymous'
-        return user_id
-
-    @property
-    def cookie_name(self):
-        return self.settings.get('cookie_name', '')
-    
-    @property
-    def password(self):
-        """our password"""
-        return self.settings.get('password', '')
-    
-    @property
-    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.
-
-        """
-        return bool(self.settings.get('password', ''))
-
-    @property
-    def read_only(self):
-        """Is the notebook read-only?
-
-        """
-        return self.settings.get('read_only', False)
-
-
-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)
-    
-    @property
-    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
-    def use_less(self):
-        """Use less instead of css in templates"""
-        return self.settings.get('use_less', False)
-    
-    #---------------------------------------------------------------
-    # URLs
-    #---------------------------------------------------------------
-    
-    @property
-    def ws_url(self):
-        """websocket url matching the current request
-
-        turns http[s]://host[:port] into
-                ws[s]://host[:port]
-        """
-        proto = self.request.protocol.replace('http', 'ws')
-        host = self.settings.get('websocket_host', '')
-        # default to config value
-        if host == '':
-            host = self.request.host # get from request
-        return "%s://%s" % (proto, host)
-    
-    @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,
-        )
-
-class AuthenticatedFileHandler(IPythonHandler, web.StaticFileHandler):
-    """static files should only be accessible when logged in"""
-
-    @authenticate_unless_readonly
-    def get(self, path):
-        return web.StaticFileHandler.get(self, path)
-
-
-class ProjectDashboardHandler(IPythonHandler):
-
-    @authenticate_unless_readonly
-    def get(self):
-        self.write(self.render_template('projectdashboard.html',
-            project=self.project,
-            project_component=self.project.split('/'),
-        ))
-
 
 class LoginHandler(IPythonHandler):
 
@@ -344,588 +55,3 @@ class LoginHandler(IPythonHandler):
         self.redirect(self.get_argument('next', default=self.base_project_url))
 
 
-class LogoutHandler(IPythonHandler):
-
-    def get(self):
-        self.clear_login_cookie()
-        if self.login_available:
-            message = {'info': 'Successfully logged out.'}
-        else:
-            message = {'warning': 'Cannot log out.  Notebook authentication '
-                       'is disabled.'}
-        self.write(self.render_template('logout.html',
-                    message=message))
-
-
-class NewHandler(IPythonHandler):
-
-    @web.authenticated
-    def get(self):
-        notebook_id = self.notebook_manager.new_notebook()
-        self.redirect('/' + urljoin(self.base_project_url, notebook_id))
-
-class NamedNotebookHandler(IPythonHandler):
-
-    @authenticate_unless_readonly
-    def get(self, notebook_id):
-        nbm = self.notebook_manager
-        if not nbm.notebook_exists(notebook_id):
-            raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)       
-        self.write(self.render_template('notebook.html',
-            project=self.project,
-            notebook_id=notebook_id,
-            kill_kernel=False,
-            mathjax_url=self.mathjax_url,
-            )
-        )
-
-
-#-----------------------------------------------------------------------------
-# Kernel handlers
-#-----------------------------------------------------------------------------
-
-
-class MainKernelHandler(IPythonHandler):
-
-    @web.authenticated
-    def get(self):
-        km = self.kernel_manager
-        self.finish(jsonapi.dumps(km.list_kernel_ids()))
-
-    @web.authenticated
-    def post(self):
-        km = self.kernel_manager
-        nbm = self.notebook_manager
-        notebook_id = self.get_argument('notebook', default=None)
-        kernel_id = km.start_kernel(notebook_id, cwd=nbm.notebook_dir)
-        data = {'ws_url':self.ws_url,'kernel_id':kernel_id}
-        self.set_header('Location', '{0}kernels/{1}'.format(self.base_kernel_url, kernel_id))
-        self.finish(jsonapi.dumps(data))
-
-
-class KernelHandler(IPythonHandler):
-
-    SUPPORTED_METHODS = ('DELETE')
-
-    @web.authenticated
-    def delete(self, kernel_id):
-        km = self.kernel_manager
-        km.shutdown_kernel(kernel_id)
-        self.set_status(204)
-        self.finish()
-
-
-class KernelActionHandler(IPythonHandler):
-
-    @web.authenticated
-    def post(self, kernel_id, action):
-        km = self.kernel_manager
-        if action == 'interrupt':
-            km.interrupt_kernel(kernel_id)
-            self.set_status(204)
-        if action == 'restart':
-            km.restart_kernel(kernel_id)
-            data = {'ws_url':self.ws_url, 'kernel_id':kernel_id}
-            self.set_header('Location', '{0}kernels/{1}'.format(self.base_kernel_url, kernel_id))
-            self.write(jsonapi.dumps(data))
-        self.finish()
-
-
-class ZMQStreamHandler(websocket.WebSocketHandler):
-    
-    def clear_cookie(self, *args, **kwargs):
-        """meaningless for websockets"""
-        pass
-
-    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, default=date_default)
-
-    def _on_zmq_reply(self, msg_list):
-        # Sometimes this gets triggered when the on_close method is scheduled in the
-        # eventloop but hasn't been called.
-        if self.stream.closed(): return
-        try:
-            msg = self._reserialize_reply(msg_list)
-        except Exception:
-            self.log.critical("Malformed message: %r" % msg_list, exc_info=True)
-        else:
-            self.write_message(msg)
-
-    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
-
-
-class AuthenticatedZMQStreamHandler(ZMQStreamHandler, IPythonHandler):
-
-    def open(self, kernel_id):
-        self.kernel_id = kernel_id.decode('ascii')
-        self.session = Session(config=self.config)
-        self.save_on_message = self.on_message
-        self.on_message = self.on_first_message
-
-    def _inject_cookie_message(self, msg):
-        """Inject the first message, which is the document cookie,
-        for authentication."""
-        if not PY3 and isinstance(msg, unicode):
-            # Cookie constructor doesn't accept unicode strings
-            # under Python 2.x for some reason
-            msg = msg.encode('utf8', 'replace')
-        try:
-            identity, msg = msg.split(':', 1)
-            self.session.session = identity.decode('ascii')
-        except Exception:
-            logging.error("First ws message didn't have the form 'identity:[cookie]' - %r", msg)
-        
-        try:
-            self.request._cookies = Cookie.SimpleCookie(msg)
-        except:
-            self.log.warn("couldn't parse cookie string: %s",msg, exc_info=True)
-
-    def on_first_message(self, msg):
-        self._inject_cookie_message(msg)
-        if self.get_current_user() is None:
-            self.log.warn("Couldn't authenticate WebSocket connection")
-            raise web.HTTPError(403)
-        self.on_message = self.save_on_message
-
-
-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)
-        self.zmq_stream = meth(self.kernel_id, identity=self.session.bsession)
-    
-    def initialize(self, *args, **kwargs):
-        self.zmq_stream = None
-    
-    def on_first_message(self, msg):
-        try:
-            super(ZMQChannelHandler, self).on_first_message(msg)
-        except web.HTTPError:
-            self.close()
-            return
-        try:
-            self.create_stream()
-        except web.HTTPError:
-            # WebSockets don't response to traditional error codes so we
-            # close the connection.
-            if not self.stream.closed():
-                self.stream.close()
-            self.close()
-        else:
-            self.zmq_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.zmq_stream, msg)
-
-    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.
-        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):
-        km = self.kernel_manager
-        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',
-            )
-        super(IOPubHandler, self).on_close()
-    
-    def _send_status_message(self, status):
-        msg = self.session.msg("status",
-            {'execution_state': status}
-        )
-        self.write_message(jsonapi.dumps(msg, default=date_default))
-
-    def on_kernel_restarted(self):
-        logging.warn("kernel %s restarted", self.kernel_id)
-        self._send_status_message('restarting')
-
-    def on_restart_failed(self):
-        logging.error("kernel %s restarted failed!", self.kernel_id)
-        self._send_status_message('dead')
-    
-    def on_message(self, msg):
-        """IOPub messages make no sense"""
-        pass
-
-class ShellHandler(ZMQChannelHandler):
-    channel = 'shell'
-
-class StdinHandler(ZMQChannelHandler):
-    channel = 'stdin'
-
-
-#-----------------------------------------------------------------------------
-# Notebook web service handlers
-#-----------------------------------------------------------------------------
-
-class NotebookRedirectHandler(IPythonHandler):
-    
-    @authenticate_unless_readonly
-    def get(self, notebook_name):
-        # strip trailing .ipynb:
-        notebook_name = os.path.splitext(notebook_name)[0]
-        notebook_id = self.notebook_manager.rev_mapping.get(notebook_name, '')
-        if notebook_id:
-            url = self.settings.get('base_project_url', '/') + notebook_id
-            return self.redirect(url)
-        else:
-            raise HTTPError(404)
-
-
-class NotebookRootHandler(IPythonHandler):
-
-    @authenticate_unless_readonly
-    def get(self):
-        nbm = self.notebook_manager
-        km = self.kernel_manager
-        files = nbm.list_notebooks()
-        for f in files :
-            f['kernel_id'] = km.kernel_for_notebook(f['notebook_id'])
-        self.finish(jsonapi.dumps(files))
-
-    @web.authenticated
-    def post(self):
-        nbm = self.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', '{0}notebooks/{1}'.format(self.base_project_url, notebook_id))
-        self.finish(jsonapi.dumps(notebook_id))
-
-
-class NotebookHandler(IPythonHandler):
-
-    SUPPORTED_METHODS = ('GET', 'PUT', 'DELETE')
-
-    @authenticate_unless_readonly
-    def get(self, notebook_id):
-        nbm = self.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.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):
-        self.notebook_manager.delete_notebook(notebook_id)
-        self.set_status(204)
-        self.finish()
-
-
-class NotebookCheckpointsHandler(IPythonHandler):
-    
-    SUPPORTED_METHODS = ('GET', 'POST')
-    
-    @web.authenticated
-    def get(self, notebook_id):
-        """get lists checkpoints for a notebook"""
-        nbm = self.notebook_manager
-        checkpoints = nbm.list_checkpoints(notebook_id)
-        data = jsonapi.dumps(checkpoints, default=date_default)
-        self.finish(data)
-    
-    @web.authenticated
-    def post(self, notebook_id):
-        """post creates a new checkpoint"""
-        nbm = self.notebook_manager
-        checkpoint = nbm.create_checkpoint(notebook_id)
-        data = jsonapi.dumps(checkpoint, default=date_default)
-        self.set_header('Location', '{0}notebooks/{1}/checkpoints/{2}'.format(
-            self.base_project_url, notebook_id, checkpoint['checkpoint_id']
-        ))
-        
-        self.finish(data)
-
-
-class ModifyNotebookCheckpointsHandler(IPythonHandler):
-    
-    SUPPORTED_METHODS = ('POST', 'DELETE')
-    
-    @web.authenticated
-    def post(self, notebook_id, checkpoint_id):
-        """post restores a notebook from a checkpoint"""
-        nbm = self.notebook_manager
-        nbm.restore_checkpoint(notebook_id, checkpoint_id)
-        self.set_status(204)
-        self.finish()
-    
-    @web.authenticated
-    def delete(self, notebook_id, checkpoint_id):
-        """delete clears a checkpoint for a given notebook"""
-        nbm = self.notebook_manager
-        nbm.delte_checkpoint(notebook_id, checkpoint_id)
-        self.set_status(204)
-        self.finish()
-
-
-class NotebookCopyHandler(IPythonHandler):
-
-    @web.authenticated
-    def get(self, notebook_id):
-        notebook_id = self.notebook_manager.copy_notebook(notebook_id)
-        self.redirect('/'+urljoin(self.base_project_url, notebook_id))
-
-
-#-----------------------------------------------------------------------------
-# Cluster handlers
-#-----------------------------------------------------------------------------
-
-
-class MainClusterHandler(IPythonHandler):
-
-    @web.authenticated
-    def get(self):
-        self.finish(jsonapi.dumps(self.cluster_manager.list_profiles()))
-
-
-class ClusterProfileHandler(IPythonHandler):
-
-    @web.authenticated
-    def get(self, profile):
-        self.finish(jsonapi.dumps(self.cluster_manager.profile_info(profile)))
-
-
-class ClusterActionHandler(IPythonHandler):
-
-    @web.authenticated
-    def post(self, profile, action):
-        cm = self.cluster_manager
-        if action == 'start':
-            n = self.get_argument('n',default=None)
-            if n is None:
-                data = cm.start_cluster(profile)
-            else:
-                data = cm.start_cluster(profile, int(n))
-        if action == 'stop':
-            data = cm.stop_cluster(profile)
-        self.finish(jsonapi.dumps(data))
-
-
-#-----------------------------------------------------------------------------
-# File handler
-#-----------------------------------------------------------------------------
-
-# to minimize subclass changes:
-HTTPError = web.HTTPError
-
-class FileFindHandler(web.StaticFileHandler):
-    """subclass of StaticFileHandler for serving files from a search path"""
-    
-    _static_paths = {}
-    # _lock is needed for tornado < 2.2.0 compat
-    _lock = threading.Lock()  # protects _static_hashes
-    
-    def initialize(self, path, default_filename=None):
-        if isinstance(path, basestring):
-            path = [path]
-        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):
-                raise HTTPError(403, "%s is not in root static directory", path)
-        
-            cls._static_paths[path] = abspath
-            return abspath
-    
-    def get(self, path, include_body=True):
-        path = self.parse_url_path(path)
-        
-        # begin subclass override
-        abspath = self.locate_file(path, self.roots)
-        # end subclass override
-        
-        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):
-            raise HTTPError(404)
-        if not os.path.isfile(abspath):
-            raise HTTPError(403, "%s is not a file", path)
-
-        stat_result = os.stat(abspath)
-        modified = datetime.datetime.utcfromtimestamp(stat_result[stat.ST_MTIME])
-
-        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)
-            if_since = datetime.datetime(*date_tuple[:6])
-            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:
-        static_paths = settings['static_path']
-        if isinstance(static_paths, basestring):
-            static_paths = [static_paths]
-        roots = tuple(
-            os.path.abspath(os.path.expanduser(p)) + os.path.sep for p in static_paths
-        )
-
-        try:
-            abs_path = filefind(path, roots)
-        except IOError:
-            app_log.error("Could not find static file %r", path)
-            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:
-                    app_log.error("Could not open static file %r", path)
-                    hashes[abs_path] = None
-            hsh = hashes.get(abs_path)
-            if hsh:
-                return hsh[:5]
-        return None
-
-
-    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
-
-
diff --git a/IPython/frontend/html/notebook/handlers/logout.py b/IPython/frontend/html/notebook/handlers/logout.py
index 32ec552..7474eba 100644
--- a/IPython/frontend/html/notebook/handlers/logout.py
+++ b/IPython/frontend/html/notebook/handlers/logout.py
@@ -1,4 +1,4 @@
-"""Tornado handlers for the notebook.
+"""Tornado handlers for logging out of the notebook.
 
 Authors:
 
@@ -6,7 +6,7 @@ Authors:
 """
 
 #-----------------------------------------------------------------------------
-#  Copyright (C) 2008-2011  The IPython Development Team
+#  Copyright (C) 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.
@@ -16,333 +16,12 @@ Authors:
 # Imports
 #-----------------------------------------------------------------------------
 
-import Cookie
-import datetime
-import email.utils
-import hashlib
-import logging
-import mimetypes
-import os
-import stat
-import threading
-import time
-import uuid
-
-from tornado.escape import url_escape
-from tornado import web
-from tornado import websocket
-
-try:
-    from tornado.log import app_log
-except ImportError:
-    app_log = logging.getLogger()
-
-from zmq.eventloop import ioloop
-from zmq.utils import jsonapi
-
-from IPython.config import Application
-from IPython.external.decorator import decorator
-from IPython.kernel.zmq.session import Session
-from IPython.lib.security import passwd_check
-from IPython.utils.jsonutil import date_default
-from IPython.utils.path import filefind
-from IPython.utils.py3compat import PY3
-
-try:
-    from docutils.core import publish_string
-except ImportError:
-    publish_string = None
-
-#-----------------------------------------------------------------------------
-# Monkeypatch for Tornado <= 2.1.1 - Remove when no longer necessary!
-#-----------------------------------------------------------------------------
-
-# 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
-
-if tornado.version_info <= (2,1,1):
-
-    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()
-
-    websocket.WebSocketHandler._execute = _execute
-    del _execute
-
-#-----------------------------------------------------------------------------
-# Decorator for disabling read-only handlers
-#-----------------------------------------------------------------------------
-
-@decorator
-def not_if_readonly(f, self, *args, **kwargs):
-    if self.settings.get('read_only', False):
-        raise web.HTTPError(403, "Notebook server is read-only")
-    else:
-        return f(self, *args, **kwargs)
-
-@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)
-
-    if self.settings.get('read_only', False):
-        return f(self, *args, **kwargs)
-    else:
-        return auth_f(self, *args, **kwargs)
-
-def urljoin(*pieces):
-    """Join components of url into a relative url
-
-    Use to prevent double slash when joining subpath
-    """
-    striped = [s.strip('/') for s in pieces]
-    return '/'.join(s for s in striped if s)
+from .base import IPythonHandler
 
 #-----------------------------------------------------------------------------
-# Top-level handlers
+# Handler
 #-----------------------------------------------------------------------------
 
-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):
-    """A RequestHandler with an authenticated user."""
-
-    def clear_login_cookie(self):
-        self.clear_cookie(self.cookie_name)
-    
-    def get_current_user(self):
-        user_id = self.get_secure_cookie(self.cookie_name)
-        # For now the user_id should not return empty, but it could eventually
-        if user_id == '':
-            user_id = 'anonymous'
-        if user_id is None:
-            # prevent extra Invalid cookie sig warnings:
-            self.clear_login_cookie()
-            if not self.read_only and not self.login_available:
-                user_id = 'anonymous'
-        return user_id
-
-    @property
-    def cookie_name(self):
-        return self.settings.get('cookie_name', '')
-    
-    @property
-    def password(self):
-        """our password"""
-        return self.settings.get('password', '')
-    
-    @property
-    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.
-
-        """
-        return bool(self.settings.get('password', ''))
-
-    @property
-    def read_only(self):
-        """Is the notebook read-only?
-
-        """
-        return self.settings.get('read_only', False)
-
-
-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)
-    
-    @property
-    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
-    def use_less(self):
-        """Use less instead of css in templates"""
-        return self.settings.get('use_less', False)
-    
-    #---------------------------------------------------------------
-    # URLs
-    #---------------------------------------------------------------
-    
-    @property
-    def ws_url(self):
-        """websocket url matching the current request
-
-        turns http[s]://host[:port] into
-                ws[s]://host[:port]
-        """
-        proto = self.request.protocol.replace('http', 'ws')
-        host = self.settings.get('websocket_host', '')
-        # default to config value
-        if host == '':
-            host = self.request.host # get from request
-        return "%s://%s" % (proto, host)
-    
-    @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,
-        )
-
-class AuthenticatedFileHandler(IPythonHandler, web.StaticFileHandler):
-    """static files should only be accessible when logged in"""
-
-    @authenticate_unless_readonly
-    def get(self, path):
-        return web.StaticFileHandler.get(self, path)
-
-
-class ProjectDashboardHandler(IPythonHandler):
-
-    @authenticate_unless_readonly
-    def get(self):
-        self.write(self.render_template('projectdashboard.html',
-            project=self.project,
-            project_component=self.project.split('/'),
-        ))
-
-
-class LoginHandler(IPythonHandler):
-
-    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,
-        ))
-
-    def get(self):
-        if self.current_user:
-            self.redirect(self.get_argument('next', default=self.base_project_url))
-        else:
-            self._render()
-
-    def post(self):
-        pwd = self.get_argument('password', default=u'')
-        if self.login_available:
-            if passwd_check(self.password, pwd):
-                self.set_secure_cookie(self.cookie_name, str(uuid.uuid4()))
-            else:
-                self._render(message={'error': 'Invalid password'})
-                return
-
-        self.redirect(self.get_argument('next', default=self.base_project_url))
-
 
 class LogoutHandler(IPythonHandler):
 
@@ -355,577 +34,3 @@ class LogoutHandler(IPythonHandler):
                        'is disabled.'}
         self.write(self.render_template('logout.html',
                     message=message))
-
-
-class NewHandler(IPythonHandler):
-
-    @web.authenticated
-    def get(self):
-        notebook_id = self.notebook_manager.new_notebook()
-        self.redirect('/' + urljoin(self.base_project_url, notebook_id))
-
-class NamedNotebookHandler(IPythonHandler):
-
-    @authenticate_unless_readonly
-    def get(self, notebook_id):
-        nbm = self.notebook_manager
-        if not nbm.notebook_exists(notebook_id):
-            raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)       
-        self.write(self.render_template('notebook.html',
-            project=self.project,
-            notebook_id=notebook_id,
-            kill_kernel=False,
-            mathjax_url=self.mathjax_url,
-            )
-        )
-
-
-#-----------------------------------------------------------------------------
-# Kernel handlers
-#-----------------------------------------------------------------------------
-
-
-class MainKernelHandler(IPythonHandler):
-
-    @web.authenticated
-    def get(self):
-        km = self.kernel_manager
-        self.finish(jsonapi.dumps(km.list_kernel_ids()))
-
-    @web.authenticated
-    def post(self):
-        km = self.kernel_manager
-        nbm = self.notebook_manager
-        notebook_id = self.get_argument('notebook', default=None)
-        kernel_id = km.start_kernel(notebook_id, cwd=nbm.notebook_dir)
-        data = {'ws_url':self.ws_url,'kernel_id':kernel_id}
-        self.set_header('Location', '{0}kernels/{1}'.format(self.base_kernel_url, kernel_id))
-        self.finish(jsonapi.dumps(data))
-
-
-class KernelHandler(IPythonHandler):
-
-    SUPPORTED_METHODS = ('DELETE')
-
-    @web.authenticated
-    def delete(self, kernel_id):
-        km = self.kernel_manager
-        km.shutdown_kernel(kernel_id)
-        self.set_status(204)
-        self.finish()
-
-
-class KernelActionHandler(IPythonHandler):
-
-    @web.authenticated
-    def post(self, kernel_id, action):
-        km = self.kernel_manager
-        if action == 'interrupt':
-            km.interrupt_kernel(kernel_id)
-            self.set_status(204)
-        if action == 'restart':
-            km.restart_kernel(kernel_id)
-            data = {'ws_url':self.ws_url, 'kernel_id':kernel_id}
-            self.set_header('Location', '{0}kernels/{1}'.format(self.base_kernel_url, kernel_id))
-            self.write(jsonapi.dumps(data))
-        self.finish()
-
-
-class ZMQStreamHandler(websocket.WebSocketHandler):
-    
-    def clear_cookie(self, *args, **kwargs):
-        """meaningless for websockets"""
-        pass
-
-    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, default=date_default)
-
-    def _on_zmq_reply(self, msg_list):
-        # Sometimes this gets triggered when the on_close method is scheduled in the
-        # eventloop but hasn't been called.
-        if self.stream.closed(): return
-        try:
-            msg = self._reserialize_reply(msg_list)
-        except Exception:
-            self.log.critical("Malformed message: %r" % msg_list, exc_info=True)
-        else:
-            self.write_message(msg)
-
-    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
-
-
-class AuthenticatedZMQStreamHandler(ZMQStreamHandler, IPythonHandler):
-
-    def open(self, kernel_id):
-        self.kernel_id = kernel_id.decode('ascii')
-        self.session = Session(config=self.config)
-        self.save_on_message = self.on_message
-        self.on_message = self.on_first_message
-
-    def _inject_cookie_message(self, msg):
-        """Inject the first message, which is the document cookie,
-        for authentication."""
-        if not PY3 and isinstance(msg, unicode):
-            # Cookie constructor doesn't accept unicode strings
-            # under Python 2.x for some reason
-            msg = msg.encode('utf8', 'replace')
-        try:
-            identity, msg = msg.split(':', 1)
-            self.session.session = identity.decode('ascii')
-        except Exception:
-            logging.error("First ws message didn't have the form 'identity:[cookie]' - %r", msg)
-        
-        try:
-            self.request._cookies = Cookie.SimpleCookie(msg)
-        except:
-            self.log.warn("couldn't parse cookie string: %s",msg, exc_info=True)
-
-    def on_first_message(self, msg):
-        self._inject_cookie_message(msg)
-        if self.get_current_user() is None:
-            self.log.warn("Couldn't authenticate WebSocket connection")
-            raise web.HTTPError(403)
-        self.on_message = self.save_on_message
-
-
-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)
-        self.zmq_stream = meth(self.kernel_id, identity=self.session.bsession)
-    
-    def initialize(self, *args, **kwargs):
-        self.zmq_stream = None
-    
-    def on_first_message(self, msg):
-        try:
-            super(ZMQChannelHandler, self).on_first_message(msg)
-        except web.HTTPError:
-            self.close()
-            return
-        try:
-            self.create_stream()
-        except web.HTTPError:
-            # WebSockets don't response to traditional error codes so we
-            # close the connection.
-            if not self.stream.closed():
-                self.stream.close()
-            self.close()
-        else:
-            self.zmq_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.zmq_stream, msg)
-
-    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.
-        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):
-        km = self.kernel_manager
-        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',
-            )
-        super(IOPubHandler, self).on_close()
-    
-    def _send_status_message(self, status):
-        msg = self.session.msg("status",
-            {'execution_state': status}
-        )
-        self.write_message(jsonapi.dumps(msg, default=date_default))
-
-    def on_kernel_restarted(self):
-        logging.warn("kernel %s restarted", self.kernel_id)
-        self._send_status_message('restarting')
-
-    def on_restart_failed(self):
-        logging.error("kernel %s restarted failed!", self.kernel_id)
-        self._send_status_message('dead')
-    
-    def on_message(self, msg):
-        """IOPub messages make no sense"""
-        pass
-
-class ShellHandler(ZMQChannelHandler):
-    channel = 'shell'
-
-class StdinHandler(ZMQChannelHandler):
-    channel = 'stdin'
-
-
-#-----------------------------------------------------------------------------
-# Notebook web service handlers
-#-----------------------------------------------------------------------------
-
-class NotebookRedirectHandler(IPythonHandler):
-    
-    @authenticate_unless_readonly
-    def get(self, notebook_name):
-        # strip trailing .ipynb:
-        notebook_name = os.path.splitext(notebook_name)[0]
-        notebook_id = self.notebook_manager.rev_mapping.get(notebook_name, '')
-        if notebook_id:
-            url = self.settings.get('base_project_url', '/') + notebook_id
-            return self.redirect(url)
-        else:
-            raise HTTPError(404)
-
-
-class NotebookRootHandler(IPythonHandler):
-
-    @authenticate_unless_readonly
-    def get(self):
-        nbm = self.notebook_manager
-        km = self.kernel_manager
-        files = nbm.list_notebooks()
-        for f in files :
-            f['kernel_id'] = km.kernel_for_notebook(f['notebook_id'])
-        self.finish(jsonapi.dumps(files))
-
-    @web.authenticated
-    def post(self):
-        nbm = self.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', '{0}notebooks/{1}'.format(self.base_project_url, notebook_id))
-        self.finish(jsonapi.dumps(notebook_id))
-
-
-class NotebookHandler(IPythonHandler):
-
-    SUPPORTED_METHODS = ('GET', 'PUT', 'DELETE')
-
-    @authenticate_unless_readonly
-    def get(self, notebook_id):
-        nbm = self.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.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):
-        self.notebook_manager.delete_notebook(notebook_id)
-        self.set_status(204)
-        self.finish()
-
-
-class NotebookCheckpointsHandler(IPythonHandler):
-    
-    SUPPORTED_METHODS = ('GET', 'POST')
-    
-    @web.authenticated
-    def get(self, notebook_id):
-        """get lists checkpoints for a notebook"""
-        nbm = self.notebook_manager
-        checkpoints = nbm.list_checkpoints(notebook_id)
-        data = jsonapi.dumps(checkpoints, default=date_default)
-        self.finish(data)
-    
-    @web.authenticated
-    def post(self, notebook_id):
-        """post creates a new checkpoint"""
-        nbm = self.notebook_manager
-        checkpoint = nbm.create_checkpoint(notebook_id)
-        data = jsonapi.dumps(checkpoint, default=date_default)
-        self.set_header('Location', '{0}notebooks/{1}/checkpoints/{2}'.format(
-            self.base_project_url, notebook_id, checkpoint['checkpoint_id']
-        ))
-        
-        self.finish(data)
-
-
-class ModifyNotebookCheckpointsHandler(IPythonHandler):
-    
-    SUPPORTED_METHODS = ('POST', 'DELETE')
-    
-    @web.authenticated
-    def post(self, notebook_id, checkpoint_id):
-        """post restores a notebook from a checkpoint"""
-        nbm = self.notebook_manager
-        nbm.restore_checkpoint(notebook_id, checkpoint_id)
-        self.set_status(204)
-        self.finish()
-    
-    @web.authenticated
-    def delete(self, notebook_id, checkpoint_id):
-        """delete clears a checkpoint for a given notebook"""
-        nbm = self.notebook_manager
-        nbm.delte_checkpoint(notebook_id, checkpoint_id)
-        self.set_status(204)
-        self.finish()
-
-
-class NotebookCopyHandler(IPythonHandler):
-
-    @web.authenticated
-    def get(self, notebook_id):
-        notebook_id = self.notebook_manager.copy_notebook(notebook_id)
-        self.redirect('/'+urljoin(self.base_project_url, notebook_id))
-
-
-#-----------------------------------------------------------------------------
-# Cluster handlers
-#-----------------------------------------------------------------------------
-
-
-class MainClusterHandler(IPythonHandler):
-
-    @web.authenticated
-    def get(self):
-        self.finish(jsonapi.dumps(self.cluster_manager.list_profiles()))
-
-
-class ClusterProfileHandler(IPythonHandler):
-
-    @web.authenticated
-    def get(self, profile):
-        self.finish(jsonapi.dumps(self.cluster_manager.profile_info(profile)))
-
-
-class ClusterActionHandler(IPythonHandler):
-
-    @web.authenticated
-    def post(self, profile, action):
-        cm = self.cluster_manager
-        if action == 'start':
-            n = self.get_argument('n',default=None)
-            if n is None:
-                data = cm.start_cluster(profile)
-            else:
-                data = cm.start_cluster(profile, int(n))
-        if action == 'stop':
-            data = cm.stop_cluster(profile)
-        self.finish(jsonapi.dumps(data))
-
-
-#-----------------------------------------------------------------------------
-# File handler
-#-----------------------------------------------------------------------------
-
-# to minimize subclass changes:
-HTTPError = web.HTTPError
-
-class FileFindHandler(web.StaticFileHandler):
-    """subclass of StaticFileHandler for serving files from a search path"""
-    
-    _static_paths = {}
-    # _lock is needed for tornado < 2.2.0 compat
-    _lock = threading.Lock()  # protects _static_hashes
-    
-    def initialize(self, path, default_filename=None):
-        if isinstance(path, basestring):
-            path = [path]
-        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):
-                raise HTTPError(403, "%s is not in root static directory", path)
-        
-            cls._static_paths[path] = abspath
-            return abspath
-    
-    def get(self, path, include_body=True):
-        path = self.parse_url_path(path)
-        
-        # begin subclass override
-        abspath = self.locate_file(path, self.roots)
-        # end subclass override
-        
-        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):
-            raise HTTPError(404)
-        if not os.path.isfile(abspath):
-            raise HTTPError(403, "%s is not a file", path)
-
-        stat_result = os.stat(abspath)
-        modified = datetime.datetime.utcfromtimestamp(stat_result[stat.ST_MTIME])
-
-        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)
-            if_since = datetime.datetime(*date_tuple[:6])
-            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:
-        static_paths = settings['static_path']
-        if isinstance(static_paths, basestring):
-            static_paths = [static_paths]
-        roots = tuple(
-            os.path.abspath(os.path.expanduser(p)) + os.path.sep for p in static_paths
-        )
-
-        try:
-            abs_path = filefind(path, roots)
-        except IOError:
-            app_log.error("Could not find static file %r", path)
-            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:
-                    app_log.error("Could not open static file %r", path)
-                    hashes[abs_path] = None
-            hsh = hashes.get(abs_path)
-            if hsh:
-                return hsh[:5]
-        return None
-
-
-    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
-
-
diff --git a/IPython/frontend/html/notebook/handlers/notebooks.py b/IPython/frontend/html/notebook/handlers/notebooks.py
index 32ec552..e0fced3 100644
--- a/IPython/frontend/html/notebook/handlers/notebooks.py
+++ b/IPython/frontend/html/notebook/handlers/notebooks.py
@@ -1,4 +1,4 @@
-"""Tornado handlers for the notebook.
+"""Tornado handlers for the live notebook view.
 
 Authors:
 
@@ -6,7 +6,7 @@ Authors:
 """
 
 #-----------------------------------------------------------------------------
-#  Copyright (C) 2008-2011  The IPython Development Team
+#  Copyright (C) 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.
@@ -16,353 +16,25 @@ Authors:
 # Imports
 #-----------------------------------------------------------------------------
 
-import Cookie
-import datetime
-import email.utils
-import hashlib
-import logging
-import mimetypes
 import os
-import stat
-import threading
-import time
-import uuid
-
-from tornado.escape import url_escape
 from tornado import web
-from tornado import websocket
-
-try:
-    from tornado.log import app_log
-except ImportError:
-    app_log = logging.getLogger()
-
-from zmq.eventloop import ioloop
-from zmq.utils import jsonapi
-
-from IPython.config import Application
-from IPython.external.decorator import decorator
-from IPython.kernel.zmq.session import Session
-from IPython.lib.security import passwd_check
-from IPython.utils.jsonutil import date_default
-from IPython.utils.path import filefind
-from IPython.utils.py3compat import PY3
-
-try:
-    from docutils.core import publish_string
-except ImportError:
-    publish_string = None
-
-#-----------------------------------------------------------------------------
-# Monkeypatch for Tornado <= 2.1.1 - Remove when no longer necessary!
-#-----------------------------------------------------------------------------
-
-# 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
-
-if tornado.version_info <= (2,1,1):
-
-    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()
-
-    websocket.WebSocketHandler._execute = _execute
-    del _execute
-
-#-----------------------------------------------------------------------------
-# Decorator for disabling read-only handlers
-#-----------------------------------------------------------------------------
-
-@decorator
-def not_if_readonly(f, self, *args, **kwargs):
-    if self.settings.get('read_only', False):
-        raise web.HTTPError(403, "Notebook server is read-only")
-    else:
-        return f(self, *args, **kwargs)
-
-@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)
-
-    if self.settings.get('read_only', False):
-        return f(self, *args, **kwargs)
-    else:
-        return auth_f(self, *args, **kwargs)
-
-def urljoin(*pieces):
-    """Join components of url into a relative url
+HTTPError = web.HTTPError
 
-    Use to prevent double slash when joining subpath
-    """
-    striped = [s.strip('/') for s in pieces]
-    return '/'.join(s for s in striped if s)
+from .base import IPythonHandler, authenticate_unless_readonly
+from ..utils import url_path_join
 
 #-----------------------------------------------------------------------------
-# Top-level handlers
+# Handlers
 #-----------------------------------------------------------------------------
 
-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):
-    """A RequestHandler with an authenticated user."""
-
-    def clear_login_cookie(self):
-        self.clear_cookie(self.cookie_name)
-    
-    def get_current_user(self):
-        user_id = self.get_secure_cookie(self.cookie_name)
-        # For now the user_id should not return empty, but it could eventually
-        if user_id == '':
-            user_id = 'anonymous'
-        if user_id is None:
-            # prevent extra Invalid cookie sig warnings:
-            self.clear_login_cookie()
-            if not self.read_only and not self.login_available:
-                user_id = 'anonymous'
-        return user_id
-
-    @property
-    def cookie_name(self):
-        return self.settings.get('cookie_name', '')
-    
-    @property
-    def password(self):
-        """our password"""
-        return self.settings.get('password', '')
-    
-    @property
-    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.
-
-        """
-        return bool(self.settings.get('password', ''))
-
-    @property
-    def read_only(self):
-        """Is the notebook read-only?
-
-        """
-        return self.settings.get('read_only', False)
-
-
-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)
-    
-    @property
-    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
-    def use_less(self):
-        """Use less instead of css in templates"""
-        return self.settings.get('use_less', False)
-    
-    #---------------------------------------------------------------
-    # URLs
-    #---------------------------------------------------------------
-    
-    @property
-    def ws_url(self):
-        """websocket url matching the current request
-
-        turns http[s]://host[:port] into
-                ws[s]://host[:port]
-        """
-        proto = self.request.protocol.replace('http', 'ws')
-        host = self.settings.get('websocket_host', '')
-        # default to config value
-        if host == '':
-            host = self.request.host # get from request
-        return "%s://%s" % (proto, host)
-    
-    @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,
-        )
-
-class AuthenticatedFileHandler(IPythonHandler, web.StaticFileHandler):
-    """static files should only be accessible when logged in"""
-
-    @authenticate_unless_readonly
-    def get(self, path):
-        return web.StaticFileHandler.get(self, path)
-
-
-class ProjectDashboardHandler(IPythonHandler):
-
-    @authenticate_unless_readonly
-    def get(self):
-        self.write(self.render_template('projectdashboard.html',
-            project=self.project,
-            project_component=self.project.split('/'),
-        ))
-
-
-class LoginHandler(IPythonHandler):
-
-    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,
-        ))
-
-    def get(self):
-        if self.current_user:
-            self.redirect(self.get_argument('next', default=self.base_project_url))
-        else:
-            self._render()
-
-    def post(self):
-        pwd = self.get_argument('password', default=u'')
-        if self.login_available:
-            if passwd_check(self.password, pwd):
-                self.set_secure_cookie(self.cookie_name, str(uuid.uuid4()))
-            else:
-                self._render(message={'error': 'Invalid password'})
-                return
-
-        self.redirect(self.get_argument('next', default=self.base_project_url))
-
-
-class LogoutHandler(IPythonHandler):
-
-    def get(self):
-        self.clear_login_cookie()
-        if self.login_available:
-            message = {'info': 'Successfully logged out.'}
-        else:
-            message = {'warning': 'Cannot log out.  Notebook authentication '
-                       'is disabled.'}
-        self.write(self.render_template('logout.html',
-                    message=message))
-
 
 class NewHandler(IPythonHandler):
 
     @web.authenticated
     def get(self):
         notebook_id = self.notebook_manager.new_notebook()
-        self.redirect('/' + urljoin(self.base_project_url, notebook_id))
+        self.redirect('/' + url_path_join(self.base_project_url, notebook_id))
+
 
 class NamedNotebookHandler(IPythonHandler):
 
@@ -380,232 +52,6 @@ class NamedNotebookHandler(IPythonHandler):
         )
 
 
-#-----------------------------------------------------------------------------
-# Kernel handlers
-#-----------------------------------------------------------------------------
-
-
-class MainKernelHandler(IPythonHandler):
-
-    @web.authenticated
-    def get(self):
-        km = self.kernel_manager
-        self.finish(jsonapi.dumps(km.list_kernel_ids()))
-
-    @web.authenticated
-    def post(self):
-        km = self.kernel_manager
-        nbm = self.notebook_manager
-        notebook_id = self.get_argument('notebook', default=None)
-        kernel_id = km.start_kernel(notebook_id, cwd=nbm.notebook_dir)
-        data = {'ws_url':self.ws_url,'kernel_id':kernel_id}
-        self.set_header('Location', '{0}kernels/{1}'.format(self.base_kernel_url, kernel_id))
-        self.finish(jsonapi.dumps(data))
-
-
-class KernelHandler(IPythonHandler):
-
-    SUPPORTED_METHODS = ('DELETE')
-
-    @web.authenticated
-    def delete(self, kernel_id):
-        km = self.kernel_manager
-        km.shutdown_kernel(kernel_id)
-        self.set_status(204)
-        self.finish()
-
-
-class KernelActionHandler(IPythonHandler):
-
-    @web.authenticated
-    def post(self, kernel_id, action):
-        km = self.kernel_manager
-        if action == 'interrupt':
-            km.interrupt_kernel(kernel_id)
-            self.set_status(204)
-        if action == 'restart':
-            km.restart_kernel(kernel_id)
-            data = {'ws_url':self.ws_url, 'kernel_id':kernel_id}
-            self.set_header('Location', '{0}kernels/{1}'.format(self.base_kernel_url, kernel_id))
-            self.write(jsonapi.dumps(data))
-        self.finish()
-
-
-class ZMQStreamHandler(websocket.WebSocketHandler):
-    
-    def clear_cookie(self, *args, **kwargs):
-        """meaningless for websockets"""
-        pass
-
-    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, default=date_default)
-
-    def _on_zmq_reply(self, msg_list):
-        # Sometimes this gets triggered when the on_close method is scheduled in the
-        # eventloop but hasn't been called.
-        if self.stream.closed(): return
-        try:
-            msg = self._reserialize_reply(msg_list)
-        except Exception:
-            self.log.critical("Malformed message: %r" % msg_list, exc_info=True)
-        else:
-            self.write_message(msg)
-
-    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
-
-
-class AuthenticatedZMQStreamHandler(ZMQStreamHandler, IPythonHandler):
-
-    def open(self, kernel_id):
-        self.kernel_id = kernel_id.decode('ascii')
-        self.session = Session(config=self.config)
-        self.save_on_message = self.on_message
-        self.on_message = self.on_first_message
-
-    def _inject_cookie_message(self, msg):
-        """Inject the first message, which is the document cookie,
-        for authentication."""
-        if not PY3 and isinstance(msg, unicode):
-            # Cookie constructor doesn't accept unicode strings
-            # under Python 2.x for some reason
-            msg = msg.encode('utf8', 'replace')
-        try:
-            identity, msg = msg.split(':', 1)
-            self.session.session = identity.decode('ascii')
-        except Exception:
-            logging.error("First ws message didn't have the form 'identity:[cookie]' - %r", msg)
-        
-        try:
-            self.request._cookies = Cookie.SimpleCookie(msg)
-        except:
-            self.log.warn("couldn't parse cookie string: %s",msg, exc_info=True)
-
-    def on_first_message(self, msg):
-        self._inject_cookie_message(msg)
-        if self.get_current_user() is None:
-            self.log.warn("Couldn't authenticate WebSocket connection")
-            raise web.HTTPError(403)
-        self.on_message = self.save_on_message
-
-
-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)
-        self.zmq_stream = meth(self.kernel_id, identity=self.session.bsession)
-    
-    def initialize(self, *args, **kwargs):
-        self.zmq_stream = None
-    
-    def on_first_message(self, msg):
-        try:
-            super(ZMQChannelHandler, self).on_first_message(msg)
-        except web.HTTPError:
-            self.close()
-            return
-        try:
-            self.create_stream()
-        except web.HTTPError:
-            # WebSockets don't response to traditional error codes so we
-            # close the connection.
-            if not self.stream.closed():
-                self.stream.close()
-            self.close()
-        else:
-            self.zmq_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.zmq_stream, msg)
-
-    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.
-        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):
-        km = self.kernel_manager
-        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',
-            )
-        super(IOPubHandler, self).on_close()
-    
-    def _send_status_message(self, status):
-        msg = self.session.msg("status",
-            {'execution_state': status}
-        )
-        self.write_message(jsonapi.dumps(msg, default=date_default))
-
-    def on_kernel_restarted(self):
-        logging.warn("kernel %s restarted", self.kernel_id)
-        self._send_status_message('restarting')
-
-    def on_restart_failed(self):
-        logging.error("kernel %s restarted failed!", self.kernel_id)
-        self._send_status_message('dead')
-    
-    def on_message(self, msg):
-        """IOPub messages make no sense"""
-        pass
-
-class ShellHandler(ZMQChannelHandler):
-    channel = 'shell'
-
-class StdinHandler(ZMQChannelHandler):
-    channel = 'stdin'
-
-
-#-----------------------------------------------------------------------------
-# Notebook web service handlers
-#-----------------------------------------------------------------------------
-
 class NotebookRedirectHandler(IPythonHandler):
     
     @authenticate_unless_readonly
@@ -620,312 +66,10 @@ class NotebookRedirectHandler(IPythonHandler):
             raise HTTPError(404)
 
 
-class NotebookRootHandler(IPythonHandler):
-
-    @authenticate_unless_readonly
-    def get(self):
-        nbm = self.notebook_manager
-        km = self.kernel_manager
-        files = nbm.list_notebooks()
-        for f in files :
-            f['kernel_id'] = km.kernel_for_notebook(f['notebook_id'])
-        self.finish(jsonapi.dumps(files))
-
-    @web.authenticated
-    def post(self):
-        nbm = self.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', '{0}notebooks/{1}'.format(self.base_project_url, notebook_id))
-        self.finish(jsonapi.dumps(notebook_id))
-
-
-class NotebookHandler(IPythonHandler):
-
-    SUPPORTED_METHODS = ('GET', 'PUT', 'DELETE')
-
-    @authenticate_unless_readonly
-    def get(self, notebook_id):
-        nbm = self.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.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):
-        self.notebook_manager.delete_notebook(notebook_id)
-        self.set_status(204)
-        self.finish()
-
-
-class NotebookCheckpointsHandler(IPythonHandler):
-    
-    SUPPORTED_METHODS = ('GET', 'POST')
-    
-    @web.authenticated
-    def get(self, notebook_id):
-        """get lists checkpoints for a notebook"""
-        nbm = self.notebook_manager
-        checkpoints = nbm.list_checkpoints(notebook_id)
-        data = jsonapi.dumps(checkpoints, default=date_default)
-        self.finish(data)
-    
-    @web.authenticated
-    def post(self, notebook_id):
-        """post creates a new checkpoint"""
-        nbm = self.notebook_manager
-        checkpoint = nbm.create_checkpoint(notebook_id)
-        data = jsonapi.dumps(checkpoint, default=date_default)
-        self.set_header('Location', '{0}notebooks/{1}/checkpoints/{2}'.format(
-            self.base_project_url, notebook_id, checkpoint['checkpoint_id']
-        ))
-        
-        self.finish(data)
-
-
-class ModifyNotebookCheckpointsHandler(IPythonHandler):
-    
-    SUPPORTED_METHODS = ('POST', 'DELETE')
-    
-    @web.authenticated
-    def post(self, notebook_id, checkpoint_id):
-        """post restores a notebook from a checkpoint"""
-        nbm = self.notebook_manager
-        nbm.restore_checkpoint(notebook_id, checkpoint_id)
-        self.set_status(204)
-        self.finish()
-    
-    @web.authenticated
-    def delete(self, notebook_id, checkpoint_id):
-        """delete clears a checkpoint for a given notebook"""
-        nbm = self.notebook_manager
-        nbm.delte_checkpoint(notebook_id, checkpoint_id)
-        self.set_status(204)
-        self.finish()
-
-
 class NotebookCopyHandler(IPythonHandler):
 
     @web.authenticated
     def get(self, notebook_id):
         notebook_id = self.notebook_manager.copy_notebook(notebook_id)
-        self.redirect('/'+urljoin(self.base_project_url, notebook_id))
-
-
-#-----------------------------------------------------------------------------
-# Cluster handlers
-#-----------------------------------------------------------------------------
-
-
-class MainClusterHandler(IPythonHandler):
-
-    @web.authenticated
-    def get(self):
-        self.finish(jsonapi.dumps(self.cluster_manager.list_profiles()))
-
-
-class ClusterProfileHandler(IPythonHandler):
-
-    @web.authenticated
-    def get(self, profile):
-        self.finish(jsonapi.dumps(self.cluster_manager.profile_info(profile)))
-
-
-class ClusterActionHandler(IPythonHandler):
-
-    @web.authenticated
-    def post(self, profile, action):
-        cm = self.cluster_manager
-        if action == 'start':
-            n = self.get_argument('n',default=None)
-            if n is None:
-                data = cm.start_cluster(profile)
-            else:
-                data = cm.start_cluster(profile, int(n))
-        if action == 'stop':
-            data = cm.stop_cluster(profile)
-        self.finish(jsonapi.dumps(data))
-
-
-#-----------------------------------------------------------------------------
-# File handler
-#-----------------------------------------------------------------------------
-
-# to minimize subclass changes:
-HTTPError = web.HTTPError
-
-class FileFindHandler(web.StaticFileHandler):
-    """subclass of StaticFileHandler for serving files from a search path"""
-    
-    _static_paths = {}
-    # _lock is needed for tornado < 2.2.0 compat
-    _lock = threading.Lock()  # protects _static_hashes
-    
-    def initialize(self, path, default_filename=None):
-        if isinstance(path, basestring):
-            path = [path]
-        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):
-                raise HTTPError(403, "%s is not in root static directory", path)
-        
-            cls._static_paths[path] = abspath
-            return abspath
-    
-    def get(self, path, include_body=True):
-        path = self.parse_url_path(path)
-        
-        # begin subclass override
-        abspath = self.locate_file(path, self.roots)
-        # end subclass override
-        
-        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):
-            raise HTTPError(404)
-        if not os.path.isfile(abspath):
-            raise HTTPError(403, "%s is not a file", path)
-
-        stat_result = os.stat(abspath)
-        modified = datetime.datetime.utcfromtimestamp(stat_result[stat.ST_MTIME])
-
-        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)
-            if_since = datetime.datetime(*date_tuple[:6])
-            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:
-        static_paths = settings['static_path']
-        if isinstance(static_paths, basestring):
-            static_paths = [static_paths]
-        roots = tuple(
-            os.path.abspath(os.path.expanduser(p)) + os.path.sep for p in static_paths
-        )
-
-        try:
-            abs_path = filefind(path, roots)
-        except IOError:
-            app_log.error("Could not find static file %r", path)
-            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:
-                    app_log.error("Could not open static file %r", path)
-                    hashes[abs_path] = None
-            hsh = hashes.get(abs_path)
-            if hsh:
-                return hsh[:5]
-        return None
-
-
-    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
-
+        self.redirect('/'+url_path_join(self.base_project_url, notebook_id))
 
diff --git a/IPython/frontend/html/notebook/handlers/notebooksapi.py b/IPython/frontend/html/notebook/handlers/notebooksapi.py
index 32ec552..c0f4db7 100644
--- a/IPython/frontend/html/notebook/handlers/notebooksapi.py
+++ b/IPython/frontend/html/notebook/handlers/notebooksapi.py
@@ -1,4 +1,4 @@
-"""Tornado handlers for the notebook.
+"""Tornado handlers for the notebooks web service.
 
 Authors:
 
@@ -16,610 +16,18 @@ Authors:
 # Imports
 #-----------------------------------------------------------------------------
 
-import Cookie
-import datetime
-import email.utils
-import hashlib
-import logging
-import mimetypes
-import os
-import stat
-import threading
-import time
-import uuid
-
-from tornado.escape import url_escape
 from tornado import web
-from tornado import websocket
-
-try:
-    from tornado.log import app_log
-except ImportError:
-    app_log = logging.getLogger()
 
-from zmq.eventloop import ioloop
 from zmq.utils import jsonapi
 
-from IPython.config import Application
-from IPython.external.decorator import decorator
-from IPython.kernel.zmq.session import Session
-from IPython.lib.security import passwd_check
 from IPython.utils.jsonutil import date_default
-from IPython.utils.path import filefind
-from IPython.utils.py3compat import PY3
-
-try:
-    from docutils.core import publish_string
-except ImportError:
-    publish_string = None
-
-#-----------------------------------------------------------------------------
-# Monkeypatch for Tornado <= 2.1.1 - Remove when no longer necessary!
-#-----------------------------------------------------------------------------
-
-# 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
-
-if tornado.version_info <= (2,1,1):
-
-    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()
-
-    websocket.WebSocketHandler._execute = _execute
-    del _execute
-
-#-----------------------------------------------------------------------------
-# Decorator for disabling read-only handlers
-#-----------------------------------------------------------------------------
-
-@decorator
-def not_if_readonly(f, self, *args, **kwargs):
-    if self.settings.get('read_only', False):
-        raise web.HTTPError(403, "Notebook server is read-only")
-    else:
-        return f(self, *args, **kwargs)
-
-@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)
-
-    if self.settings.get('read_only', False):
-        return f(self, *args, **kwargs)
-    else:
-        return auth_f(self, *args, **kwargs)
-
-def urljoin(*pieces):
-    """Join components of url into a relative url
-
-    Use to prevent double slash when joining subpath
-    """
-    striped = [s.strip('/') for s in pieces]
-    return '/'.join(s for s in striped if s)
-
-#-----------------------------------------------------------------------------
-# Top-level handlers
-#-----------------------------------------------------------------------------
-
-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):
-    """A RequestHandler with an authenticated user."""
-
-    def clear_login_cookie(self):
-        self.clear_cookie(self.cookie_name)
-    
-    def get_current_user(self):
-        user_id = self.get_secure_cookie(self.cookie_name)
-        # For now the user_id should not return empty, but it could eventually
-        if user_id == '':
-            user_id = 'anonymous'
-        if user_id is None:
-            # prevent extra Invalid cookie sig warnings:
-            self.clear_login_cookie()
-            if not self.read_only and not self.login_available:
-                user_id = 'anonymous'
-        return user_id
-
-    @property
-    def cookie_name(self):
-        return self.settings.get('cookie_name', '')
-    
-    @property
-    def password(self):
-        """our password"""
-        return self.settings.get('password', '')
-    
-    @property
-    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.
-
-        """
-        return bool(self.settings.get('password', ''))
-
-    @property
-    def read_only(self):
-        """Is the notebook read-only?
-
-        """
-        return self.settings.get('read_only', False)
-
-
-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)
-    
-    @property
-    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
-    def use_less(self):
-        """Use less instead of css in templates"""
-        return self.settings.get('use_less', False)
-    
-    #---------------------------------------------------------------
-    # URLs
-    #---------------------------------------------------------------
-    
-    @property
-    def ws_url(self):
-        """websocket url matching the current request
-
-        turns http[s]://host[:port] into
-                ws[s]://host[:port]
-        """
-        proto = self.request.protocol.replace('http', 'ws')
-        host = self.settings.get('websocket_host', '')
-        # default to config value
-        if host == '':
-            host = self.request.host # get from request
-        return "%s://%s" % (proto, host)
-    
-    @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,
-        )
-
-class AuthenticatedFileHandler(IPythonHandler, web.StaticFileHandler):
-    """static files should only be accessible when logged in"""
-
-    @authenticate_unless_readonly
-    def get(self, path):
-        return web.StaticFileHandler.get(self, path)
-
-
-class ProjectDashboardHandler(IPythonHandler):
-
-    @authenticate_unless_readonly
-    def get(self):
-        self.write(self.render_template('projectdashboard.html',
-            project=self.project,
-            project_component=self.project.split('/'),
-        ))
-
-
-class LoginHandler(IPythonHandler):
-
-    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,
-        ))
-
-    def get(self):
-        if self.current_user:
-            self.redirect(self.get_argument('next', default=self.base_project_url))
-        else:
-            self._render()
-
-    def post(self):
-        pwd = self.get_argument('password', default=u'')
-        if self.login_available:
-            if passwd_check(self.password, pwd):
-                self.set_secure_cookie(self.cookie_name, str(uuid.uuid4()))
-            else:
-                self._render(message={'error': 'Invalid password'})
-                return
-
-        self.redirect(self.get_argument('next', default=self.base_project_url))
-
-
-class LogoutHandler(IPythonHandler):
-
-    def get(self):
-        self.clear_login_cookie()
-        if self.login_available:
-            message = {'info': 'Successfully logged out.'}
-        else:
-            message = {'warning': 'Cannot log out.  Notebook authentication '
-                       'is disabled.'}
-        self.write(self.render_template('logout.html',
-                    message=message))
-
-
-class NewHandler(IPythonHandler):
-
-    @web.authenticated
-    def get(self):
-        notebook_id = self.notebook_manager.new_notebook()
-        self.redirect('/' + urljoin(self.base_project_url, notebook_id))
-
-class NamedNotebookHandler(IPythonHandler):
-
-    @authenticate_unless_readonly
-    def get(self, notebook_id):
-        nbm = self.notebook_manager
-        if not nbm.notebook_exists(notebook_id):
-            raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)       
-        self.write(self.render_template('notebook.html',
-            project=self.project,
-            notebook_id=notebook_id,
-            kill_kernel=False,
-            mathjax_url=self.mathjax_url,
-            )
-        )
-
-
-#-----------------------------------------------------------------------------
-# Kernel handlers
-#-----------------------------------------------------------------------------
-
-
-class MainKernelHandler(IPythonHandler):
-
-    @web.authenticated
-    def get(self):
-        km = self.kernel_manager
-        self.finish(jsonapi.dumps(km.list_kernel_ids()))
-
-    @web.authenticated
-    def post(self):
-        km = self.kernel_manager
-        nbm = self.notebook_manager
-        notebook_id = self.get_argument('notebook', default=None)
-        kernel_id = km.start_kernel(notebook_id, cwd=nbm.notebook_dir)
-        data = {'ws_url':self.ws_url,'kernel_id':kernel_id}
-        self.set_header('Location', '{0}kernels/{1}'.format(self.base_kernel_url, kernel_id))
-        self.finish(jsonapi.dumps(data))
-
-
-class KernelHandler(IPythonHandler):
-
-    SUPPORTED_METHODS = ('DELETE')
-
-    @web.authenticated
-    def delete(self, kernel_id):
-        km = self.kernel_manager
-        km.shutdown_kernel(kernel_id)
-        self.set_status(204)
-        self.finish()
-
-
-class KernelActionHandler(IPythonHandler):
-
-    @web.authenticated
-    def post(self, kernel_id, action):
-        km = self.kernel_manager
-        if action == 'interrupt':
-            km.interrupt_kernel(kernel_id)
-            self.set_status(204)
-        if action == 'restart':
-            km.restart_kernel(kernel_id)
-            data = {'ws_url':self.ws_url, 'kernel_id':kernel_id}
-            self.set_header('Location', '{0}kernels/{1}'.format(self.base_kernel_url, kernel_id))
-            self.write(jsonapi.dumps(data))
-        self.finish()
-
-
-class ZMQStreamHandler(websocket.WebSocketHandler):
-    
-    def clear_cookie(self, *args, **kwargs):
-        """meaningless for websockets"""
-        pass
-
-    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, default=date_default)
-
-    def _on_zmq_reply(self, msg_list):
-        # Sometimes this gets triggered when the on_close method is scheduled in the
-        # eventloop but hasn't been called.
-        if self.stream.closed(): return
-        try:
-            msg = self._reserialize_reply(msg_list)
-        except Exception:
-            self.log.critical("Malformed message: %r" % msg_list, exc_info=True)
-        else:
-            self.write_message(msg)
-
-    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
-
-
-class AuthenticatedZMQStreamHandler(ZMQStreamHandler, IPythonHandler):
-
-    def open(self, kernel_id):
-        self.kernel_id = kernel_id.decode('ascii')
-        self.session = Session(config=self.config)
-        self.save_on_message = self.on_message
-        self.on_message = self.on_first_message
-
-    def _inject_cookie_message(self, msg):
-        """Inject the first message, which is the document cookie,
-        for authentication."""
-        if not PY3 and isinstance(msg, unicode):
-            # Cookie constructor doesn't accept unicode strings
-            # under Python 2.x for some reason
-            msg = msg.encode('utf8', 'replace')
-        try:
-            identity, msg = msg.split(':', 1)
-            self.session.session = identity.decode('ascii')
-        except Exception:
-            logging.error("First ws message didn't have the form 'identity:[cookie]' - %r", msg)
-        
-        try:
-            self.request._cookies = Cookie.SimpleCookie(msg)
-        except:
-            self.log.warn("couldn't parse cookie string: %s",msg, exc_info=True)
-
-    def on_first_message(self, msg):
-        self._inject_cookie_message(msg)
-        if self.get_current_user() is None:
-            self.log.warn("Couldn't authenticate WebSocket connection")
-            raise web.HTTPError(403)
-        self.on_message = self.save_on_message
-
-
-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)
-        self.zmq_stream = meth(self.kernel_id, identity=self.session.bsession)
-    
-    def initialize(self, *args, **kwargs):
-        self.zmq_stream = None
-    
-    def on_first_message(self, msg):
-        try:
-            super(ZMQChannelHandler, self).on_first_message(msg)
-        except web.HTTPError:
-            self.close()
-            return
-        try:
-            self.create_stream()
-        except web.HTTPError:
-            # WebSockets don't response to traditional error codes so we
-            # close the connection.
-            if not self.stream.closed():
-                self.stream.close()
-            self.close()
-        else:
-            self.zmq_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.zmq_stream, msg)
-
-    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.
-        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):
-        km = self.kernel_manager
-        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',
-            )
-        super(IOPubHandler, self).on_close()
-    
-    def _send_status_message(self, status):
-        msg = self.session.msg("status",
-            {'execution_state': status}
-        )
-        self.write_message(jsonapi.dumps(msg, default=date_default))
-
-    def on_kernel_restarted(self):
-        logging.warn("kernel %s restarted", self.kernel_id)
-        self._send_status_message('restarting')
-
-    def on_restart_failed(self):
-        logging.error("kernel %s restarted failed!", self.kernel_id)
-        self._send_status_message('dead')
-    
-    def on_message(self, msg):
-        """IOPub messages make no sense"""
-        pass
-
-class ShellHandler(ZMQChannelHandler):
-    channel = 'shell'
-
-class StdinHandler(ZMQChannelHandler):
-    channel = 'stdin'
 
+from .base import IPythonHandler, authenticate_unless_readonly
 
 #-----------------------------------------------------------------------------
 # Notebook web service handlers
 #-----------------------------------------------------------------------------
 
-class NotebookRedirectHandler(IPythonHandler):
-    
-    @authenticate_unless_readonly
-    def get(self, notebook_name):
-        # strip trailing .ipynb:
-        notebook_name = os.path.splitext(notebook_name)[0]
-        notebook_id = self.notebook_manager.rev_mapping.get(notebook_name, '')
-        if notebook_id:
-            url = self.settings.get('base_project_url', '/') + notebook_id
-            return self.redirect(url)
-        else:
-            raise HTTPError(404)
-
-
 class NotebookRootHandler(IPythonHandler):
 
     @authenticate_unless_readonly
@@ -726,206 +134,5 @@ class ModifyNotebookCheckpointsHandler(IPythonHandler):
         self.finish()
 
 
-class NotebookCopyHandler(IPythonHandler):
-
-    @web.authenticated
-    def get(self, notebook_id):
-        notebook_id = self.notebook_manager.copy_notebook(notebook_id)
-        self.redirect('/'+urljoin(self.base_project_url, notebook_id))
-
-
-#-----------------------------------------------------------------------------
-# Cluster handlers
-#-----------------------------------------------------------------------------
-
-
-class MainClusterHandler(IPythonHandler):
-
-    @web.authenticated
-    def get(self):
-        self.finish(jsonapi.dumps(self.cluster_manager.list_profiles()))
-
-
-class ClusterProfileHandler(IPythonHandler):
-
-    @web.authenticated
-    def get(self, profile):
-        self.finish(jsonapi.dumps(self.cluster_manager.profile_info(profile)))
-
-
-class ClusterActionHandler(IPythonHandler):
-
-    @web.authenticated
-    def post(self, profile, action):
-        cm = self.cluster_manager
-        if action == 'start':
-            n = self.get_argument('n',default=None)
-            if n is None:
-                data = cm.start_cluster(profile)
-            else:
-                data = cm.start_cluster(profile, int(n))
-        if action == 'stop':
-            data = cm.stop_cluster(profile)
-        self.finish(jsonapi.dumps(data))
-
-
-#-----------------------------------------------------------------------------
-# File handler
-#-----------------------------------------------------------------------------
-
-# to minimize subclass changes:
-HTTPError = web.HTTPError
-
-class FileFindHandler(web.StaticFileHandler):
-    """subclass of StaticFileHandler for serving files from a search path"""
-    
-    _static_paths = {}
-    # _lock is needed for tornado < 2.2.0 compat
-    _lock = threading.Lock()  # protects _static_hashes
-    
-    def initialize(self, path, default_filename=None):
-        if isinstance(path, basestring):
-            path = [path]
-        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):
-                raise HTTPError(403, "%s is not in root static directory", path)
-        
-            cls._static_paths[path] = abspath
-            return abspath
-    
-    def get(self, path, include_body=True):
-        path = self.parse_url_path(path)
-        
-        # begin subclass override
-        abspath = self.locate_file(path, self.roots)
-        # end subclass override
-        
-        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):
-            raise HTTPError(404)
-        if not os.path.isfile(abspath):
-            raise HTTPError(403, "%s is not a file", path)
-
-        stat_result = os.stat(abspath)
-        modified = datetime.datetime.utcfromtimestamp(stat_result[stat.ST_MTIME])
-
-        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)
-            if_since = datetime.datetime(*date_tuple[:6])
-            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:
-        static_paths = settings['static_path']
-        if isinstance(static_paths, basestring):
-            static_paths = [static_paths]
-        roots = tuple(
-            os.path.abspath(os.path.expanduser(p)) + os.path.sep for p in static_paths
-        )
-
-        try:
-            abs_path = filefind(path, roots)
-        except IOError:
-            app_log.error("Could not find static file %r", path)
-            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:
-                    app_log.error("Could not open static file %r", path)
-                    hashes[abs_path] = None
-            hsh = hashes.get(abs_path)
-            if hsh:
-                return hsh[:5]
-        return None
-
-
-    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
 
 
diff --git a/IPython/frontend/html/notebook/handlers/tree.py b/IPython/frontend/html/notebook/handlers/tree.py
index 32ec552..ffcfce6 100644
--- a/IPython/frontend/html/notebook/handlers/tree.py
+++ b/IPython/frontend/html/notebook/handlers/tree.py
@@ -1,4 +1,4 @@
-"""Tornado handlers for the notebook.
+"""Tornado handlers for the tree view.
 
 Authors:
 
@@ -6,7 +6,7 @@ Authors:
 """
 
 #-----------------------------------------------------------------------------
-#  Copyright (C) 2008-2011  The IPython Development Team
+#  Copyright (C) 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.
@@ -16,297 +16,12 @@ Authors:
 # Imports
 #-----------------------------------------------------------------------------
 
-import Cookie
-import datetime
-import email.utils
-import hashlib
-import logging
-import mimetypes
-import os
-import stat
-import threading
-import time
-import uuid
-
-from tornado.escape import url_escape
-from tornado import web
-from tornado import websocket
-
-try:
-    from tornado.log import app_log
-except ImportError:
-    app_log = logging.getLogger()
-
-from zmq.eventloop import ioloop
-from zmq.utils import jsonapi
-
-from IPython.config import Application
-from IPython.external.decorator import decorator
-from IPython.kernel.zmq.session import Session
-from IPython.lib.security import passwd_check
-from IPython.utils.jsonutil import date_default
-from IPython.utils.path import filefind
-from IPython.utils.py3compat import PY3
-
-try:
-    from docutils.core import publish_string
-except ImportError:
-    publish_string = None
-
-#-----------------------------------------------------------------------------
-# Monkeypatch for Tornado <= 2.1.1 - Remove when no longer necessary!
-#-----------------------------------------------------------------------------
-
-# 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
-
-if tornado.version_info <= (2,1,1):
-
-    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()
-
-    websocket.WebSocketHandler._execute = _execute
-    del _execute
-
-#-----------------------------------------------------------------------------
-# Decorator for disabling read-only handlers
-#-----------------------------------------------------------------------------
-
-@decorator
-def not_if_readonly(f, self, *args, **kwargs):
-    if self.settings.get('read_only', False):
-        raise web.HTTPError(403, "Notebook server is read-only")
-    else:
-        return f(self, *args, **kwargs)
-
-@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)
-
-    if self.settings.get('read_only', False):
-        return f(self, *args, **kwargs)
-    else:
-        return auth_f(self, *args, **kwargs)
-
-def urljoin(*pieces):
-    """Join components of url into a relative url
-
-    Use to prevent double slash when joining subpath
-    """
-    striped = [s.strip('/') for s in pieces]
-    return '/'.join(s for s in striped if s)
+from .base import IPythonHandler, authenticate_unless_readonly
 
 #-----------------------------------------------------------------------------
-# Top-level handlers
+# Handlers
 #-----------------------------------------------------------------------------
 
-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):
-    """A RequestHandler with an authenticated user."""
-
-    def clear_login_cookie(self):
-        self.clear_cookie(self.cookie_name)
-    
-    def get_current_user(self):
-        user_id = self.get_secure_cookie(self.cookie_name)
-        # For now the user_id should not return empty, but it could eventually
-        if user_id == '':
-            user_id = 'anonymous'
-        if user_id is None:
-            # prevent extra Invalid cookie sig warnings:
-            self.clear_login_cookie()
-            if not self.read_only and not self.login_available:
-                user_id = 'anonymous'
-        return user_id
-
-    @property
-    def cookie_name(self):
-        return self.settings.get('cookie_name', '')
-    
-    @property
-    def password(self):
-        """our password"""
-        return self.settings.get('password', '')
-    
-    @property
-    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.
-
-        """
-        return bool(self.settings.get('password', ''))
-
-    @property
-    def read_only(self):
-        """Is the notebook read-only?
-
-        """
-        return self.settings.get('read_only', False)
-
-
-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)
-    
-    @property
-    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
-    def use_less(self):
-        """Use less instead of css in templates"""
-        return self.settings.get('use_less', False)
-    
-    #---------------------------------------------------------------
-    # URLs
-    #---------------------------------------------------------------
-    
-    @property
-    def ws_url(self):
-        """websocket url matching the current request
-
-        turns http[s]://host[:port] into
-                ws[s]://host[:port]
-        """
-        proto = self.request.protocol.replace('http', 'ws')
-        host = self.settings.get('websocket_host', '')
-        # default to config value
-        if host == '':
-            host = self.request.host # get from request
-        return "%s://%s" % (proto, host)
-    
-    @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,
-        )
-
-class AuthenticatedFileHandler(IPythonHandler, web.StaticFileHandler):
-    """static files should only be accessible when logged in"""
-
-    @authenticate_unless_readonly
-    def get(self, path):
-        return web.StaticFileHandler.get(self, path)
-
 
 class ProjectDashboardHandler(IPythonHandler):
 
@@ -316,616 +31,3 @@ class ProjectDashboardHandler(IPythonHandler):
             project=self.project,
             project_component=self.project.split('/'),
         ))
-
-
-class LoginHandler(IPythonHandler):
-
-    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,
-        ))
-
-    def get(self):
-        if self.current_user:
-            self.redirect(self.get_argument('next', default=self.base_project_url))
-        else:
-            self._render()
-
-    def post(self):
-        pwd = self.get_argument('password', default=u'')
-        if self.login_available:
-            if passwd_check(self.password, pwd):
-                self.set_secure_cookie(self.cookie_name, str(uuid.uuid4()))
-            else:
-                self._render(message={'error': 'Invalid password'})
-                return
-
-        self.redirect(self.get_argument('next', default=self.base_project_url))
-
-
-class LogoutHandler(IPythonHandler):
-
-    def get(self):
-        self.clear_login_cookie()
-        if self.login_available:
-            message = {'info': 'Successfully logged out.'}
-        else:
-            message = {'warning': 'Cannot log out.  Notebook authentication '
-                       'is disabled.'}
-        self.write(self.render_template('logout.html',
-                    message=message))
-
-
-class NewHandler(IPythonHandler):
-
-    @web.authenticated
-    def get(self):
-        notebook_id = self.notebook_manager.new_notebook()
-        self.redirect('/' + urljoin(self.base_project_url, notebook_id))
-
-class NamedNotebookHandler(IPythonHandler):
-
-    @authenticate_unless_readonly
-    def get(self, notebook_id):
-        nbm = self.notebook_manager
-        if not nbm.notebook_exists(notebook_id):
-            raise web.HTTPError(404, u'Notebook does not exist: %s' % notebook_id)       
-        self.write(self.render_template('notebook.html',
-            project=self.project,
-            notebook_id=notebook_id,
-            kill_kernel=False,
-            mathjax_url=self.mathjax_url,
-            )
-        )
-
-
-#-----------------------------------------------------------------------------
-# Kernel handlers
-#-----------------------------------------------------------------------------
-
-
-class MainKernelHandler(IPythonHandler):
-
-    @web.authenticated
-    def get(self):
-        km = self.kernel_manager
-        self.finish(jsonapi.dumps(km.list_kernel_ids()))
-
-    @web.authenticated
-    def post(self):
-        km = self.kernel_manager
-        nbm = self.notebook_manager
-        notebook_id = self.get_argument('notebook', default=None)
-        kernel_id = km.start_kernel(notebook_id, cwd=nbm.notebook_dir)
-        data = {'ws_url':self.ws_url,'kernel_id':kernel_id}
-        self.set_header('Location', '{0}kernels/{1}'.format(self.base_kernel_url, kernel_id))
-        self.finish(jsonapi.dumps(data))
-
-
-class KernelHandler(IPythonHandler):
-
-    SUPPORTED_METHODS = ('DELETE')
-
-    @web.authenticated
-    def delete(self, kernel_id):
-        km = self.kernel_manager
-        km.shutdown_kernel(kernel_id)
-        self.set_status(204)
-        self.finish()
-
-
-class KernelActionHandler(IPythonHandler):
-
-    @web.authenticated
-    def post(self, kernel_id, action):
-        km = self.kernel_manager
-        if action == 'interrupt':
-            km.interrupt_kernel(kernel_id)
-            self.set_status(204)
-        if action == 'restart':
-            km.restart_kernel(kernel_id)
-            data = {'ws_url':self.ws_url, 'kernel_id':kernel_id}
-            self.set_header('Location', '{0}kernels/{1}'.format(self.base_kernel_url, kernel_id))
-            self.write(jsonapi.dumps(data))
-        self.finish()
-
-
-class ZMQStreamHandler(websocket.WebSocketHandler):
-    
-    def clear_cookie(self, *args, **kwargs):
-        """meaningless for websockets"""
-        pass
-
-    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, default=date_default)
-
-    def _on_zmq_reply(self, msg_list):
-        # Sometimes this gets triggered when the on_close method is scheduled in the
-        # eventloop but hasn't been called.
-        if self.stream.closed(): return
-        try:
-            msg = self._reserialize_reply(msg_list)
-        except Exception:
-            self.log.critical("Malformed message: %r" % msg_list, exc_info=True)
-        else:
-            self.write_message(msg)
-
-    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
-
-
-class AuthenticatedZMQStreamHandler(ZMQStreamHandler, IPythonHandler):
-
-    def open(self, kernel_id):
-        self.kernel_id = kernel_id.decode('ascii')
-        self.session = Session(config=self.config)
-        self.save_on_message = self.on_message
-        self.on_message = self.on_first_message
-
-    def _inject_cookie_message(self, msg):
-        """Inject the first message, which is the document cookie,
-        for authentication."""
-        if not PY3 and isinstance(msg, unicode):
-            # Cookie constructor doesn't accept unicode strings
-            # under Python 2.x for some reason
-            msg = msg.encode('utf8', 'replace')
-        try:
-            identity, msg = msg.split(':', 1)
-            self.session.session = identity.decode('ascii')
-        except Exception:
-            logging.error("First ws message didn't have the form 'identity:[cookie]' - %r", msg)
-        
-        try:
-            self.request._cookies = Cookie.SimpleCookie(msg)
-        except:
-            self.log.warn("couldn't parse cookie string: %s",msg, exc_info=True)
-
-    def on_first_message(self, msg):
-        self._inject_cookie_message(msg)
-        if self.get_current_user() is None:
-            self.log.warn("Couldn't authenticate WebSocket connection")
-            raise web.HTTPError(403)
-        self.on_message = self.save_on_message
-
-
-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)
-        self.zmq_stream = meth(self.kernel_id, identity=self.session.bsession)
-    
-    def initialize(self, *args, **kwargs):
-        self.zmq_stream = None
-    
-    def on_first_message(self, msg):
-        try:
-            super(ZMQChannelHandler, self).on_first_message(msg)
-        except web.HTTPError:
-            self.close()
-            return
-        try:
-            self.create_stream()
-        except web.HTTPError:
-            # WebSockets don't response to traditional error codes so we
-            # close the connection.
-            if not self.stream.closed():
-                self.stream.close()
-            self.close()
-        else:
-            self.zmq_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.zmq_stream, msg)
-
-    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.
-        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):
-        km = self.kernel_manager
-        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',
-            )
-        super(IOPubHandler, self).on_close()
-    
-    def _send_status_message(self, status):
-        msg = self.session.msg("status",
-            {'execution_state': status}
-        )
-        self.write_message(jsonapi.dumps(msg, default=date_default))
-
-    def on_kernel_restarted(self):
-        logging.warn("kernel %s restarted", self.kernel_id)
-        self._send_status_message('restarting')
-
-    def on_restart_failed(self):
-        logging.error("kernel %s restarted failed!", self.kernel_id)
-        self._send_status_message('dead')
-    
-    def on_message(self, msg):
-        """IOPub messages make no sense"""
-        pass
-
-class ShellHandler(ZMQChannelHandler):
-    channel = 'shell'
-
-class StdinHandler(ZMQChannelHandler):
-    channel = 'stdin'
-
-
-#-----------------------------------------------------------------------------
-# Notebook web service handlers
-#-----------------------------------------------------------------------------
-
-class NotebookRedirectHandler(IPythonHandler):
-    
-    @authenticate_unless_readonly
-    def get(self, notebook_name):
-        # strip trailing .ipynb:
-        notebook_name = os.path.splitext(notebook_name)[0]
-        notebook_id = self.notebook_manager.rev_mapping.get(notebook_name, '')
-        if notebook_id:
-            url = self.settings.get('base_project_url', '/') + notebook_id
-            return self.redirect(url)
-        else:
-            raise HTTPError(404)
-
-
-class NotebookRootHandler(IPythonHandler):
-
-    @authenticate_unless_readonly
-    def get(self):
-        nbm = self.notebook_manager
-        km = self.kernel_manager
-        files = nbm.list_notebooks()
-        for f in files :
-            f['kernel_id'] = km.kernel_for_notebook(f['notebook_id'])
-        self.finish(jsonapi.dumps(files))
-
-    @web.authenticated
-    def post(self):
-        nbm = self.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', '{0}notebooks/{1}'.format(self.base_project_url, notebook_id))
-        self.finish(jsonapi.dumps(notebook_id))
-
-
-class NotebookHandler(IPythonHandler):
-
-    SUPPORTED_METHODS = ('GET', 'PUT', 'DELETE')
-
-    @authenticate_unless_readonly
-    def get(self, notebook_id):
-        nbm = self.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.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):
-        self.notebook_manager.delete_notebook(notebook_id)
-        self.set_status(204)
-        self.finish()
-
-
-class NotebookCheckpointsHandler(IPythonHandler):
-    
-    SUPPORTED_METHODS = ('GET', 'POST')
-    
-    @web.authenticated
-    def get(self, notebook_id):
-        """get lists checkpoints for a notebook"""
-        nbm = self.notebook_manager
-        checkpoints = nbm.list_checkpoints(notebook_id)
-        data = jsonapi.dumps(checkpoints, default=date_default)
-        self.finish(data)
-    
-    @web.authenticated
-    def post(self, notebook_id):
-        """post creates a new checkpoint"""
-        nbm = self.notebook_manager
-        checkpoint = nbm.create_checkpoint(notebook_id)
-        data = jsonapi.dumps(checkpoint, default=date_default)
-        self.set_header('Location', '{0}notebooks/{1}/checkpoints/{2}'.format(
-            self.base_project_url, notebook_id, checkpoint['checkpoint_id']
-        ))
-        
-        self.finish(data)
-
-
-class ModifyNotebookCheckpointsHandler(IPythonHandler):
-    
-    SUPPORTED_METHODS = ('POST', 'DELETE')
-    
-    @web.authenticated
-    def post(self, notebook_id, checkpoint_id):
-        """post restores a notebook from a checkpoint"""
-        nbm = self.notebook_manager
-        nbm.restore_checkpoint(notebook_id, checkpoint_id)
-        self.set_status(204)
-        self.finish()
-    
-    @web.authenticated
-    def delete(self, notebook_id, checkpoint_id):
-        """delete clears a checkpoint for a given notebook"""
-        nbm = self.notebook_manager
-        nbm.delte_checkpoint(notebook_id, checkpoint_id)
-        self.set_status(204)
-        self.finish()
-
-
-class NotebookCopyHandler(IPythonHandler):
-
-    @web.authenticated
-    def get(self, notebook_id):
-        notebook_id = self.notebook_manager.copy_notebook(notebook_id)
-        self.redirect('/'+urljoin(self.base_project_url, notebook_id))
-
-
-#-----------------------------------------------------------------------------
-# Cluster handlers
-#-----------------------------------------------------------------------------
-
-
-class MainClusterHandler(IPythonHandler):
-
-    @web.authenticated
-    def get(self):
-        self.finish(jsonapi.dumps(self.cluster_manager.list_profiles()))
-
-
-class ClusterProfileHandler(IPythonHandler):
-
-    @web.authenticated
-    def get(self, profile):
-        self.finish(jsonapi.dumps(self.cluster_manager.profile_info(profile)))
-
-
-class ClusterActionHandler(IPythonHandler):
-
-    @web.authenticated
-    def post(self, profile, action):
-        cm = self.cluster_manager
-        if action == 'start':
-            n = self.get_argument('n',default=None)
-            if n is None:
-                data = cm.start_cluster(profile)
-            else:
-                data = cm.start_cluster(profile, int(n))
-        if action == 'stop':
-            data = cm.stop_cluster(profile)
-        self.finish(jsonapi.dumps(data))
-
-
-#-----------------------------------------------------------------------------
-# File handler
-#-----------------------------------------------------------------------------
-
-# to minimize subclass changes:
-HTTPError = web.HTTPError
-
-class FileFindHandler(web.StaticFileHandler):
-    """subclass of StaticFileHandler for serving files from a search path"""
-    
-    _static_paths = {}
-    # _lock is needed for tornado < 2.2.0 compat
-    _lock = threading.Lock()  # protects _static_hashes
-    
-    def initialize(self, path, default_filename=None):
-        if isinstance(path, basestring):
-            path = [path]
-        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):
-                raise HTTPError(403, "%s is not in root static directory", path)
-        
-            cls._static_paths[path] = abspath
-            return abspath
-    
-    def get(self, path, include_body=True):
-        path = self.parse_url_path(path)
-        
-        # begin subclass override
-        abspath = self.locate_file(path, self.roots)
-        # end subclass override
-        
-        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):
-            raise HTTPError(404)
-        if not os.path.isfile(abspath):
-            raise HTTPError(403, "%s is not a file", path)
-
-        stat_result = os.stat(abspath)
-        modified = datetime.datetime.utcfromtimestamp(stat_result[stat.ST_MTIME])
-
-        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)
-            if_since = datetime.datetime(*date_tuple[:6])
-            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:
-        static_paths = settings['static_path']
-        if isinstance(static_paths, basestring):
-            static_paths = [static_paths]
-        roots = tuple(
-            os.path.abspath(os.path.expanduser(p)) + os.path.sep for p in static_paths
-        )
-
-        try:
-            abs_path = filefind(path, roots)
-        except IOError:
-            app_log.error("Could not find static file %r", path)
-            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:
-                    app_log.error("Could not open static file %r", path)
-                    hashes[abs_path] = None
-            hsh = hashes.get(abs_path)
-            if hsh:
-                return hsh[:5]
-        return None
-
-
-    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
-
-
diff --git a/IPython/frontend/html/notebook/notebookapp.py b/IPython/frontend/html/notebook/notebookapp.py
index 344fd3c..cf6b7e2 100644
--- a/IPython/frontend/html/notebook/notebookapp.py
+++ b/IPython/frontend/html/notebook/notebookapp.py
@@ -21,7 +21,6 @@ import errno
 import logging
 import os
 import random
-import re
 import select
 import signal
 import socket
@@ -64,40 +63,54 @@ from tornado import web
 # Our own libraries
 from IPython.frontend.html.notebook import DEFAULT_STATIC_FILES_PATH
 from .kernelmanager import MappingKernelManager
-from .handlers import (LoginHandler, LogoutHandler,
-    ProjectDashboardHandler, NewHandler, NamedNotebookHandler,
-    MainKernelHandler, KernelHandler, KernelActionHandler, IOPubHandler, StdinHandler,
-    ShellHandler, NotebookRootHandler, NotebookHandler, NotebookCopyHandler,
-    NotebookRedirectHandler, NotebookCheckpointsHandler, ModifyNotebookCheckpointsHandler,
-    AuthenticatedFileHandler, FileFindHandler,
-    MainClusterHandler, ClusterProfileHandler, ClusterActionHandler, 
+
+from .handlers.clustersapi import (
+    MainClusterHandler, ClusterProfileHandler, ClusterActionHandler
+)
+from .handlers.kernelsapi import (
+    MainKernelHandler, KernelHandler, KernelActionHandler,
+    IOPubHandler, StdinHandler, ShellHandler
+)
+from .handlers.notebooksapi import (
+    NotebookRootHandler, NotebookHandler,
+    NotebookCheckpointsHandler, ModifyNotebookCheckpointsHandler
+)
+from .handlers.tree import ProjectDashboardHandler
+from .handlers.login import LoginHandler
+from .handlers.logout import LogoutHandler
+from .handlers.notebooks import (
+    NewHandler, NamedNotebookHandler, 
+    NotebookCopyHandler, NotebookRedirectHandler
 )
+
+from .handlers.base import AuthenticatedFileHandler
+from .handlers.files import FileFindHandler
+
 from .nbmanager import NotebookManager
 from .filenbmanager import FileNotebookManager
 from .clustermanager import ClusterManager
 
 from IPython.config.application import catch_config_error, boolean_flag
 from IPython.core.application import BaseIPythonApplication
-from IPython.core.profiledir import ProfileDir
 from IPython.frontend.consoleapp import IPythonConsoleApp
 from IPython.kernel import swallow_argv
-from IPython.kernel.zmq.session import Session, default_secure
-from IPython.kernel.zmq.zmqshell import ZMQInteractiveShell
+from IPython.kernel.zmq.session import default_secure
 from IPython.kernel.zmq.kernelapp import (
     kernel_flags,
     kernel_aliases,
-    IPKernelApp
 )
 from IPython.utils.importstring import import_item
 from IPython.utils.localinterfaces import LOCALHOST
 from IPython.utils import submodule
 from IPython.utils.traitlets import (
-    Dict, Unicode, Integer, List, Enum, Bool,
+    Dict, Unicode, Integer, List, Bool,
     DottedObjectName
 )
 from IPython.utils import py3compat
 from IPython.utils.path import filefind
 
+from .utils import url_path_join
+
 #-----------------------------------------------------------------------------
 # Module globals
 #-----------------------------------------------------------------------------
@@ -122,12 +135,6 @@ ipython notebook --port=5555 --ip=*    # Listen on port 5555, all interfaces
 # Helper functions
 #-----------------------------------------------------------------------------
 
-def url_path_join(a,b):
-    if a.endswith('/') and b.startswith('/'):
-        return a[:-1]+b
-    else:
-        return a+b
-
 def random_ports(port, n):
     """Generate a list of n random ports near the given port.
 
diff --git a/IPython/frontend/html/notebook/utils.py b/IPython/frontend/html/notebook/utils.py
new file mode 100644
index 0000000..cf3fba8
--- /dev/null
+++ b/IPython/frontend/html/notebook/utils.py
@@ -0,0 +1,31 @@
+"""Notebook related utilities
+
+Authors:
+
+* Brian Granger
+"""
+
+#-----------------------------------------------------------------------------
+#  Copyright (C) 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
+#-----------------------------------------------------------------------------
+
+def url_path_join(*pieces):
+    """Join components of url into a relative url
+
+    Use to prevent double slash when joining subpath. This will leave the
+    initial and final / in place
+    """
+    initial = pieces[0].startswith('/')
+    final = pieces[-1].endswith('/')
+    striped = [s.strip('/') for s in pieces]
+    result = '/'.join(s for s in striped if s)
+    if initial: result = '/' + result
+    if final: result = result + '/'
+    return result