From 532cf95d59be498e4fa8c527d116be753ebdcc28 Mon Sep 17 00:00:00 2001 From: kqjy Date: Sun, 22 Mar 2026 11:14:32 +0800 Subject: [PATCH] Debug s3 api issues on Granian --- app/__init__.py | 27 ++++++++++++++++++++++----- app/admin_api.py | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 5 deletions(-) diff --git a/app/__init__.py b/app/__init__.py index 45c41a3..95801e7 100644 --- a/app/__init__.py +++ b/app/__init__.py @@ -47,6 +47,8 @@ _request_counter = itertools.count(1) class _ChunkedTransferMiddleware: + _logger = logging.getLogger("chunked_middleware") + def __init__(self, app): self.app = app @@ -54,14 +56,29 @@ class _ChunkedTransferMiddleware: if environ.get("REQUEST_METHOD") not in ("PUT", "POST"): return self.app(environ, start_response) + sha256 = environ.get("HTTP_X_AMZ_CONTENT_SHA256", "") + decoded_len = environ.get("HTTP_X_AMZ_DECODED_CONTENT_LENGTH", "") + content_encoding = environ.get("HTTP_CONTENT_ENCODING", "") + transfer_encoding = environ.get("HTTP_TRANSFER_ENCODING", "") content_length = environ.get("CONTENT_LENGTH") - body_expected = ( - environ.get("HTTP_X_AMZ_DECODED_CONTENT_LENGTH") - or "chunked" in environ.get("HTTP_TRANSFER_ENCODING", "").lower() - or "aws-chunked" in environ.get("HTTP_CONTENT_ENCODING", "").lower() + + is_streaming = ( + "STREAMING" in sha256.upper() + or decoded_len + or "aws-chunked" in content_encoding.lower() + or "chunked" in transfer_encoding.lower() ) - if body_expected and (not content_length or content_length == "0"): + if not is_streaming: + return self.app(environ, start_response) + + cl_int = 0 + try: + cl_int = int(content_length) if content_length else 0 + except (ValueError, TypeError): + pass + + if cl_int <= 0: try: raw = environ["wsgi.input"] body = raw.read() diff --git a/app/admin_api.py b/app/admin_api.py index 5b3eec3..c6738db 100644 --- a/app/admin_api.py +++ b/app/admin_api.py @@ -982,3 +982,42 @@ def integrity_history(): offset = int(request.args.get("offset", 0)) records = checker.get_history(limit=limit, offset=offset) return jsonify({"executions": records}) + + +@admin_api_bp.route("/debug/upload", methods=["PUT", "POST"]) +@limiter.limit(lambda: _get_admin_rate_limit()) +def debug_upload(): + principal, error = _require_admin() + if error: + return error + + env = request.environ + info = { + "CONTENT_LENGTH": env.get("CONTENT_LENGTH"), + "CONTENT_TYPE": env.get("CONTENT_TYPE"), + "HTTP_TRANSFER_ENCODING": env.get("HTTP_TRANSFER_ENCODING"), + "HTTP_CONTENT_ENCODING": env.get("HTTP_CONTENT_ENCODING"), + "HTTP_EXPECT": env.get("HTTP_EXPECT"), + "HTTP_X_AMZ_CONTENT_SHA256": env.get("HTTP_X_AMZ_CONTENT_SHA256"), + "HTTP_X_AMZ_DECODED_CONTENT_LENGTH": env.get("HTTP_X_AMZ_DECODED_CONTENT_LENGTH"), + "REQUEST_METHOD": env.get("REQUEST_METHOD"), + "SERVER_PROTOCOL": env.get("SERVER_PROTOCOL"), + "wsgi.input_type": type(env.get("wsgi.input")).__name__, + "request.content_length": request.content_length, + } + + try: + body = request.get_data(cache=False) + info["body_length"] = len(body) + if body: + info["body_preview_hex"] = body[:200].hex() + except Exception as e: + info["body_error"] = str(e) + + all_http = {} + for k, v in env.items(): + if k.startswith("HTTP_") and isinstance(v, str): + all_http[k] = v + info["all_http_headers"] = all_http + + return jsonify(info)