Files
jupyterhub/jupyterhub/objects.py
Min RK e6ce468301 set Spawner.server directly
avoids Spawner.server property looking up on the ORM every time,
which is expensive and we want to check `Spawner.server is None` often.
2017-08-03 09:59:01 +02:00

175 lines
5.1 KiB
Python

"""Some general objects for use in JupyterHub"""
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
import socket
from urllib.parse import urlparse
import warnings
from traitlets import (
HasTraits, Instance, Integer, Unicode,
default, observe,
)
from .traitlets import URLPrefix
from . import orm
from .utils import (
url_path_join, can_connect, wait_for_server,
wait_for_http_server, random_port,
)
class Server(HasTraits):
"""An object representing an HTTP endpoint.
*Some* of these reside in the database (user servers),
but others (Hub, proxy) are in-memory only.
"""
orm_server = Instance(orm.Server, allow_none=True)
ip = Unicode()
connect_ip = Unicode()
connect_port = Integer()
proto = Unicode('http')
port = Integer()
base_url = URLPrefix('/')
cookie_name = Unicode('')
@property
def _connect_ip(self):
"""The address to use when connecting to this server
When `ip` is set to a real ip address, the same value is used.
When `ip` refers to 'all interfaces' (e.g. '0.0.0.0'),
clients connect via hostname by default.
Setting `connect_ip` explicitly overrides any default behavior.
"""
if self.connect_ip:
return self.connect_ip
elif self.ip in {'', '0.0.0.0'}:
# if listening on all interfaces, default to hostname for connect
return socket.gethostname()
else:
return self.ip
@property
def _connect_port(self):
"""
The port to use when connecting to this server.
Defaults to self.port, but can be overridden by setting self.connect_port
"""
if self.connect_port:
return self.connect_port
return self.port
@classmethod
def from_orm(cls, orm_server):
"""Create a server from an orm.Server"""
return cls(orm_server=orm_server)
@classmethod
def from_url(cls, url):
"""Create a Server from a given URL"""
urlinfo = urlparse(url)
proto = urlinfo.scheme
ip = urlinfo.hostname or ''
port = urlinfo.port
if not port:
if proto == 'https':
port = 443
else:
port = 80
return cls(proto=proto, ip=ip, port=port, base_url=urlinfo.path)
@default('port')
def _default_port(self):
return random_port()
@observe('orm_server')
def _orm_server_changed(self, change):
"""When we get an orm_server, get attributes from there."""
obj = change.new
self.proto = obj.proto
self.ip = obj.ip
self.port = obj.port
self.base_url = obj.base_url
self.cookie_name = obj.cookie_name
# setter to pass through to the database
@observe('ip', 'proto', 'port', 'base_url', 'cookie_name')
def _change(self, change):
if self.orm_server and getattr(self.orm_server, change.name) != change.new:
# setattr on an sqlalchemy object sets the dirty flag,
# even if the value doesn't change.
# Avoid calling setattr when there's been no change,
# to avoid setting the dirty flag and triggering rollback.
setattr(self.orm_server, change.name, change.new)
@property
def host(self):
return "{proto}://{ip}:{port}".format(
proto=self.proto,
ip=self._connect_ip,
port=self._connect_port,
)
@property
def url(self):
return "{host}{uri}".format(
host=self.host,
uri=self.base_url,
)
@property
def bind_url(self):
"""representation of URL used for binding
Never used in APIs, only logging,
since it can be non-connectable value, such as '', meaning all interfaces.
"""
if self.ip in {'', '0.0.0.0'}:
return self.url.replace(self._connect_ip, self.ip or '*', 1)
return self.url
def wait_up(self, timeout=10, http=False):
"""Wait for this server to come up"""
if http:
return wait_for_http_server(self.url, timeout=timeout)
else:
return wait_for_server(self._connect_ip, self._connect_port, timeout=timeout)
def is_up(self):
"""Is the server accepting connections?"""
return can_connect(self._connect_ip, self._connect_port)
class Hub(Server):
"""Bring it all together at the hub.
The Hub is a server, plus its API path suffix
the api_url is the full URL plus the api_path suffix on the end
of the server base_url.
"""
cookie_name = 'jupyter-hub-token'
@property
def server(self):
warnings.warn("Hub.server is deprecated in JupyterHub 0.8. Access attributes on the Hub directly.",
DeprecationWarning,
stacklevel=2,
)
return self
public_host = Unicode()
@property
def api_url(self):
"""return the full API url (with proto://host...)"""
return url_path_join(self.url, 'api')
def __repr__(self):
return "<%s %s:%s>" % (
self.__class__.__name__, self.server.ip, self.server.port,
)