6363from tornado import web
6464from tornado .httputil import url_concat
6565from tornado .log import LogFormatter , app_log , access_log , gen_log
66+ from tornado .netutil import bind_unix_socket
6667
6768from notebook import (
69+ DEFAULT_NOTEBOOK_PORT ,
6870 DEFAULT_STATIC_FILES_PATH ,
6971 DEFAULT_TEMPLATE_PATH_LIST ,
7072 __version__ ,
@@ -448,8 +450,8 @@ class NbserverStopApp(JupyterApp):
448450 version = __version__
449451 description = "Stop currently running notebook server for a given port"
450452
451- port = Integer (8888 , config = True ,
452- help = "Port of the server to be killed. Default 8888" )
453+ port = Integer (DEFAULT_NOTEBOOK_PORT , config = True ,
454+ help = "Port of the server to be killed. Default %s" % DEFAULT_NOTEBOOK_PORT )
453455
454456 def parse_command_line (self , argv = None ):
455457 super (NbserverStopApp , self ).parse_command_line (argv )
@@ -692,10 +694,18 @@ def _valdate_ip(self, proposal):
692694 or containerized setups for example).""" )
693695 )
694696
695- port = Integer (8888 , config = True ,
697+ port = Integer (DEFAULT_NOTEBOOK_PORT , config = True ,
696698 help = _ ("The port the notebook server will listen on." )
697699 )
698700
701+ sock = Unicode (u'' , config = True ,
702+ help = _ ("The UNIX socket the notebook server will listen on." )
703+ )
704+
705+ sock_umask = Unicode (u'' , config = True ,
706+ help = _ ("The UNIX socket umask to set on creation (default: 0600)." )
707+ )
708+
699709 port_retries = Integer (50 , config = True ,
700710 help = _ ("The number of additional ports to try if the specified port is not available." )
701711 )
@@ -1400,6 +1410,20 @@ def init_webapp(self):
14001410 self .log .critical (_ ("\t $ python -m notebook.auth password" ))
14011411 sys .exit (1 )
14021412
1413+ # Socket options validation.
1414+ if self .sock :
1415+ if self .port != DEFAULT_NOTEBOOK_PORT :
1416+ self .log .critical (
1417+ _ ('Options --port and --sock are mutually exclusive. Aborting.' ),
1418+ )
1419+ sys .exit (1 )
1420+
1421+ if sys .platform .startswith ('win' ):
1422+ self .log .critical (
1423+ _ ('Option --sock is not supported on Windows. Aborting.' ),
1424+ )
1425+ sys .exit (1 )
1426+
14031427 self .web_app = NotebookWebApplication (
14041428 self , self .kernel_manager , self .contents_manager ,
14051429 self .session_manager , self .kernel_spec_manager ,
@@ -1436,6 +1460,32 @@ def init_webapp(self):
14361460 max_body_size = self .max_body_size ,
14371461 max_buffer_size = self .max_buffer_size )
14381462
1463+ success = self ._bind_http_server ()
1464+ if not success :
1465+ self .log .critical (_ ('ERROR: the notebook server could not be started because '
1466+ 'no available port could be found.' ))
1467+ self .exit (1 )
1468+
1469+ def _bind_http_server (self ):
1470+ return self ._bind_http_server_unix () if self .sock else self ._bind_http_server_tcp ()
1471+
1472+ def _bind_http_server_unix (self ):
1473+ try :
1474+ sock = bind_unix_socket (self .sock , mode = int (self .sock_umask , 8 ))
1475+ self .http_server .add_socket (sock )
1476+ except socket .error as e :
1477+ if e .errno == errno .EADDRINUSE :
1478+ self .log .info (_ ('The socket %s is already in use.' ) % self .sock )
1479+ return False
1480+ elif e .errno in (errno .EACCES , getattr (errno , 'WSAEACCES' , errno .EACCES )):
1481+ self .log .warning (_ ("Permission to listen on sock %s denied" ) % self .sock )
1482+ return False
1483+ else :
1484+ raise
1485+ else :
1486+ return True
1487+
1488+ def _bind_http_server_tcp (self ):
14391489 success = None
14401490 for port in random_ports (self .port , self .port_retries + 1 ):
14411491 try :
@@ -1453,37 +1503,42 @@ def init_webapp(self):
14531503 self .port = port
14541504 success = True
14551505 break
1456- if not success :
1457- self .log .critical (_ ('ERROR: the notebook server could not be started because '
1458- 'no available port could be found.' ))
1459- self .exit (1 )
1506+ return success
14601507
14611508 @property
14621509 def display_url (self ):
14631510 if self .custom_display_url :
14641511 url = self .custom_display_url
14651512 if not url .endswith ('/' ):
14661513 url += '/'
1514+ elif self .sock :
1515+ url = self ._unix_sock_url ()
14671516 else :
14681517 if self .ip in ('' , '0.0.0.0' ):
14691518 ip = "%s" % socket .gethostname ()
14701519 else :
14711520 ip = self .ip
1472- url = self ._url (ip )
1521+ url = self ._tcp_url (ip )
14731522 if self .token :
14741523 # Don't log full token if it came from config
14751524 token = self .token if self ._token_generated else '...'
14761525 url = (url_concat (url , {'token' : token })
14771526 + '\n or '
1478- + url_concat (self ._url ('127.0.0.1' ), {'token' : token }))
1527+ + url_concat (self ._tcp_url ('127.0.0.1' ), {'token' : token }))
14791528 return url
14801529
14811530 @property
14821531 def connection_url (self ):
1483- ip = self .ip if self .ip else 'localhost'
1484- return self ._url (ip )
1532+ if self .sock :
1533+ return self ._unix_sock_url ()
1534+ else :
1535+ ip = self .ip if self .ip else 'localhost'
1536+ return self ._tcp_url (ip )
1537+
1538+ def _unix_sock_url (self ):
1539+ return 'http+unix://%s/%s' % (self .sock , self .base_url )
14851540
1486- def _url (self , ip ):
1541+ def _tcp_url (self , ip ):
14871542 proto = 'https' if self .certfile else 'http'
14881543 return "%s://%s:%i%s" % (proto , ip , self .port , self .base_url )
14891544
@@ -1834,6 +1889,12 @@ def start(self):
18341889 self .write_browser_open_file ()
18351890
18361891 if self .open_browser or self .file_to_run :
1892+ if self .sock :
1893+ # If we're bound to a UNIX socket, we can't reliably connect from a browser.
1894+ self .log .critical (
1895+ _ ('Options --open_browser|--file_to_run and --sock are mutually exclusive. Aborting.' ),
1896+ )
1897+ sys .exit (1 )
18371898 self .launch_browser ()
18381899
18391900 if self .token and self ._token_generated :
0 commit comments