diff --git a/notebook/notebookapp.py b/notebook/notebookapp.py index 46c881bee..6aa42ce12 100755 --- a/notebook/notebookapp.py +++ b/notebook/notebookapp.py @@ -26,6 +26,7 @@ import signal import socket import sys +import tempfile import threading import time import warnings @@ -107,7 +108,7 @@ from notebook._sysinfo import get_sys_info from ._tz import utcnow, utcfromtimestamp -from .utils import url_path_join, check_pid, url_escape +from .utils import url_path_join, check_pid, url_escape, urljoin, pathname2url #----------------------------------------------------------------------------- # Module globals @@ -1184,6 +1185,13 @@ def _update_mathjax_config(self, change): def _default_info_file(self): info_file = "nbserver-%s.json" % os.getpid() return os.path.join(self.runtime_dir, info_file) + + browser_open_file = Unicode() + + @default('browser_open_file') + def _default_browser_open_file(self): + basename = "nbserver-%s-open.html" % os.getpid() + return os.path.join(self.runtime_dir, basename) pylab = Unicode('disabled', config=True, help=_(""" @@ -1697,6 +1705,67 @@ def remove_server_info_file(self): if e.errno != errno.ENOENT: raise + def write_browser_open_file(self): + """Write an nbserver--open.html file + + This can be used to open the notebook in a browser + """ + # default_url contains base_url, but so does connection_url + open_url = self.default_url[len(self.base_url):] + + with open(self.browser_open_file, 'w', encoding='utf-8') as f: + self._write_browser_open_file(open_url, f) + + def _write_browser_open_file(self, url, fh): + if self.token: + url = url_concat(url, {'token': self.one_time_token}) + url = url_path_join(self.connection_url, url) + + jinja2_env = self.web_app.settings['jinja2_env'] + template = jinja2_env.get_template('browser-open.html') + fh.write(template.render(open_url=url)) + + def remove_browser_open_file(self): + """Remove the nbserver--open.html file created for this server. + + Ignores the error raised when the file has already been removed. + """ + try: + os.unlink(self.browser_open_file) + except OSError as e: + if e.errno != errno.ENOENT: + raise + + def launch_browser(self): + try: + browser = webbrowser.get(self.browser or None) + except webbrowser.Error as e: + self.log.warning(_('No web browser found: %s.') % e) + browser = None + + if not browser: + return + + if self.file_to_run: + if not os.path.exists(self.file_to_run): + self.log.critical(_("%s does not exist") % self.file_to_run) + self.exit(1) + + relpath = os.path.relpath(self.file_to_run, self.notebook_dir) + uri = url_escape(url_path_join('notebooks', *relpath.split(os.sep))) + + # Write a temporary file to open in the browser + fd, open_file = tempfile.mkstemp(suffix='.html') + with open(fd, 'w', encoding='utf-8') as fh: + self._write_browser_open_file(uri, fh) + else: + open_file = self.browser_open_file + + b = lambda: browser.open( + urljoin('file:', pathname2url(open_file)), + new=self.webbrowser_open_new) + threading.Thread(target=b).start() + def start(self): """ Start the Notebook server app, after initialization @@ -1726,30 +1795,10 @@ def start(self): "resources section at https://jupyter.org/community.html.")) self.write_server_info_file() + self.write_browser_open_file() if self.open_browser or self.file_to_run: - try: - browser = webbrowser.get(self.browser or None) - except webbrowser.Error as e: - self.log.warning(_('No web browser found: %s.') % e) - browser = None - - if self.file_to_run: - if not os.path.exists(self.file_to_run): - self.log.critical(_("%s does not exist") % self.file_to_run) - self.exit(1) - - relpath = os.path.relpath(self.file_to_run, self.notebook_dir) - uri = url_escape(url_path_join('notebooks', *relpath.split(os.sep))) - else: - # default_url contains base_url, but so does connection_url - uri = self.default_url[len(self.base_url):] - if self.one_time_token: - uri = url_concat(uri, {'token': self.one_time_token}) - if browser: - b = lambda : browser.open(url_path_join(self.connection_url, uri), - new=self.webbrowser_open_new) - threading.Thread(target=b).start() + self.launch_browser() if self.token and self._token_generated: # log full URL with generated token, so there's a copy/pasteable link @@ -1773,6 +1822,7 @@ def start(self): info(_("Interrupted...")) finally: self.remove_server_info_file() + self.remove_browser_open_file() self.cleanup_kernels() def stop(self): diff --git a/notebook/templates/browser-open.html b/notebook/templates/browser-open.html new file mode 100644 index 000000000..6f277967f --- /dev/null +++ b/notebook/templates/browser-open.html @@ -0,0 +1,18 @@ +{# This template is not served, but written as a file to open in the browser, + passing the token without putting it in a command-line argument. #} + + + + + + Opening Jupyter Notebook + + + +

+ This page should redirect you to Jupyter Notebook. If it doesn't, + click here to go to Jupyter. +

+ + + diff --git a/notebook/utils.py b/notebook/utils.py index de1b3ea6f..5b3d9dff9 100644 --- a/notebook/utils.py +++ b/notebook/utils.py @@ -13,10 +13,11 @@ from distutils.version import LooseVersion try: - from urllib.parse import quote, unquote, urlparse + from urllib.parse import quote, unquote, urlparse, urljoin + from urllib.request import pathname2url except ImportError: - from urllib import quote, unquote - from urlparse import urlparse + from urllib import quote, unquote, pathname2url + from urlparse import urlparse, urljoin from ipython_genutils import py3compat