fix: move HEAD request out of critical path — non-blocking background thread

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.
This commit is contained in:
Claus Lohmar 2026-07-22 12:16:45 +00:00
parent c31ad3409a
commit adfb68fe6b

View file

@ -20,6 +20,7 @@ from logging.handlers import RotatingFileHandler
import os import os
import shutil import shutil
import subprocess import subprocess
import threading
import time import time
import uuid import uuid
from pathlib import Path from pathlib import Path
@ -262,17 +263,7 @@ async def upload_raw(request: Request):
if free_gb < 50: if free_gb < 50:
logger.warning("Low disk: %.1f GB free — download may fail", free_gb) logger.warning("Low disk: %.1f GB free — download may fail", free_gb)
# Try to get file size via HEAD request (for speed estimation) # Start wget immediately (HEAD request for size happens in background)
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)")
logger.info("Starting background download: %s%s", url, dest) logger.info("Starting background download: %s%s", url, dest)
try: try:
proc = subprocess.Popen( proc = subprocess.Popen(
@ -288,10 +279,22 @@ async def upload_raw(request: Request):
"vmid": vmid, "vmid": vmid,
"vm_name": vm_name, "vm_name": vm_name,
"start_time": time.time(), "start_time": time.time(),
"content_length": content_length, "content_length": 0,
"_last_logged_bytes": 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({ return JSONResponse({
"phase": "downloading", "phase": "downloading",
"filename": filename, "filename": filename,