Skip to content

fix: use %s placeholders in HTTPError to prevent Tornado from doubling % in gateway URLs#1620

Merged
krassowski merged 2 commits into
jupyter-server:mainfrom
terminalchai:fix/gateway-httperror-percent-encoding
Apr 30, 2026
Merged

fix: use %s placeholders in HTTPError to prevent Tornado from doubling % in gateway URLs#1620
krassowski merged 2 commits into
jupyter-server:mainfrom
terminalchai:fix/gateway-httperror-percent-encoding

Conversation

@terminalchai
Copy link
Copy Markdown
Contributor

Problem

Closes #1503.

When the Gateway server is unreachable or returns an error, send_request_to_gateway() raises web.HTTPError passing the error message as an f-string. Tornado's HTTPError.init escapes % → %% in log_message when no positional args are provided. This caused URLs containing percent-encoded characters (e.g. http://host/?redirect=http%3A%2F%2Fexample.com) to appear with doubled percent signs in error responses and logs.

From Tornado's source:
\\python
if log_message and not args:
self.log_message = log_message.replace('%', '%%')
\\

Fix

Replace the three f-string log_message arguments in send_request_to_gateway() with %s format strings and pass URLs / messages as positional �rgs. When �rgs is non-empty Tornado skips the escaping, so percent-encoded characters in gateway URLs are preserved correctly.

Changes

  • **\jupyter_server/gateway/gateway_client.py**: changed all three \web.HTTPError\ raises in \send_request_to_gateway()\ to use %s\ placeholders + positional args.
  • **\ ests/test_gateway.py**: added \ est_gateway_httperror_percent_not_doubled()\ which directly verifies that the old f-string approach doubles %\ in \log_message\ and the new placeholder+args approach does not.

Test

\
29 passed in 5.10s
\\

…g % in gateway URLs

Tornado's HTTPError escapes '%' as '%%' in the log_message string when no
positional args are provided. This caused gateway URLs containing percent-
encoded characters (e.g. 'http%3A%2F%2F') to appear doubled in error
responses and logs.

Replace f-string log_message arguments in the three web.HTTPError raises in
send_request_to_gateway() with '%s' placeholder strings and pass URL/message
values through the args parameter. Tornado only escapes log_message when args
is empty, so URL percent characters are preserved correctly.

Add test_gateway_httperror_percent_not_doubled() to demonstrate the old
f-string path doubles '%' and the new placeholder+args path does not.

Closes jupyter-server#1503
@ptch314
Copy link
Copy Markdown

ptch314 commented Apr 27, 2026

+1, this issue is also affecting me.

FYI, this was also fixed recently in a newer version of Tornado (6.5.0+), with a new get_message method, but the original log_message method and behavior is unchanged for backwards compatibility:

https://github.com/tornadoweb/tornado/pull/3465/changes

@krassowski krassowski added the bug label Apr 29, 2026
Copy link
Copy Markdown

@ptch314 ptch314 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM. Note that this PR adds regression tests for when %s are correctly left alone. But we do not need test cases for the original behavior when %s are replaced with %%s, because Jupyter Server does not intentionally utilize this behavior of log_message anywhere.

@krassowski krassowski merged commit 7aafbf2 into jupyter-server:main Apr 30, 2026
44 checks passed
@Carreau Carreau modified the milestone: 2.18 Apr 30, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

% in error message being encoded into %% (by Tornado HTTPError)

4 participants