diff --git a/jupyterhub/apihandlers/__init__.py b/jupyterhub/apihandlers/__init__.py index 9dd87767..367595c0 100644 --- a/jupyterhub/apihandlers/__init__.py +++ b/jupyterhub/apihandlers/__init__.py @@ -1,9 +1,10 @@ from .base import * from .auth import * +from .proxy import * from .users import * -from . import auth, users +from . import auth, proxy, users default_handlers = [] -for mod in (auth, users): +for mod in (auth, proxy, users): default_handlers.extend(mod.default_handlers) diff --git a/jupyterhub/apihandlers/proxy.py b/jupyterhub/apihandlers/proxy.py new file mode 100644 index 00000000..58d13793 --- /dev/null +++ b/jupyterhub/apihandlers/proxy.py @@ -0,0 +1,68 @@ +"""Proxy handlers""" + +# Copyright (c) Jupyter Development Team. +# Distributed under the terms of the Modified BSD License. + +import json + +from tornado import gen, web + +from .. import orm +from ..utils import admin_only +from .base import APIHandler + +class ProxyAPIHandler(APIHandler): + + @admin_only + @gen.coroutine + def get(self): + """GET /api/proxy fetches the routing table + + This is the same as fetching the routing table directly from the proxy, + but without clients needing to maintain separate + """ + routes = yield self.proxy.get_routes() + self.write(json.dumps(routes)) + + @admin_only + @gen.coroutine + def post(self): + """POST checks the proxy to ensure""" + yield self.proxy.check_routes() + + + @admin_only + @gen.coroutine + def patch(self): + """PATCH updates the location of the proxy + + Can be used to notify the Hub that a new proxy is in charge + """ + if not self.request.body: + raise web.HTTPError(400, "need JSON body") + + try: + model = json.loads(self.request.body.decode('utf8', 'replace')) + except ValueError: + raise web.HTTPError(400, "Request body must be JSON dict") + if not isinstance(model, dict): + raise web.HTTPError(400, "Request body must be JSON dict") + + server = self.proxy.api_server + if 'ip' in model: + server.ip = model['ip'] + if 'port' in model: + server.port = model['port'] + if 'protocol' in model: + server.proto = model['protocol'] + if 'auth_token' in model: + self.proxy.auth_token = model['auth_token'] + self.db.commit() + self.log.info("Updated proxy at %s", server.url) + yield self.proxy.check_routes() + + + +default_handlers = [ + (r"/api/proxy", ProxyAPIHandler), +] diff --git a/jupyterhub/apihandlers/users.py b/jupyterhub/apihandlers/users.py index 9fea47bd..b55e3f4d 100644 --- a/jupyterhub/apihandlers/users.py +++ b/jupyterhub/apihandlers/users.py @@ -1,4 +1,4 @@ -"""Authorization handlers""" +"""User handlers""" # Copyright (c) Jupyter Development Team. # Distributed under the terms of the Modified BSD License. diff --git a/jupyterhub/tests/test_api.py b/jupyterhub/tests/test_api.py index 7cde62c1..b45b6a63 100644 --- a/jupyterhub/tests/test_api.py +++ b/jupyterhub/tests/test_api.py @@ -275,3 +275,10 @@ def test_never_spawn(app, io_loop): assert not user.spawn_pending status = io_loop.run_sync(user.spawner.poll) assert status is not None + + +def test_get_proxy(app, io_loop): + r = api_request(app, 'proxy') + r.raise_for_status() + reply = r.json() + assert list(reply.keys()) == ['/']