mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-18 07:23:00 +00:00
Merge pull request #5146 from agoose77/fix-spawn-429
set HTTP status when spawn via GET params fails
This commit is contained in:
@@ -24,6 +24,7 @@ from ..roles import assign_default_roles
|
|||||||
from ..scopes import needs_scope
|
from ..scopes import needs_scope
|
||||||
from ..user import User
|
from ..user import User
|
||||||
from ..utils import (
|
from ..utils import (
|
||||||
|
format_exception,
|
||||||
isoformat,
|
isoformat,
|
||||||
iterate_until,
|
iterate_until,
|
||||||
maybe_future,
|
maybe_future,
|
||||||
@@ -865,9 +866,8 @@ class SpawnProgressAPIHandler(APIHandler):
|
|||||||
failed_event['message'] = "Spawn cancelled"
|
failed_event['message'] = "Spawn cancelled"
|
||||||
elif f and f.done() and f.exception():
|
elif f and f.done() and f.exception():
|
||||||
exc = f.exception()
|
exc = f.exception()
|
||||||
message = getattr(exc, "jupyterhub_message", str(exc))
|
message, html_message = format_exception(exc)
|
||||||
failed_event['message'] = f"Spawn failed: {message}"
|
failed_event['message'] = f"Spawn failed: {message}"
|
||||||
html_message = getattr(exc, "jupyterhub_html_message", "")
|
|
||||||
if html_message:
|
if html_message:
|
||||||
failed_event['html_message'] = html_message
|
failed_event['html_message'] = html_message
|
||||||
else:
|
else:
|
||||||
@@ -906,9 +906,8 @@ class SpawnProgressAPIHandler(APIHandler):
|
|||||||
failed_event['message'] = "Spawn cancelled"
|
failed_event['message'] = "Spawn cancelled"
|
||||||
elif f and f.done() and f.exception():
|
elif f and f.done() and f.exception():
|
||||||
exc = f.exception()
|
exc = f.exception()
|
||||||
message = getattr(exc, "jupyterhub_message", str(exc))
|
message, html_message = format_exception(exc)
|
||||||
failed_event['message'] = f"Spawn failed: {message}"
|
failed_event['message'] = f"Spawn failed: {message}"
|
||||||
html_message = getattr(exc, "jupyterhub_html_message", "")
|
|
||||||
if html_message:
|
if html_message:
|
||||||
failed_event['html_message'] = html_message
|
failed_event['html_message'] = html_message
|
||||||
else:
|
else:
|
||||||
|
@@ -15,7 +15,13 @@ from tornado.httputil import url_concat
|
|||||||
from .. import __version__, orm
|
from .. import __version__, orm
|
||||||
from ..metrics import SERVER_POLL_DURATION_SECONDS, ServerPollStatus
|
from ..metrics import SERVER_POLL_DURATION_SECONDS, ServerPollStatus
|
||||||
from ..scopes import describe_raw_scopes, needs_scope
|
from ..scopes import describe_raw_scopes, needs_scope
|
||||||
from ..utils import maybe_future, url_escape_path, url_path_join, utcnow
|
from ..utils import (
|
||||||
|
format_exception,
|
||||||
|
maybe_future,
|
||||||
|
url_escape_path,
|
||||||
|
url_path_join,
|
||||||
|
utcnow,
|
||||||
|
)
|
||||||
from .base import BaseHandler
|
from .base import BaseHandler
|
||||||
|
|
||||||
|
|
||||||
@@ -92,7 +98,9 @@ class SpawnHandler(BaseHandler):
|
|||||||
|
|
||||||
default_url = None
|
default_url = None
|
||||||
|
|
||||||
async def _render_form(self, for_user, spawner_options_form, message=''):
|
async def _render_form(
|
||||||
|
self, for_user, spawner_options_form, message='', html_message=''
|
||||||
|
):
|
||||||
auth_state = await for_user.get_auth_state()
|
auth_state = await for_user.get_auth_state()
|
||||||
return await self.render_template(
|
return await self.render_template(
|
||||||
'spawn.html',
|
'spawn.html',
|
||||||
@@ -100,6 +108,7 @@ class SpawnHandler(BaseHandler):
|
|||||||
auth_state=auth_state,
|
auth_state=auth_state,
|
||||||
spawner_options_form=spawner_options_form,
|
spawner_options_form=spawner_options_form,
|
||||||
error_message=message,
|
error_message=message,
|
||||||
|
html_error_message=html_message,
|
||||||
url=url_concat(
|
url=url_concat(
|
||||||
self.request.uri, {"_xsrf": self.xsrf_token.decode('ascii')}
|
self.request.uri, {"_xsrf": self.xsrf_token.decode('ascii')}
|
||||||
),
|
),
|
||||||
@@ -177,7 +186,6 @@ class SpawnHandler(BaseHandler):
|
|||||||
await spawner.run_auth_state_hook(auth_state)
|
await spawner.run_auth_state_hook(auth_state)
|
||||||
|
|
||||||
# Try to start server directly when query arguments are passed.
|
# Try to start server directly when query arguments are passed.
|
||||||
error_message = ''
|
|
||||||
query_options = {}
|
query_options = {}
|
||||||
for key, byte_list in self.request.query_arguments.items():
|
for key, byte_list in self.request.query_arguments.items():
|
||||||
query_options[key] = [bs.decode('utf8') for bs in byte_list]
|
query_options[key] = [bs.decode('utf8') for bs in byte_list]
|
||||||
@@ -185,6 +193,8 @@ class SpawnHandler(BaseHandler):
|
|||||||
# 'next' is reserved argument for redirect after spawn
|
# 'next' is reserved argument for redirect after spawn
|
||||||
query_options.pop('next', None)
|
query_options.pop('next', None)
|
||||||
|
|
||||||
|
spawn_exc = None
|
||||||
|
|
||||||
if len(query_options) > 0:
|
if len(query_options) > 0:
|
||||||
try:
|
try:
|
||||||
self.log.debug(
|
self.log.debug(
|
||||||
@@ -200,16 +210,31 @@ class SpawnHandler(BaseHandler):
|
|||||||
"Failed to spawn single-user server with query arguments",
|
"Failed to spawn single-user server with query arguments",
|
||||||
exc_info=True,
|
exc_info=True,
|
||||||
)
|
)
|
||||||
error_message = str(e)
|
spawn_exc = e
|
||||||
# fallback to behavior without failing query arguments
|
# fallback to behavior without failing query arguments
|
||||||
|
|
||||||
spawner_options_form = await spawner.get_options_form()
|
spawner_options_form = await spawner.get_options_form()
|
||||||
if spawner_options_form:
|
if spawner_options_form:
|
||||||
self.log.debug("Serving options form for %s", spawner._log_name)
|
self.log.debug("Serving options form for %s", spawner._log_name)
|
||||||
|
|
||||||
|
# Explicitly catch HTTPError and report them to the client
|
||||||
|
# This may need scoping to particular error codes.
|
||||||
|
if isinstance(spawn_exc, web.HTTPError):
|
||||||
|
self.set_status(spawn_exc.status_code)
|
||||||
|
|
||||||
|
for name, value in spawn_exc.headers.items():
|
||||||
|
self.set_header(name, value)
|
||||||
|
|
||||||
|
if spawn_exc:
|
||||||
|
error_message, error_html_message = format_exception(spawn_exc)
|
||||||
|
else:
|
||||||
|
error_message = error_html_message = None
|
||||||
|
|
||||||
form = await self._render_form(
|
form = await self._render_form(
|
||||||
for_user=user,
|
for_user=user,
|
||||||
spawner_options_form=spawner_options_form,
|
spawner_options_form=spawner_options_form,
|
||||||
message=error_message,
|
message=error_message,
|
||||||
|
html_message=error_html_message,
|
||||||
)
|
)
|
||||||
self.finish(form)
|
self.finish(form)
|
||||||
else:
|
else:
|
||||||
@@ -265,9 +290,23 @@ class SpawnHandler(BaseHandler):
|
|||||||
self.log.error(
|
self.log.error(
|
||||||
"Failed to spawn single-user server with form", exc_info=True
|
"Failed to spawn single-user server with form", exc_info=True
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Explicitly catch HTTPError and report them to the client
|
||||||
|
# This may need scoping to particular error codes.
|
||||||
|
if isinstance(e, web.HTTPError):
|
||||||
|
self.set_status(e.status_code)
|
||||||
|
|
||||||
|
for name, value in e.headers.items():
|
||||||
|
self.set_header(name, value)
|
||||||
|
|
||||||
|
error_message, error_html_message = format_exception(e)
|
||||||
|
|
||||||
spawner_options_form = await user.spawner.get_options_form()
|
spawner_options_form = await user.spawner.get_options_form()
|
||||||
form = await self._render_form(
|
form = await self._render_form(
|
||||||
for_user=user, spawner_options_form=spawner_options_form, message=str(e)
|
for_user=user,
|
||||||
|
spawner_options_form=spawner_options_form,
|
||||||
|
message=error_message,
|
||||||
|
html_message=error_html_message,
|
||||||
)
|
)
|
||||||
self.finish(form)
|
self.finish(form)
|
||||||
return
|
return
|
||||||
@@ -379,6 +418,8 @@ class SpawnPendingHandler(BaseHandler):
|
|||||||
if isinstance(exc, web.HTTPError):
|
if isinstance(exc, web.HTTPError):
|
||||||
status_code = exc.status_code
|
status_code = exc.status_code
|
||||||
self.set_status(status_code)
|
self.set_status(status_code)
|
||||||
|
|
||||||
|
message, html_message = format_exception(exc, only_jupyterhub=True)
|
||||||
html = await self.render_template(
|
html = await self.render_template(
|
||||||
"not_running.html",
|
"not_running.html",
|
||||||
user=user,
|
user=user,
|
||||||
@@ -386,8 +427,8 @@ class SpawnPendingHandler(BaseHandler):
|
|||||||
server_name=server_name,
|
server_name=server_name,
|
||||||
spawn_url=spawn_url,
|
spawn_url=spawn_url,
|
||||||
failed=True,
|
failed=True,
|
||||||
failed_html_message=getattr(exc, 'jupyterhub_html_message', ''),
|
failed_html_message=html_message,
|
||||||
failed_message=getattr(exc, 'jupyterhub_message', ''),
|
failed_message=message,
|
||||||
exception=exc,
|
exception=exc,
|
||||||
)
|
)
|
||||||
self.finish(html)
|
self.finish(html)
|
||||||
|
@@ -984,3 +984,13 @@ def fmt_ip_url(ip):
|
|||||||
if ":" in ip:
|
if ":" in ip:
|
||||||
return f"[{ip}]"
|
return f"[{ip}]"
|
||||||
return ip
|
return ip
|
||||||
|
|
||||||
|
|
||||||
|
def format_exception(exc, *, only_jupyterhub=False):
|
||||||
|
"""
|
||||||
|
Format an exception into a text string and HTML pair.
|
||||||
|
"""
|
||||||
|
default_message = None if only_jupyterhub else str(exc)
|
||||||
|
return getattr(exc, "jupyterhub_message", default_message), getattr(
|
||||||
|
exc, "jupyterhub_html_message", None
|
||||||
|
)
|
||||||
|
@@ -14,7 +14,11 @@
|
|||||||
{% if for_user and user.name != for_user.name -%}
|
{% if for_user and user.name != for_user.name -%}
|
||||||
<p>Spawning server for {{ for_user.name }}</p>
|
<p>Spawning server for {{ for_user.name }}</p>
|
||||||
{% endif -%}
|
{% endif -%}
|
||||||
{% if error_message -%}<p class="spawn-error-msg alert alert-danger">Error: {{ error_message }}</p>{% endif %}
|
{% if error_message %}
|
||||||
|
<p class="spawn-error-msg alert alert-danger">Error: {{ error_message }}</p>
|
||||||
|
{% elif error_html_message %}
|
||||||
|
<p class="spawn-error-msg alert alert-danger">{{ error_html_message | safe }}</p>
|
||||||
|
{% endif %}
|
||||||
<form enctype="multipart/form-data"
|
<form enctype="multipart/form-data"
|
||||||
id="spawn_form"
|
id="spawn_form"
|
||||||
action="{{ url | safe }}"
|
action="{{ url | safe }}"
|
||||||
|
Reference in New Issue
Block a user