Merge pull request #3636 from yuvipanda/404

Fail suspected API requests with 424, not 503
This commit is contained in:
Min RK
2021-10-05 15:16:18 +02:00
committed by GitHub
3 changed files with 48 additions and 13 deletions

View File

@@ -1518,6 +1518,25 @@ class JupyterHub(Application):
""",
).tag(config=True)
use_legacy_stopped_server_status_code = Bool(
False,
help="""
Return 503 rather than 424 when request comes in for a non-running server.
Prior to JupyterHub 2.0, we returned a 503 when any request came in for
a user server that was currently not running. By default, JupyterHub 2.0
will return a 424 - this makes operational metric dashboards more useful.
JupyterLab < 3.2 expected the 503 to know if the user server is no longer
running, and prompted the user to start their server. Set this config to
true to retain the old behavior, so JupyterLab < 3.2 can continue to show
the appropriate UI when the user server is stopped.
This option will be removed in a future release.
""",
config=True,
)
def init_handlers(self):
h = []
# load handlers from the authenticator

View File

@@ -1357,7 +1357,7 @@ class UserUrlHandler(BaseHandler):
**Changed Behavior as of 1.0** This handler no longer triggers a spawn. Instead, it checks if:
1. server is not active, serve page prompting for spawn (status: 503)
1. server is not active, serve page prompting for spawn (status: 424)
2. server is ready (This shouldn't happen! Proxy isn't updated yet. Wait a bit and redirect.)
3. server is active, redirect to /hub/spawn-pending to monitor launch progress
(will redirect back when finished)
@@ -1376,7 +1376,14 @@ class UserUrlHandler(BaseHandler):
self.log.warning(
"Failing suspected API request to not-running server: %s", self.request.path
)
self.set_status(503)
# If we got here, the server is not running. To differentiate
# that the *server* itself is not running, rather than just the particular
# resource *in* the server is not found, we return a 424 instead of a 404.
# We allow retaining the old behavior to support older JupyterLab versions
self.set_status(
424 if not self.app.use_legacy_stopped_server_status_code else 503
)
self.set_header("Content-Type", "application/json")
spawn_url = urlparse(self.request.full_url())._replace(query="")
@@ -1541,15 +1548,17 @@ class UserUrlHandler(BaseHandler):
self.redirect(pending_url, status=303)
return
# if we got here, the server is not running
# serve a page prompting for spawn and 503 error
# visiting /user/:name no longer triggers implicit spawn
# without explicit user action
# If we got here, the server is not running. To differentiate
# that the *server* itself is not running, rather than just the particular
# page *in* the server is not found, we return a 424 instead of a 404.
# We allow retaining the old behavior to support older JupyterLab versions
spawn_url = url_concat(
url_path_join(self.hub.base_url, "spawn", user.escaped_name, server_name),
{"next": self.request.uri},
)
self.set_status(503)
self.set_status(
424 if not self.app.use_legacy_stopped_server_status_code else 503
)
auth_state = await user.get_auth_state()
html = await self.render_template(

View File

@@ -56,8 +56,8 @@ async def test_root_redirect(app):
r = await get_page(url, app, cookies=cookies)
path = urlparse(r.url).path
assert path == ujoin(app.base_url, 'hub/user/%s/test.ipynb' % name)
# serve "server not running" page, which has status 503
assert r.status_code == 503
# serve "server not running" page, which has status 424
assert r.status_code == 424
async def test_root_default_url_noauth(app):
@@ -172,7 +172,7 @@ async def test_spawn_redirect(app):
r = await get_page('user/' + name, app, hub=False, cookies=cookies)
path = urlparse(r.url).path
assert path == ujoin(app.base_url, 'hub/user/%s/' % name)
assert r.status_code == 503
assert r.status_code == 424
async def test_spawn_handler_access(app):
@@ -507,13 +507,13 @@ async def test_user_redirect_deprecated(app, username):
print(urlparse(r.url))
path = urlparse(r.url).path
assert path == ujoin(app.base_url, 'hub/user/%s/' % name)
assert r.status_code == 503
assert r.status_code == 424
r = await get_page('/user/baduser/test.ipynb', app, cookies=cookies, hub=False)
print(urlparse(r.url))
path = urlparse(r.url).path
assert path == ujoin(app.base_url, 'hub/user/%s/test.ipynb' % name)
assert r.status_code == 503
assert r.status_code == 424
r = await get_page('/user/baduser/test.ipynb', app, hub=False)
r.raise_for_status()
@@ -1061,13 +1061,20 @@ async def test_token_page(app):
async def test_server_not_running_api_request(app):
cookies = await app.login_user("bees")
r = await get_page("user/bees/api/status", app, hub=False, cookies=cookies)
assert r.status_code == 503
assert r.status_code == 424
assert r.headers["content-type"] == "application/json"
message = r.json()['message']
assert ujoin(app.base_url, "hub/spawn/bees") in message
assert " /user/bees" in message
async def test_server_not_running_api_request_legacy_status(app):
app.use_legacy_stopped_server_status_code = True
cookies = await app.login_user("bees")
r = await get_page("user/bees/api/status", app, hub=False, cookies=cookies)
assert r.status_code == 503
async def test_metrics_no_auth(app):
r = await get_page("metrics", app)
assert r.status_code == 403