{"record":{"id":"89194e4f1fb32dd2","repo":"itwanger/toBeBetterJavaer","slug":"download-failed-with-http-response-status","errorCode":null,"errorMessage":"Download failed with HTTP ${response.status}","messagePattern":"Download failed with HTTP (.+?)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/convert-mdnice-images-to-cdn.js","lineNumber":365,"sourceCode":"  const workers = Array.from({ length: Math.min(concurrency, items.length) }, async () => {\n    while (cursor < items.length) {\n      const index = cursor;\n      cursor += 1;\n      await worker(items[index], index);\n    }\n  });\n  await Promise.all(workers);\n}\n\nasync function downloadImage(url) {\n  const response = await fetch(url, {\n    redirect: \"follow\",\n    headers: {\n      \"User-Agent\": \"toBeBetterJavaer-image-cdn/1.0\",\n    },\n  });\n  if (!response.ok) {\n    throw new Error(`Download failed with HTTP ${response.status}`);\n  }\n  const contentType = normalizeContentType(response.headers.get(\"content-type\"));\n  if (contentType && !contentType.startsWith(\"image/\") && contentType !== \"application/octet-stream\") {\n    throw new Error(`Unexpected content type: ${contentType}`);\n  }\n  const body = Buffer.from(await response.arrayBuffer());\n  if (body.length === 0) {\n    throw new Error(\"Downloaded file is empty\");\n  }\n  return { body, contentType };\n}\n\nfunction normalizeContentType(contentType) {\n  if (!contentType) {\n    return \"\";\n  }\n  return contentType.split(\";\")[0].trim().toLowerCase();\n}","sourceCodeStart":347,"sourceCodeEnd":383,"githubUrl":"https://github.com/itwanger/toBeBetterJavaer/blob/6617f5fd0b0597735bdc028f2fcd036817877327/scripts/convert-mdnice-images-to-cdn.js#L347-L383","documentation":"Thrown by downloadImage() in scripts/convert-mdnice-images-to-cdn.js when an HTTP fetch of an mdnice image URL returns a non-2xx status (response.ok is false). Fetch follows redirects and sends a custom User-Agent (toBeBetterJavaer-image-cdn/1.0). The thrown message embeds the exact HTTP status so you can distinguish 404 dead links from 403 anti-hotlink blocks from 5xx origin failures.","triggerScenarios":"A Markdown file references a files.mdnice.com image that was deleted (404); the CDN blocks the custom User-Agent or referer-less requests (403); rate limiting (429); transient origin errors (5xx). Only fires during a --write run since dry runs only scan.","commonSituations":"Old articles whose mdnice-hosted images expired; hotlink protection on the source CDN; flaky mobile-network or proxy environments; a proxy/interceptor returning 407/502; mdnice changing URL signing.","solutions":["Retry the run — transient 5xx/429 failures often clear on a second pass","For 404, find the dead image URL in the --verbose dry-run output and fix or remove that reference in the Markdown","For 403, test the URL with the same User-Agent (`curl -A toBeBetterJavaer-image-cdn/1.0 <url>`); if blocked, download the image manually, put it in docs, and reference it locally","Use --limit to process a few URLs at a time and isolate the failing one"],"exampleFix":"# before: image gone from mdnice CDN\n![arch](https://files.mdnice.com/user/1234/dead-image.png)\n\n# after: reference a local copy already in the repo\n![arch](/images/dead-image.png)","handlingStrategy":"retry","validationCode":"// Cheap pre-check for a specific URL before a --write run\nconst res = await fetch(url, { method: \"HEAD\", headers: { \"User-Agent\": \"toBeBetterJavaer-image-cdn/1.0\" } });\nif (!res.ok) console.warn(`Will fail: ${url} -> HTTP ${res.status}`);","typeGuard":null,"tryCatchPattern":"catch (err) { if (/^Download failed with HTTP (4\\d\\d|5\\d\\d)$/.test(err.message)) { if (isTransient(err.message)) retryWithBackoff(); else logDeadLink(err.message); } else throw err; }","preventionTips":["Run --verbose dry runs periodically to spot dead mdnice links early","Retry once on 5xx/429 before treating a URL as dead","Mirror important images into the repo so the script is not the only copy path"],"tags":["network","http","cdn","download"],"backgroundTag":null,"analyzedSha":"6617f5fd0b0597735bdc028f2fcd036817877327","analyzedAt":"2026-08-14T14:50:55.107Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}