From adfb68fe6bbea4a2b23c9ed9f19ba0b58bce8f51 Mon Sep 17 00:00:00 2001 From: Claus Lohmar Date: Wed, 22 Jul 2026 12:16:45 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20move=20HEAD=20request=20out=20of=20criti?= =?UTF-8?q?cal=20path=20=E2=80=94=20non-blocking=20background=20thread?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 10s HEAD request for Content-Length blocked the download endpoint response. nginx proxy in front (bench.srv2.sechpoint.app:443 → :5000) timed out waiting, returned empty response → 'data is null' JS error. Now wget starts immediately, response returns instantly, and HEAD runs in a daemon thread to populate content_length for ETA later. --- frontend/app.py | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/frontend/app.py b/frontend/app.py index b05b61c..7975776 100644 --- a/frontend/app.py +++ b/frontend/app.py @@ -20,6 +20,7 @@ from logging.handlers import RotatingFileHandler import os import shutil import subprocess +import threading import time import uuid from pathlib import Path @@ -262,17 +263,7 @@ async def upload_raw(request: Request): if free_gb < 50: logger.warning("Low disk: %.1f GB free — download may fail", free_gb) - # Try to get file size via HEAD request (for speed estimation) - content_length = 0 - try: - head_resp = http_requests.head(url, timeout=10, allow_redirects=True) - cl = head_resp.headers.get("Content-Length") - if cl: - content_length = int(cl) - logger.info("Download size from HEAD: %.1f GiB", content_length / (1024**3)) - except Exception: - logger.info("Could not determine download size (HEAD failed — will skip ETA check)") - + # Start wget immediately (HEAD request for size happens in background) logger.info("Starting background download: %s → %s", url, dest) try: proc = subprocess.Popen( @@ -288,10 +279,22 @@ async def upload_raw(request: Request): "vmid": vmid, "vm_name": vm_name, "start_time": time.time(), - "content_length": content_length, + "content_length": 0, "_last_logged_bytes": 0, } + # Fire-and-forget HEAD request to get file size for ETA (non-blocking) + def _fetch_content_length(): + try: + head_resp = http_requests.head(url, timeout=5, allow_redirects=True) + cl = head_resp.headers.get("Content-Length") + if cl and filename in _active_downloads: + _active_downloads[filename]["content_length"] = int(cl) + logger.info("Download size from HEAD: %.1f GiB", int(cl) / (1024**3)) + except Exception: + pass + threading.Thread(target=_fetch_content_length, daemon=True).start() + return JSONResponse({ "phase": "downloading", "filename": filename,