diff --git a/jupyterhub/orm.py b/jupyterhub/orm.py index b26bc6c1..e4fcdfa9 100644 --- a/jupyterhub/orm.py +++ b/jupyterhub/orm.py @@ -26,7 +26,7 @@ from sqlalchemy import create_engine from IPython.utils.py3compat import str_to_unicode -from .utils import random_port, url_path_join, wait_for_server +from .utils import random_port, url_path_join, wait_for_server, wait_for_http_server def new_token(*args, **kwargs): @@ -96,9 +96,12 @@ class Server(Base): ) @gen.coroutine - def wait_up(self, timeout=10): + def wait_up(self, timeout=10, http=False): """Wait for this server to come up""" - yield wait_for_server(self.ip or 'localhost', self.port, timeout=timeout) + if http: + yield wait_for_http_server(self.url.replace('//*', '//localhost'), timeout=timeout) + else: + yield wait_for_server(self.ip or 'localhost', self.port, timeout=timeout) def is_up(self): """Is the server accepting connections?""" @@ -323,8 +326,8 @@ class User(Base): self.state = spawner.get_state() self.last_activity = datetime.utcnow() db.commit() - - yield self.server.wait_up() + + yield self.server.wait_up(http=True) raise gen.Return(self) @gen.coroutine diff --git a/jupyterhub/utils.py b/jupyterhub/utils.py index cd21c2a5..a33308e8 100644 --- a/jupyterhub/utils.py +++ b/jupyterhub/utils.py @@ -8,6 +8,7 @@ import errno import os import socket from tornado import web, gen, ioloop +from tornado.httpclient import HTTPRequest, AsyncHTTPClient, HTTPError from tornado.log import app_log from IPython.html.utils import url_path_join @@ -41,7 +42,7 @@ def random_hex(nbytes): @gen.coroutine def wait_for_server(ip, port, timeout=10): - """wait for a server to show up at ip:port""" + """wait for any server to show up at ip:port""" loop = ioloop.IOLoop.current() tic = loop.time() while loop.time() - tic < timeout: @@ -57,6 +58,34 @@ def wait_for_server(ip, port, timeout=10): return raise TimeoutError +@gen.coroutine +def wait_for_http_server(url, timeout=10): + """Wait for an HTTP Server to respond at url + + Any non-5XX response code will do, even 404. + """ + loop = ioloop.IOLoop.current() + tic = loop.time() + client = AsyncHTTPClient() + while loop.time() - tic < timeout: + try: + r = yield client.fetch(url, follow_redirects=False) + except HTTPError as e: + if e.code >= 500: + # failed to respond properly, wait and try again + if e.code != 599: + # we expect 599 for no connection, + # but 502 or other proxy error is conceivable + app_log.warn("Server at %s responded with error: %s", url, e.code) + yield gen.Task(loop.add_timeout, loop.time() + 0.25) + else: + app_log.debug("Server at %s responded with %s", url, e.code) + return + else: + return + + raise TimeoutError + def auth_decorator(check_auth): """Make an authentication decorator