2626import signal
2727import socket
2828import sys
29+ import tempfile
2930import threading
3031import time
3132import warnings
107108from notebook ._sysinfo import get_sys_info
108109
109110from ._tz import utcnow , utcfromtimestamp
110- from .utils import url_path_join , check_pid , url_escape
111+ from .utils import url_path_join , check_pid , url_escape , urljoin , pathname2url
111112
112113#-----------------------------------------------------------------------------
113114# Module globals
@@ -754,12 +755,6 @@ def _write_cookie_secret_file(self, secret):
754755 """ )
755756 ).tag (config = True )
756757
757- one_time_token = Unicode (
758- help = _ ("""One-time token used for opening a browser.
759- Once used, this token cannot be used again.
760- """ )
761- )
762-
763758 _token_generated = True
764759
765760 @default ('token' )
@@ -1184,6 +1179,13 @@ def _update_mathjax_config(self, change):
11841179 def _default_info_file (self ):
11851180 info_file = "nbserver-%s.json" % os .getpid ()
11861181 return os .path .join (self .runtime_dir , info_file )
1182+
1183+ browser_open_file = Unicode ()
1184+
1185+ @default ('browser_open_file' )
1186+ def _default_browser_open_file (self ):
1187+ basename = "nbserver-%s-open.html" % os .getpid ()
1188+ return os .path .join (self .runtime_dir , basename )
11871189
11881190 pylab = Unicode ('disabled' , config = True ,
11891191 help = _ ("""
@@ -1363,9 +1365,6 @@ def init_webapp(self):
13631365 self .tornado_settings ['cookie_options' ] = self .cookie_options
13641366 self .tornado_settings ['get_secure_cookie_kwargs' ] = self .get_secure_cookie_kwargs
13651367 self .tornado_settings ['token' ] = self .token
1366- if (self .open_browser or self .file_to_run ) and not self .password :
1367- self .one_time_token = binascii .hexlify (os .urandom (24 )).decode ('ascii' )
1368- self .tornado_settings ['one_time_token' ] = self .one_time_token
13691368
13701369 # ensure default_url starts with base_url
13711370 if not self .default_url .startswith (self .base_url ):
@@ -1697,6 +1696,67 @@ def remove_server_info_file(self):
16971696 if e .errno != errno .ENOENT :
16981697 raise
16991698
1699+ def write_browser_open_file (self ):
1700+ """Write an nbserver-<pid>-open.html file
1701+
1702+ This can be used to open the notebook in a browser
1703+ """
1704+ # default_url contains base_url, but so does connection_url
1705+ open_url = self .default_url [len (self .base_url ):]
1706+
1707+ with open (self .browser_open_file , 'w' , encoding = 'utf-8' ) as f :
1708+ self ._write_browser_open_file (open_url , f )
1709+
1710+ def _write_browser_open_file (self , url , fh ):
1711+ if self .token :
1712+ url = url_concat (url , {'token' : self .token })
1713+ url = url_path_join (self .connection_url , url )
1714+
1715+ jinja2_env = self .web_app .settings ['jinja2_env' ]
1716+ template = jinja2_env .get_template ('browser-open.html' )
1717+ fh .write (template .render (open_url = url ))
1718+
1719+ def remove_browser_open_file (self ):
1720+ """Remove the nbserver-<pid>-open.html file created for this server.
1721+
1722+ Ignores the error raised when the file has already been removed.
1723+ """
1724+ try :
1725+ os .unlink (self .browser_open_file )
1726+ except OSError as e :
1727+ if e .errno != errno .ENOENT :
1728+ raise
1729+
1730+ def launch_browser (self ):
1731+ try :
1732+ browser = webbrowser .get (self .browser or None )
1733+ except webbrowser .Error as e :
1734+ self .log .warning (_ ('No web browser found: %s.' ) % e )
1735+ browser = None
1736+
1737+ if not browser :
1738+ return
1739+
1740+ if self .file_to_run :
1741+ if not os .path .exists (self .file_to_run ):
1742+ self .log .critical (_ ("%s does not exist" ) % self .file_to_run )
1743+ self .exit (1 )
1744+
1745+ relpath = os .path .relpath (self .file_to_run , self .notebook_dir )
1746+ uri = url_escape (url_path_join ('notebooks' , * relpath .split (os .sep )))
1747+
1748+ # Write a temporary file to open in the browser
1749+ fd , open_file = tempfile .mkstemp (suffix = '.html' )
1750+ with open (fd , 'w' , encoding = 'utf-8' ) as fh :
1751+ self ._write_browser_open_file (uri , fh )
1752+ else :
1753+ open_file = self .browser_open_file
1754+
1755+ b = lambda : browser .open (
1756+ urljoin ('file:' , pathname2url (open_file )),
1757+ new = self .webbrowser_open_new )
1758+ threading .Thread (target = b ).start ()
1759+
17001760 def start (self ):
17011761 """ Start the Notebook server app, after initialization
17021762
@@ -1726,38 +1786,19 @@ def start(self):
17261786 "resources section at https://jupyter.org/community.html." ))
17271787
17281788 self .write_server_info_file ()
1789+ self .write_browser_open_file ()
17291790
17301791 if self .open_browser or self .file_to_run :
1731- try :
1732- browser = webbrowser .get (self .browser or None )
1733- except webbrowser .Error as e :
1734- self .log .warning (_ ('No web browser found: %s.' ) % e )
1735- browser = None
1736-
1737- if self .file_to_run :
1738- if not os .path .exists (self .file_to_run ):
1739- self .log .critical (_ ("%s does not exist" ) % self .file_to_run )
1740- self .exit (1 )
1741-
1742- relpath = os .path .relpath (self .file_to_run , self .notebook_dir )
1743- uri = url_escape (url_path_join ('notebooks' , * relpath .split (os .sep )))
1744- else :
1745- # default_url contains base_url, but so does connection_url
1746- uri = self .default_url [len (self .base_url ):]
1747- if self .one_time_token :
1748- uri = url_concat (uri , {'token' : self .one_time_token })
1749- if browser :
1750- b = lambda : browser .open (url_path_join (self .connection_url , uri ),
1751- new = self .webbrowser_open_new )
1752- threading .Thread (target = b ).start ()
1792+ self .launch_browser ()
17531793
17541794 if self .token and self ._token_generated :
17551795 # log full URL with generated token, so there's a copy/pasteable link
17561796 # with auth info.
17571797 self .log .critical ('\n ' .join ([
17581798 '\n ' ,
1759- 'Copy/paste this URL into your browser when you connect for the first time,' ,
1760- 'to login with a token:' ,
1799+ 'To access the notebook, open this file in a browser:' ,
1800+ ' %s' % urljoin ('file:' , pathname2url (self .browser_open_file )),
1801+ 'Or copy and paste one of these URLs:' ,
17611802 ' %s' % self .display_url ,
17621803 ]))
17631804
@@ -1773,6 +1814,7 @@ def start(self):
17731814 info (_ ("Interrupted..." ))
17741815 finally :
17751816 self .remove_server_info_file ()
1817+ self .remove_browser_open_file ()
17761818 self .cleanup_kernels ()
17771819
17781820 def stop (self ):
0 commit comments