Skip to content

Commit a15656c

Browse files
authored
Merge pull request #5146 from agoose77/fix-spawn-429
set HTTP status when spawn via GET params fails
2 parents f2da977 + 1978c36 commit a15656c

File tree

4 files changed

+66
-12
lines changed

4 files changed

+66
-12
lines changed

jupyterhub/apihandlers/users.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
from ..scopes import needs_scope
2525
from ..user import User
2626
from ..utils import (
27+
format_exception,
2728
isoformat,
2829
iterate_until,
2930
maybe_future,
@@ -865,9 +866,8 @@ async def get_ready_event():
865866
failed_event['message'] = "Spawn cancelled"
866867
elif f and f.done() and f.exception():
867868
exc = f.exception()
868-
message = getattr(exc, "jupyterhub_message", str(exc))
869+
message, html_message = format_exception(exc)
869870
failed_event['message'] = f"Spawn failed: {message}"
870-
html_message = getattr(exc, "jupyterhub_html_message", "")
871871
if html_message:
872872
failed_event['html_message'] = html_message
873873
else:
@@ -906,9 +906,8 @@ async def get_ready_event():
906906
failed_event['message'] = "Spawn cancelled"
907907
elif f and f.done() and f.exception():
908908
exc = f.exception()
909-
message = getattr(exc, "jupyterhub_message", str(exc))
909+
message, html_message = format_exception(exc)
910910
failed_event['message'] = f"Spawn failed: {message}"
911-
html_message = getattr(exc, "jupyterhub_html_message", "")
912911
if html_message:
913912
failed_event['html_message'] = html_message
914913
else:

jupyterhub/handlers/pages.py

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,13 @@
1515
from .. import __version__, orm
1616
from ..metrics import SERVER_POLL_DURATION_SECONDS, ServerPollStatus
1717
from ..scopes import describe_raw_scopes, needs_scope
18-
from ..utils import maybe_future, url_escape_path, url_path_join, utcnow
18+
from ..utils import (
19+
format_exception,
20+
maybe_future,
21+
url_escape_path,
22+
url_path_join,
23+
utcnow,
24+
)
1925
from .base import BaseHandler
2026

2127

@@ -92,14 +98,17 @@ class SpawnHandler(BaseHandler):
9298

9399
default_url = None
94100

95-
async def _render_form(self, for_user, spawner_options_form, message=''):
101+
async def _render_form(
102+
self, for_user, spawner_options_form, message='', html_message=''
103+
):
96104
auth_state = await for_user.get_auth_state()
97105
return await self.render_template(
98106
'spawn.html',
99107
for_user=for_user,
100108
auth_state=auth_state,
101109
spawner_options_form=spawner_options_form,
102110
error_message=message,
111+
html_error_message=html_message,
103112
url=url_concat(
104113
self.request.uri, {"_xsrf": self.xsrf_token.decode('ascii')}
105114
),
@@ -177,14 +186,15 @@ async def _get(self, user_name, server_name):
177186
await spawner.run_auth_state_hook(auth_state)
178187

179188
# Try to start server directly when query arguments are passed.
180-
error_message = ''
181189
query_options = {}
182190
for key, byte_list in self.request.query_arguments.items():
183191
query_options[key] = [bs.decode('utf8') for bs in byte_list]
184192

185193
# 'next' is reserved argument for redirect after spawn
186194
query_options.pop('next', None)
187195

196+
spawn_exc = None
197+
188198
if len(query_options) > 0:
189199
try:
190200
self.log.debug(
@@ -200,16 +210,31 @@ async def _get(self, user_name, server_name):
200210
"Failed to spawn single-user server with query arguments",
201211
exc_info=True,
202212
)
203-
error_message = str(e)
213+
spawn_exc = e
204214
# fallback to behavior without failing query arguments
205215

206216
spawner_options_form = await spawner.get_options_form()
207217
if spawner_options_form:
208218
self.log.debug("Serving options form for %s", spawner._log_name)
219+
220+
# Explicitly catch HTTPError and report them to the client
221+
# This may need scoping to particular error codes.
222+
if isinstance(spawn_exc, web.HTTPError):
223+
self.set_status(spawn_exc.status_code)
224+
225+
for name, value in spawn_exc.headers.items():
226+
self.set_header(name, value)
227+
228+
if spawn_exc:
229+
error_message, error_html_message = format_exception(spawn_exc)
230+
else:
231+
error_message = error_html_message = None
232+
209233
form = await self._render_form(
210234
for_user=user,
211235
spawner_options_form=spawner_options_form,
212236
message=error_message,
237+
html_message=error_html_message,
213238
)
214239
self.finish(form)
215240
else:
@@ -265,9 +290,23 @@ async def _post(self, user_name, server_name):
265290
self.log.error(
266291
"Failed to spawn single-user server with form", exc_info=True
267292
)
293+
294+
# Explicitly catch HTTPError and report them to the client
295+
# This may need scoping to particular error codes.
296+
if isinstance(e, web.HTTPError):
297+
self.set_status(e.status_code)
298+
299+
for name, value in e.headers.items():
300+
self.set_header(name, value)
301+
302+
error_message, error_html_message = format_exception(e)
303+
268304
spawner_options_form = await user.spawner.get_options_form()
269305
form = await self._render_form(
270-
for_user=user, spawner_options_form=spawner_options_form, message=str(e)
306+
for_user=user,
307+
spawner_options_form=spawner_options_form,
308+
message=error_message,
309+
html_message=error_html_message,
271310
)
272311
self.finish(form)
273312
return
@@ -379,15 +418,17 @@ async def get(self, user_name, server_name=''):
379418
if isinstance(exc, web.HTTPError):
380419
status_code = exc.status_code
381420
self.set_status(status_code)
421+
422+
message, html_message = format_exception(exc, only_jupyterhub=True)
382423
html = await self.render_template(
383424
"not_running.html",
384425
user=user,
385426
auth_state=auth_state,
386427
server_name=server_name,
387428
spawn_url=spawn_url,
388429
failed=True,
389-
failed_html_message=getattr(exc, 'jupyterhub_html_message', ''),
390-
failed_message=getattr(exc, 'jupyterhub_message', ''),
430+
failed_html_message=html_message,
431+
failed_message=message,
391432
exception=exc,
392433
)
393434
self.finish(html)

jupyterhub/utils.py

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -984,3 +984,13 @@ def fmt_ip_url(ip):
984984
if ":" in ip:
985985
return f"[{ip}]"
986986
return ip
987+
988+
989+
def format_exception(exc, *, only_jupyterhub=False):
990+
"""
991+
Format an exception into a text string and HTML pair.
992+
"""
993+
default_message = None if only_jupyterhub else str(exc)
994+
return getattr(exc, "jupyterhub_message", default_message), getattr(
995+
exc, "jupyterhub_html_message", None
996+
)

share/jupyterhub/templates/spawn.html

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,11 @@ <h1>Server Options</h1>
1414
{% if for_user and user.name != for_user.name -%}
1515
<p>Spawning server for {{ for_user.name }}</p>
1616
{% endif -%}
17-
{% if error_message -%}<p class="spawn-error-msg alert alert-danger">Error: {{ error_message }}</p>{% endif %}
17+
{% if error_message %}
18+
<p class="spawn-error-msg alert alert-danger">Error: {{ error_message }}</p>
19+
{% elif error_html_message %}
20+
<p class="spawn-error-msg alert alert-danger">{{ error_html_message | safe }}</p>
21+
{% endif %}
1822
<form enctype="multipart/form-data"
1923
id="spawn_form"
2024
action="{{ url | safe }}"

0 commit comments

Comments
 (0)