mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-16 22:43:00 +00:00
Merge pull request #3636 from yuvipanda/404
Fail suspected API requests with 424, not 503
This commit is contained in:
@@ -1518,6 +1518,25 @@ class JupyterHub(Application):
|
|||||||
""",
|
""",
|
||||||
).tag(config=True)
|
).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):
|
def init_handlers(self):
|
||||||
h = []
|
h = []
|
||||||
# load handlers from the authenticator
|
# load handlers from the authenticator
|
||||||
|
@@ -1357,7 +1357,7 @@ class UserUrlHandler(BaseHandler):
|
|||||||
|
|
||||||
**Changed Behavior as of 1.0** This handler no longer triggers a spawn. Instead, it checks if:
|
**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.)
|
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
|
3. server is active, redirect to /hub/spawn-pending to monitor launch progress
|
||||||
(will redirect back when finished)
|
(will redirect back when finished)
|
||||||
@@ -1376,7 +1376,14 @@ class UserUrlHandler(BaseHandler):
|
|||||||
self.log.warning(
|
self.log.warning(
|
||||||
"Failing suspected API request to not-running server: %s", self.request.path
|
"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")
|
self.set_header("Content-Type", "application/json")
|
||||||
|
|
||||||
spawn_url = urlparse(self.request.full_url())._replace(query="")
|
spawn_url = urlparse(self.request.full_url())._replace(query="")
|
||||||
@@ -1541,15 +1548,17 @@ class UserUrlHandler(BaseHandler):
|
|||||||
self.redirect(pending_url, status=303)
|
self.redirect(pending_url, status=303)
|
||||||
return
|
return
|
||||||
|
|
||||||
# if we got here, the server is not running
|
# If we got here, the server is not running. To differentiate
|
||||||
# serve a page prompting for spawn and 503 error
|
# that the *server* itself is not running, rather than just the particular
|
||||||
# visiting /user/:name no longer triggers implicit spawn
|
# page *in* the server is not found, we return a 424 instead of a 404.
|
||||||
# without explicit user action
|
# We allow retaining the old behavior to support older JupyterLab versions
|
||||||
spawn_url = url_concat(
|
spawn_url = url_concat(
|
||||||
url_path_join(self.hub.base_url, "spawn", user.escaped_name, server_name),
|
url_path_join(self.hub.base_url, "spawn", user.escaped_name, server_name),
|
||||||
{"next": self.request.uri},
|
{"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()
|
auth_state = await user.get_auth_state()
|
||||||
html = await self.render_template(
|
html = await self.render_template(
|
||||||
|
@@ -56,8 +56,8 @@ async def test_root_redirect(app):
|
|||||||
r = await get_page(url, app, cookies=cookies)
|
r = await get_page(url, app, cookies=cookies)
|
||||||
path = urlparse(r.url).path
|
path = urlparse(r.url).path
|
||||||
assert path == ujoin(app.base_url, 'hub/user/%s/test.ipynb' % name)
|
assert path == ujoin(app.base_url, 'hub/user/%s/test.ipynb' % name)
|
||||||
# serve "server not running" page, which has status 503
|
# serve "server not running" page, which has status 424
|
||||||
assert r.status_code == 503
|
assert r.status_code == 424
|
||||||
|
|
||||||
|
|
||||||
async def test_root_default_url_noauth(app):
|
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)
|
r = await get_page('user/' + name, app, hub=False, cookies=cookies)
|
||||||
path = urlparse(r.url).path
|
path = urlparse(r.url).path
|
||||||
assert path == ujoin(app.base_url, 'hub/user/%s/' % name)
|
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):
|
async def test_spawn_handler_access(app):
|
||||||
@@ -507,13 +507,13 @@ async def test_user_redirect_deprecated(app, username):
|
|||||||
print(urlparse(r.url))
|
print(urlparse(r.url))
|
||||||
path = urlparse(r.url).path
|
path = urlparse(r.url).path
|
||||||
assert path == ujoin(app.base_url, 'hub/user/%s/' % name)
|
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)
|
r = await get_page('/user/baduser/test.ipynb', app, cookies=cookies, hub=False)
|
||||||
print(urlparse(r.url))
|
print(urlparse(r.url))
|
||||||
path = urlparse(r.url).path
|
path = urlparse(r.url).path
|
||||||
assert path == ujoin(app.base_url, 'hub/user/%s/test.ipynb' % name)
|
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 = await get_page('/user/baduser/test.ipynb', app, hub=False)
|
||||||
r.raise_for_status()
|
r.raise_for_status()
|
||||||
@@ -1061,13 +1061,20 @@ async def test_token_page(app):
|
|||||||
async def test_server_not_running_api_request(app):
|
async def test_server_not_running_api_request(app):
|
||||||
cookies = await app.login_user("bees")
|
cookies = await app.login_user("bees")
|
||||||
r = await get_page("user/bees/api/status", app, hub=False, cookies=cookies)
|
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"
|
assert r.headers["content-type"] == "application/json"
|
||||||
message = r.json()['message']
|
message = r.json()['message']
|
||||||
assert ujoin(app.base_url, "hub/spawn/bees") in message
|
assert ujoin(app.base_url, "hub/spawn/bees") in message
|
||||||
assert " /user/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):
|
async def test_metrics_no_auth(app):
|
||||||
r = await get_page("metrics", app)
|
r = await get_page("metrics", app)
|
||||||
assert r.status_code == 403
|
assert r.status_code == 403
|
||||||
|
Reference in New Issue
Block a user