diff --git a/IPython/html/notebookapp.py b/IPython/html/notebookapp.py
index 85b3fc26f..1dccff76e 100644
--- a/IPython/html/notebookapp.py
+++ b/IPython/html/notebookapp.py
@@ -19,6 +19,8 @@ from __future__ import print_function
# stdlib
import errno
+import io
+import json
import logging
import os
import random
@@ -73,6 +75,7 @@ from .base.handlers import AuthenticatedFileHandler, FileFindHandler
from IPython.config.application import catch_config_error, boolean_flag
from IPython.core.application import BaseIPythonApplication
+from IPython.core.profiledir import ProfileDir
from IPython.consoleapp import IPythonConsoleApp
from IPython.kernel import swallow_argv
from IPython.kernel.zmq.session import default_secure
@@ -214,6 +217,27 @@ class NotebookWebApplication(web.Application):
return new_handlers
+class NbserverListApp(BaseIPythonApplication):
+
+ description="List currently running notebook servers in this profile."
+
+ flags = dict(
+ json=({'NbserverListApp': {'json': True}},
+ "Produce machine-readable JSON output."),
+ )
+
+ json = Bool(False, config=True,
+ help="If True, each line of output will be a JSON object with the "
+ "details from the server info file.")
+
+ def start(self):
+ if not self.json:
+ print("Currently running servers:")
+ for serverinfo in list_running_servers(self.profile):
+ if self.json:
+ print(json.dumps(serverinfo))
+ else:
+ print(serverinfo['url'], "::", serverinfo['notebook_dir'])
#-----------------------------------------------------------------------------
# Aliases and Flags
@@ -286,6 +310,10 @@ class NotebookApp(BaseIPythonApplication):
FileNotebookManager]
flags = Dict(flags)
aliases = Dict(aliases)
+
+ subcommands = dict(
+ list=(NbserverListApp, NbserverListApp.description.splitlines()[0]),
+ )
kernel_argv = List(Unicode)
@@ -502,6 +530,12 @@ class NotebookApp(BaseIPythonApplication):
"sent by the upstream reverse proxy. Neccesary if the proxy handles SSL")
)
+ info_file = Unicode()
+
+ def _info_file_default(self):
+ info_file = "nbserver-%s.json"%os.getpid()
+ return os.path.join(self.profile_dir.security_dir, info_file)
+
def parse_command_line(self, argv=None):
super(NotebookApp, self).parse_command_line(argv)
@@ -597,6 +631,20 @@ class NotebookApp(BaseIPythonApplication):
'no available port could be found.')
self.exit(1)
+ @property
+ def display_url(self):
+ ip = self.ip if self.ip else '[all ip addresses on your system]'
+ return self._url(ip)
+
+ @property
+ def connection_url(self):
+ ip = self.ip if self.ip else localhost()
+ return self._url(ip)
+
+ def _url(self, ip):
+ proto = 'https' if self.certfile else 'http'
+ return "%s://%s:%i%s" % (proto, ip, self.port, self.base_project_url)
+
def init_signal(self):
if not sys.platform.startswith('win'):
signal.signal(signal.SIGINT, self._handle_sigint)
@@ -669,7 +717,6 @@ class NotebookApp(BaseIPythonApplication):
elif status == 'unclean':
self.log.warn("components submodule unclean, you may see 404s on static/components")
self.log.warn("run `setup.py submodule` or `git submodule update` to update")
-
@catch_config_error
def initialize(self, argv=None):
@@ -694,33 +741,59 @@ class NotebookApp(BaseIPythonApplication):
"Return the current working directory and the server url information"
info = self.notebook_manager.info_string() + "\n"
info += "%d active kernels \n" % len(self.kernel_manager._kernels)
- return info + "The IPython Notebook is running at: %s" % self._url
+ return info + "The IPython Notebook is running at: %s" % self.display_url
+
+ def server_info(self):
+ """Return a JSONable dict of information about this server."""
+ return {'url': self.connection_url,
+ 'hostname': self.ip if self.ip else 'localhost',
+ 'port': self.port,
+ 'secure': bool(self.certfile),
+ 'base_project_url': self.base_project_url,
+ 'notebook_dir': os.path.abspath(self.notebook_manager.notebook_dir),
+ }
+
+ def write_server_info_file(self):
+ """Write the result of server_info() to the JSON file info_file."""
+ with open(self.info_file, 'w') as f:
+ json.dump(self.server_info(), f, indent=2)
+
+ def remove_server_info_file(self):
+ """Remove the nbserver-.json file created for this server.
+
+ Ignores the error raised when the file has already been removed.
+ """
+ try:
+ os.unlink(self.info_file)
+ except OSError as e:
+ if e.errno != errno.ENOENT:
+ raise
def start(self):
""" Start the IPython Notebook server app, after initialization
This method takes no arguments so all configuration and initialization
must be done prior to calling this method."""
- ip = self.ip if self.ip else '[all ip addresses on your system]'
- proto = 'https' if self.certfile else 'http'
+ if self.subapp is not None:
+ return self.subapp.start()
+
info = self.log.info
- self._url = "%s://%s:%i%s" % (proto, ip, self.port,
- self.base_project_url)
for line in self.notebook_info().split("\n"):
info(line)
info("Use Control-C to stop this server and shut down all kernels (twice to skip confirmation).")
+ self.write_server_info_file()
+
if self.open_browser or self.file_to_run:
- ip = self.ip or localhost()
try:
browser = webbrowser.get(self.browser or None)
except webbrowser.Error as e:
self.log.warn('No web browser found: %s.' % e)
browser = None
- nbdir = os.path.abspath(self.notebook_manager.notebook_dir)
f = self.file_to_run
if f:
+ nbdir = os.path.abspath(self.notebook_manager.notebook_dir)
if f.startswith(nbdir):
f = f[len(nbdir):]
else:
@@ -735,8 +808,8 @@ class NotebookApp(BaseIPythonApplication):
else:
url = url_path_join('tree', f)
if browser:
- b = lambda : browser.open("%s://%s:%i%s%s" % (proto, ip,
- self.port, self.base_project_url, url), new=2)
+ b = lambda : browser.open("%s%s" % (self.connection_url, url),
+ new=2)
threading.Thread(target=b).start()
try:
ioloop.IOLoop.instance().start()
@@ -744,7 +817,21 @@ class NotebookApp(BaseIPythonApplication):
info("Interrupted...")
finally:
self.cleanup_kernels()
+ self.remove_server_info_file()
+
+
+def list_running_servers(profile='default'):
+ """Iterate over the server info files of running notebook servers.
+ Given a profile name, find nbserver-* files in the security directory of
+ that profile, and yield dicts of their information, each one pertaining to
+ a currently running notebook server instance.
+ """
+ pd = ProfileDir.find_profile_dir_by_name(get_ipython_dir(), name=profile)
+ for file in os.listdir(pd.security_dir):
+ if file.startswith('nbserver-'):
+ with io.open(os.path.join(pd.security_dir, file), encoding='utf-8') as f:
+ yield json.load(f)
#-----------------------------------------------------------------------------
# Main entry point
diff --git a/IPython/html/tests/test_notebookapp.py b/IPython/html/tests/test_notebookapp.py
index 56509cdc9..4422da91a 100644
--- a/IPython/html/tests/test_notebookapp.py
+++ b/IPython/html/tests/test_notebookapp.py
@@ -14,6 +14,7 @@
import nose.tools as nt
import IPython.testing.tools as tt
+from IPython.html import notebookapp
#-----------------------------------------------------------------------------
# Test functions
@@ -23,3 +24,18 @@ def test_help_output():
"""ipython notebook --help-all works"""
tt.help_all_output_test('notebook')
+def test_server_info_file():
+ nbapp = notebookapp.NotebookApp(profile='nbserver_file_test')
+ def get_servers():
+ return list(notebookapp.list_running_servers(profile='nbserver_file_test'))
+ nbapp.initialize(argv=[])
+ nbapp.write_server_info_file()
+ servers = get_servers()
+ nt.assert_equal(len(servers), 1)
+ nt.assert_equal(servers[0]['port'], nbapp.port)
+ nt.assert_equal(servers[0]['url'], nbapp.connection_url)
+ nbapp.remove_server_info_file()
+ nt.assert_equal(get_servers(), [])
+
+ # The ENOENT error should be silenced.
+ nbapp.remove_server_info_file()
\ No newline at end of file