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' )
@@ -1178,6 +1173,13 @@ def _update_mathjax_config(self, change):
11781173 def _default_info_file (self ):
11791174 info_file = "nbserver-%s.json" % os .getpid ()
11801175 return os .path .join (self .runtime_dir , info_file )
1176+
1177+ browser_open_file = Unicode ()
1178+
1179+ @default ('browser_open_file' )
1180+ def _default_browser_open_file (self ):
1181+ basename = "nbserver-%s-open.html" % os .getpid ()
1182+ return os .path .join (self .runtime_dir , basename )
11811183
11821184 pylab = Unicode ('disabled' , config = True ,
11831185 help = _ ("""
@@ -1357,9 +1359,6 @@ def init_webapp(self):
13571359 self .tornado_settings ['cookie_options' ] = self .cookie_options
13581360 self .tornado_settings ['get_secure_cookie_kwargs' ] = self .get_secure_cookie_kwargs
13591361 self .tornado_settings ['token' ] = self .token
1360- if (self .open_browser or self .file_to_run ) and not self .password :
1361- self .one_time_token = binascii .hexlify (os .urandom (24 )).decode ('ascii' )
1362- self .tornado_settings ['one_time_token' ] = self .one_time_token
13631362
13641363 # ensure default_url starts with base_url
13651364 if not self .default_url .startswith (self .base_url ):
@@ -1689,6 +1688,67 @@ def remove_server_info_file(self):
16891688 if e .errno != errno .ENOENT :
16901689 raise
16911690
1691+ def write_browser_open_file (self ):
1692+ """Write an nbserver-<pid>-open.html file
1693+
1694+ This can be used to open the notebook in a browser
1695+ """
1696+ # default_url contains base_url, but so does connection_url
1697+ open_url = self .default_url [len (self .base_url ):]
1698+
1699+ with io .open (self .browser_open_file , 'w' , encoding = 'utf-8' ) as f :
1700+ self ._write_browser_open_file (open_url , f )
1701+
1702+ def _write_browser_open_file (self , url , fh ):
1703+ if self .token :
1704+ url = url_concat (url , {'token' : self .token })
1705+ url = url_path_join (self .connection_url , url )
1706+
1707+ jinja2_env = self .web_app .settings ['jinja2_env' ]
1708+ template = jinja2_env .get_template ('browser-open.html' )
1709+ fh .write (template .render (open_url = url ))
1710+
1711+ def remove_browser_open_file (self ):
1712+ """Remove the nbserver-<pid>-open.html file created for this server.
1713+
1714+ Ignores the error raised when the file has already been removed.
1715+ """
1716+ try :
1717+ os .unlink (self .browser_open_file )
1718+ except OSError as e :
1719+ if e .errno != errno .ENOENT :
1720+ raise
1721+
1722+ def launch_browser (self ):
1723+ try :
1724+ browser = webbrowser .get (self .browser or None )
1725+ except webbrowser .Error as e :
1726+ self .log .warning (_ ('No web browser found: %s.' ) % e )
1727+ browser = None
1728+
1729+ if not browser :
1730+ return
1731+
1732+ if self .file_to_run :
1733+ if not os .path .exists (self .file_to_run ):
1734+ self .log .critical (_ ("%s does not exist" ) % self .file_to_run )
1735+ self .exit (1 )
1736+
1737+ relpath = os .path .relpath (self .file_to_run , self .notebook_dir )
1738+ uri = url_escape (url_path_join ('notebooks' , * relpath .split (os .sep )))
1739+
1740+ # Write a temporary file to open in the browser
1741+ fd , open_file = tempfile .mkstemp (suffix = '.html' )
1742+ with open (fd , 'w' , encoding = 'utf-8' ) as fh :
1743+ self ._write_browser_open_file (uri , fh )
1744+ else :
1745+ open_file = self .browser_open_file
1746+
1747+ b = lambda : browser .open (
1748+ urljoin ('file:' , pathname2url (open_file )),
1749+ new = self .webbrowser_open_new )
1750+ threading .Thread (target = b ).start ()
1751+
16921752 def start (self ):
16931753 """ Start the Notebook server app, after initialization
16941754
@@ -1718,38 +1778,19 @@ def start(self):
17181778 "resources section at https://jupyter.org/community.html." ))
17191779
17201780 self .write_server_info_file ()
1781+ self .write_browser_open_file ()
17211782
17221783 if self .open_browser or self .file_to_run :
1723- try :
1724- browser = webbrowser .get (self .browser or None )
1725- except webbrowser .Error as e :
1726- self .log .warning (_ ('No web browser found: %s.' ) % e )
1727- browser = None
1728-
1729- if self .file_to_run :
1730- if not os .path .exists (self .file_to_run ):
1731- self .log .critical (_ ("%s does not exist" ) % self .file_to_run )
1732- self .exit (1 )
1733-
1734- relpath = os .path .relpath (self .file_to_run , self .notebook_dir )
1735- uri = url_escape (url_path_join ('notebooks' , * relpath .split (os .sep )))
1736- else :
1737- # default_url contains base_url, but so does connection_url
1738- uri = self .default_url [len (self .base_url ):]
1739- if self .one_time_token :
1740- uri = url_concat (uri , {'token' : self .one_time_token })
1741- if browser :
1742- b = lambda : browser .open (url_path_join (self .connection_url , uri ),
1743- new = self .webbrowser_open_new )
1744- threading .Thread (target = b ).start ()
1784+ self .launch_browser ()
17451785
17461786 if self .token and self ._token_generated :
17471787 # log full URL with generated token, so there's a copy/pasteable link
17481788 # with auth info.
17491789 self .log .critical ('\n ' .join ([
17501790 '\n ' ,
1751- 'Copy/paste this URL into your browser when you connect for the first time,' ,
1752- 'to login with a token:' ,
1791+ 'To access the notebook, open this file in a browser:' ,
1792+ ' %s' % urljoin ('file:' , pathname2url (self .browser_open_file )),
1793+ 'Or copy and paste one of these URLs:' ,
17531794 ' %s' % self .display_url ,
17541795 ]))
17551796
@@ -1765,6 +1806,7 @@ def start(self):
17651806 info (_ ("Interrupted..." ))
17661807 finally :
17671808 self .remove_server_info_file ()
1809+ self .remove_browser_open_file ()
17681810 self .cleanup_kernels ()
17691811
17701812 def stop (self ):
0 commit comments