From df192c2ab6a7d958625c550436164b49d047531b Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Fri, 13 Mar 2026 16:27:59 +0100 Subject: [PATCH 1/2] Make middleware return bytes --- lib/galaxy/web/framework/middleware/error.py | 2 +- lib/galaxy/web/framework/middleware/profile.py | 4 ++-- lib/galaxy/web/framework/middleware/static.py | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/galaxy/web/framework/middleware/error.py b/lib/galaxy/web/framework/middleware/error.py index 86e1738efe7..93daa1c1c36 100644 --- a/lib/galaxy/web/framework/middleware/error.py +++ b/lib/galaxy/web/framework/middleware/error.py @@ -267,7 +267,7 @@ class CatchingIter: if not self.start_checker.response_started: self.start_checker("500 Internal Server Error", [("content-type", "text/html")], exc_info) - return response + return response.encode("utf-8", errors="ignore") def close(self): # This should at least print something to stderr if the diff --git a/lib/galaxy/web/framework/middleware/profile.py b/lib/galaxy/web/framework/middleware/profile.py index 672c90d4154..7bfeed86907 100644 --- a/lib/galaxy/web/framework/middleware/profile.py +++ b/lib/galaxy/web/framework/middleware/profile.py @@ -67,7 +67,7 @@ class ProfileMiddleware: prof = cProfile.Profile() prof.runctx("run_app()", globals(), locals()) # Build up body with stats - body = "".join(body) + body = b"".join(body) headers = catch_response[1] content_type = response.header_value(headers, "content-type") if not content_type.startswith("text/html"): @@ -77,7 +77,7 @@ class ProfileMiddleware: stats.strip_dirs() stats.sort_stats("time", "calls") output = pstats_as_html(stats, self.limit) - body += template % output + body += (template % output).encode("utf-8") return [body] diff --git a/lib/galaxy/web/framework/middleware/static.py b/lib/galaxy/web/framework/middleware/static.py index 432c6084189..b7f9da7b85f 100644 --- a/lib/galaxy/web/framework/middleware/static.py +++ b/lib/galaxy/web/framework/middleware/static.py @@ -54,7 +54,7 @@ class CacheableStaticURLParser(StaticURLParser): headers: list[tuple[str, str]] = [] ETAG.update(headers, mytime) start_response("304 Not Modified", headers) - return [""] # empty body + return [b""] # empty body app = FileApp(full) if self.cache_seconds: app.cache_control(max_age=int(self.cache_seconds)) From 0ffc5d7bacf7300cc2270b40d7185d2634a2fe27 Mon Sep 17 00:00:00 2001 From: mvdbeek Date: Fri, 13 Mar 2026 16:55:46 +0100 Subject: [PATCH 2/2] Fix legacy_expose_api error callable returning str instead of bytes The error inner function was used as a WSGI sub-application but returned a bare string. WSGI requires response bodies to be iterables of byte strings. With a2wsgi converting to ASGI, h11 rejects the str, causing a TypeError in starlette's BaseHTTPMiddleware. --- lib/galaxy/web/framework/decorators.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/galaxy/web/framework/decorators.py b/lib/galaxy/web/framework/decorators.py index 13b8d7dd70f..b76fd717d4d 100644 --- a/lib/galaxy/web/framework/decorators.py +++ b/lib/galaxy/web/framework/decorators.py @@ -20,6 +20,7 @@ from galaxy.exceptions.utils import ( ) from galaxy.util import ( parse_non_hex_float, + smart_str, unicodify, ) from galaxy.util.json import safe_dumps @@ -154,7 +155,7 @@ def legacy_expose_api(func, to_json=True, user_required=True): def decorator(self, trans, *args, **kwargs): def error(environ, start_response): start_response(error_status, [("Content-type", "text/plain")]) - return error_message + return [smart_str(error_message)] error_status = "403 Forbidden" if trans.error_message: