tonhowtf/omniget · error

HTTP {} downloading {}

Error message

HTTP {} downloading {}

What it means

download_single_stream checks the HTTP status before reading the body: for both a fresh download (no existing bytes) and a resume where the server did not return 206 or 416, any non-success status (4xx/5xx other than the two handled cases) aborts the attempt with 'HTTP <status> downloading <url>'. It deliberately embeds the status code and URL so the caller can see exactly which request failed and why class of failure (auth, not-found, server error).

Solutions

  1. Read the embedded status code: 404 -> verify the URL/link still exists; 403/401 -> supply credentials via download_direct_with_headers headers (Authorization, Cookie, Referer); 429 -> back off and retry later; 5xx -> retry after the server recovers.
  2. Test the exact URL with curl -I to reproduce the status outside the app and inspect response headers (WWW-Authenticate, Retry-After) for the fix.
  3. Refresh expired signed/tokenized URLs before downloading.
  4. Add required headers (User-Agent, Referer) that hotlink-protected CDNs demand; a missing browser-like User-Agent commonly causes 403.
  5. If 5xx persists across retries, switch mirrors or inform the user the source is temporarily unavailable.

Example fix

// before
let file = downloader.download_direct("https://cdn.example.com/gone.mp4", &out).await?; // HTTP 403 downloading ...
// after
let mut headers = HeaderMap::new();
headers.insert(header::REFERER, "https://example.com/page".parse()?);
headers.insert(header::USER_AGENT, "Mozilla/5.0".parse()?);
headers.insert(header::COOKIE, format!("session={}", session_cookie).parse()?);
let file = downloader.download_direct_with_headers(&url, &out, &headers).await?;
Defensive patterns

Strategy: try-catch

Validate before calling

// Rust: classify the URL's status before committing to a download
async fn check_status(client: &reqwest::Client, url: &str) -> Result<u16, String> {
    let r = client.head(url).send().await.map_err(|e| e.to_string())?;
    Ok(r.status().as_u16())
} // 403/401 -> add headers; 404 -> refresh link; 429 -> wait; else proceed

Try / catch

match downloader.download(&url, &out).await {
    Err(e) if e.to_string().starts_with("HTTP ") => {
        let msg = e.to_string(); // e.g. 'HTTP 403 downloading https://...'
        let status: u16 = msg.split_whitespace().nth(1).and_then(|s| s.parse().ok()).unwrap_or(0);
        match status {
            401 | 403 => Err(anyhow!("auth/hotlink blocked; supply headers")),
            404 => Err(anyhow!("file gone; refresh link")),
            429 => Err(anyhow!("rate limited; back off")),
            _ => Err(anyhow!("server error; retry later")),
        }
    }
    other => other,
}

Prevention

When it happens

Trigger: Any request during download/download_direct/download_direct_with_headers where the server replies with a non-2xx status — 404 for a removed file, 403 for forbidden/hotlink-protected content, 401 for missing auth, 429 rate-limiting, or 5xx server errors. For resumes, 200 instead of 206 also lands here (falls through to the non-success branch only if... it is success, so in practice 4xx/5xx).

Common situations: URL typo'd or file deleted (404); hotlink protection or missing Referer/Cookie/Authorization headers (403/401); aggressive rate limiting (429) especially across retry loops; temporary upstream outages (502/503); expired signed URLs returning 403.

Understand the failure class

Background: "API error: {status}" and "HTTP 401/403/404/429/5xx" errors: non-2xx HTTP responses explained — this error's family across 27 libraries.

Related errors


AI-assisted analysis of tonhowtf/omniget@8600b91f42 (2026-09-12). Data as JSON: /api/errors/2690f9297282fc86. Report an issue: GitHub.

Appendix: source

Thrown at src-tauri/omniget-core/src/core/direct_downloader.rs:504

        if let Some(total) = total_size {
            if existing_bytes >= total {
                return Ok(());
            }
        }
        request = request.header("Range", format!("bytes={}-", existing_bytes));
    }

    let response = request.send().await?;

    let mut offset = 0u64;
    if existing_bytes > 0 {
        if response.status() == reqwest::StatusCode::PARTIAL_CONTENT {
            offset = existing_bytes;
        } else if response.status() == reqwest::StatusCode::RANGE_NOT_SATISFIABLE {
            let _ = std::fs::remove_file(part_path);
            return Err(anyhow!("Range not satisfiable, restarting"));
        } else if !response.status().is_success() {
            return Err(anyhow!("HTTP {} downloading {}", response.status(), url));
        }
    } else if !response.status().is_success() {
        return Err(anyhow!("HTTP {} downloading {}", response.status(), url));
    }

    if let Some(ct) = response.headers().get("content-type") {
        if let Ok(ct_str) = ct.to_str() {
            if ct_str.contains("text/html") {
                return Err(anyhow!(
                    "Server returned HTML instead of media — URL may have expired"
                ));
            }
        }
    }

    use std::io::Write;
    let raw_file = if offset > 0 {
        std::fs::OpenOptions::new().append(true).open(part_path)?

View on GitHub (pinned to 8600b91f42)