Merge pull request #353 from minrk/try-localhost

Ensure that we can bind and connect to localhost
This commit is contained in:
Min RK
2016-01-20 15:37:42 +01:00
5 changed files with 47 additions and 11 deletions

View File

@@ -50,7 +50,7 @@ from ._data import DATA_FILES_PATH
from .log import CoroutineLogFormatter, log_request from .log import CoroutineLogFormatter, log_request
from .traitlets import URLPrefix, Command from .traitlets import URLPrefix, Command
from .utils import ( from .utils import (
url_path_join, url_path_join, localhost,
ISO8601_ms, ISO8601_s, ISO8601_ms, ISO8601_s,
) )
# classes for config # classes for config
@@ -260,7 +260,7 @@ class JupyterHub(Application):
token = orm.new_token() token = orm.new_token()
return token return token
proxy_api_ip = Unicode('localhost', config=True, proxy_api_ip = Unicode(localhost(), config=True,
help="The ip for the proxy API handlers" help="The ip for the proxy API handlers"
) )
proxy_api_port = Integer(config=True, proxy_api_port = Integer(config=True,
@@ -272,7 +272,7 @@ class JupyterHub(Application):
hub_port = Integer(8081, config=True, hub_port = Integer(8081, config=True,
help="The port for this process" help="The port for this process"
) )
hub_ip = Unicode('localhost', config=True, hub_ip = Unicode(localhost(), config=True,
help="The ip for this process" help="The ip for this process"
) )

View File

@@ -26,7 +26,7 @@ from sqlalchemy import create_engine
from .utils import ( from .utils import (
random_port, url_path_join, wait_for_server, wait_for_http_server, random_port, url_path_join, wait_for_server, wait_for_http_server,
new_token, hash_token, compare_token, new_token, hash_token, compare_token, localhost,
) )
@@ -78,7 +78,7 @@ class Server(Base):
ip = self.ip ip = self.ip
if ip in {'', '0.0.0.0'}: if ip in {'', '0.0.0.0'}:
# when listening on all interfaces, connect to localhost # when listening on all interfaces, connect to localhost
ip = 'localhost' ip = localhost()
return "{proto}://{ip}:{port}".format( return "{proto}://{ip}:{port}".format(
proto=self.proto, proto=self.proto,
ip=ip, ip=ip,
@@ -109,12 +109,12 @@ class Server(Base):
if http: if http:
yield wait_for_http_server(self.url, timeout=timeout) yield wait_for_http_server(self.url, timeout=timeout)
else: else:
yield wait_for_server(self.ip or 'localhost', self.port, timeout=timeout) yield wait_for_server(self.ip or localhost(), self.port, timeout=timeout)
def is_up(self): def is_up(self):
"""Is the server accepting connections?""" """Is the server accepting connections?"""
try: try:
socket.create_connection((self.ip or 'localhost', self.port)) socket.create_connection((self.ip or localhost(), self.port))
except socket.error as e: except socket.error as e:
if e.errno == errno.ENETUNREACH: if e.errno == errno.ENETUNREACH:
try: try:

View File

@@ -22,7 +22,7 @@ from traitlets import (
) )
from .traitlets import Command from .traitlets import Command
from .utils import random_port from .utils import random_port, localhost
class Spawner(LoggingConfigurable): class Spawner(LoggingConfigurable):
"""Base class for spawning single-user notebook servers. """Base class for spawning single-user notebook servers.
@@ -41,7 +41,7 @@ class Spawner(LoggingConfigurable):
hub = Any() hub = Any()
authenticator = Any() authenticator = Any()
api_token = Unicode() api_token = Unicode()
ip = Unicode('localhost', config=True, ip = Unicode(localhost(), config=True,
help="The IP address (or hostname) the single-user server should listen on" help="The IP address (or hostname) the single-user server should listen on"
) )
start_timeout = Integer(60, config=True, start_timeout = Integer(60, config=True,

View File

@@ -17,6 +17,7 @@ from ..spawner import LocalProcessSpawner
from ..app import JupyterHub from ..app import JupyterHub
from ..auth import PAMAuthenticator from ..auth import PAMAuthenticator
from .. import orm from .. import orm
from ..utils import localhost
from pamela import PAMError from pamela import PAMError
@@ -108,7 +109,7 @@ class MockHub(JupyterHub):
db_file = None db_file = None
def _ip_default(self): def _ip_default(self):
return 'localhost' return localhost()
def _authenticator_class_default(self): def _authenticator_class_default(self):
return MockPAMAuthenticator return MockPAMAuthenticator

View File

@@ -6,10 +6,12 @@
from binascii import b2a_hex from binascii import b2a_hex
import errno import errno
import hashlib import hashlib
from hmac import compare_digest
import os import os
import socket import socket
from threading import Thread
import uuid import uuid
from hmac import compare_digest import warnings
from tornado import web, gen, ioloop from tornado import web, gen, ioloop
from tornado.httpclient import AsyncHTTPClient, HTTPError from tornado.httpclient import AsyncHTTPClient, HTTPError
@@ -192,3 +194,36 @@ def url_path_join(*pieces):
result = '/' result = '/'
return result return result
def localhost():
"""Return localhost or 127.0.0.1"""
if hasattr(localhost, '_localhost'):
return localhost._localhost
binder = connector = None
try:
binder = socket.socket()
binder.bind(('localhost', 0))
binder.listen(1)
port = binder.getsockname()[1]
def accept():
try:
conn, addr = binder.accept()
except ConnectionAbortedError:
pass
else:
conn.close()
t = Thread(target=accept)
t.start()
connector = socket.create_connection(('localhost', port), timeout=10)
t.join(timeout=10)
except (socket.error, socket.gaierror) as e:
warnings.warn("localhost doesn't appear to work, using 127.0.0.1\n%s" % e, RuntimeWarning)
localhost._localhost = '127.0.0.1'
else:
localhost._localhost = 'localhost'
finally:
if binder:
binder.close()
if connector:
connector.close()
return localhost._localhost