正确做法是发送HTTP HEAD请求仅获取响应头:用http.NewRequest("HEAD", url, nil)构造请求,设置User-Agent避免403,解析resp.Header.Get("Content-Length")(需strconv.ParseInt转换)和resp.Header.Get("Content-Type"),但须校验StatusCode为200或安全重定向,且注意CDN可能精简头部导致Content-Length缺失。

如何用 HEAD 请求获取远程文件大小和类型
直接 GET 整个文件再读 Content-Length 和 Content-Type 是浪费带宽且危险的——大文件可能卡死或触发 OOM。正确做法是发 HEAD,只取响应头。
- 用
http.NewRequest("HEAD", url, nil)构造请求,避免默认GET拉取正文 - 必须设置
User-Agent,否则 GitHub raw、Cloudflare 等服务直接返回 403 -
resp.Header.Get("Content-Length")返回的是字符串,需用strconv.ParseInt转成int64;若为空,说明服务端未提供或不支持 -
resp.Header.Get("Content-Type")可能是text/html; charset=utf-8,也可能是application/octet-stream,别只看前缀 - 注意:某些 CDN(如 Vercel)对
HEAD响应头做精简,Content-Length可能缺失,此时只能 fallback 到GET+io.Copy(ioutil.Discard, resp.Body)(但不写入磁盘)
判断服务端是否支持断点续传(Range)
不是所有 HTTP 服务都支持 Range,硬上会导致重复下载或文件错位。得先确认 Accept-Ranges: bytes 是否存在。
- 发一次
HEAD后检查resp.Header.Get("Accept-Ranges") == "bytes" - 部分服务(如 Nginx 默认配置)返回
Accept-Ranges: none或直接不返回该头,等同于不支持 - 即使头存在,也要验证:发一个带
Range: bytes=0-0的HEAD,看是否返回206 Partial Content和准确的Content-Range - 别依赖
Content-Length推算是否支持——有些代理会伪造它,但拒绝Range
从 Content-Disposition 头提取原始文件名
服务端常通过 Content-Disposition 指定下载文件名,但格式不统一,直接解析容易出错。
- 典型值如:
attachment; filename="report.pdf"; filename*=UTF-8''%E6%8A%A5%E5%91%8A.pdf - 优先解析
filename*=部分(RFC 5987),它是 UTF-8 编码的,兼容中文/日文等;filename=是 ASCII-only,中文会乱码 - 用
mime.BdecodeWord解码filename*=后的值,而不是手动url.QueryUnescape - 若两个字段都缺失,fallback 到
path.Base(url),但要注意 URL 中可能含 query 参数(如?v=1.2),得先用url.Parse清洗
为什么 Status Code 不是 200 就不能信元数据
很多服务在认证失败、限流或重定向时,仍返回非空的 Content-Length 和 Content-Type,但它们指向错误页而非真实资源。
立即学习“go语言免费学习笔记(深入)”;
- 例如 GitHub private repo 返回 404 时,
Content-Type是text/html,Content-Length是 HTML 页面长度——你误以为是目标文件大小 -
HEAD请求同样要检查resp.StatusCode,只接受200或明确支持的302(需自行重放,不能依赖自动重定向) - 若状态码异常,
resp.Header里的元数据已不可信,应立即返回错误,而不是尝试解析 - 特别注意:某些反爬网关(如 Cloudflare)对 HEAD 返回 403,但对 GET 返回 200 —— 这属于策略性差异,得按实际场景决定是否降级为 GET 获取元数据
HEAD 成功不代表资源可访问,Content-Length 存在不代表它准确,filename 字段不等于安全可用的本地路径**。


















