直接用http.Get下载文件常出空文件等问题,因未检查状态码、未关闭响应体、未确认body可读;须用io.Copy流式写入、设超时、校验路径与文件名、处理重定向及断点续传。
直接用 http.get 下载文件是可行的,但大概率会出问题:空文件、连接卡死、中文名乱码、路径遍历、大文件 oom——真正能上线的下载逻辑必须绕过这些坑。
为什么 http.Get(url) 常常写出来是空文件?
常见错误不是代码写错,而是没做三件事:没检查状态码、没关响应体、没确认 body 可读。HTTP 返回 200 只代表连接成功,不代表 body 里有数据——可能是 HTML 错误页、重定向跳转后的内容为空、或服务端提前关闭了流。
- 务必先判断
resp.StatusCode是否在200–299范围,别只看err == nil - 必须加
defer resp.Body.Close(),否则连接不释放,几十次请求后net/http: timeout awaiting response headers就来了 - 如果 URL 可能重定向(比如 GitHub Releases 链接),
http.DefaultClient默认跟随,但最终resp.Request.URL.String()才是真实地址,得手动校验它是否仍指向文件资源 - 别信
Content-Length头一定存在;chunked 编码下它为空,此时io.Copy仍能工作,但进度计算会失效
怎么安全地把 resp.Body 写进本地文件?
核心就一条:永远用 io.Copy,绝不用 ioutil.ReadAll 或 io.ReadAll。后者会把整个响应体加载进内存,下个 800MB 的 ZIP 就直接 OOM。
- 用
os.Create(destPath)创建文件,失败立即返回,别忽略err - 写入前确保目录存在:
os.MkdirAll(filepath.Dir(destPath), 0755) - 路径拼接必须用
filepath.Join(dir, safeName),再用filepath.Abs+strings.HasPrefix检查是否越界,防../../etc/passwd类攻击 - 文件名若来自 URL,需清理非法字符(Windows 下
: " / \ | ? *),可简单用filepath.Base+ 正则替换,别直接信任原始url.Path
如何支持断点续传和进度显示?
io.Copy 本身不提供回调,要进度就得自己控制读写循环;要续传就得发 Range 请求头并校验 Content-Range 响应头——这两件事标准库都不自动做。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 进度显示:用固定大小缓冲区(如
buf := make([]byte, 64*1024))循环resp.Body.Read(buf),每次读完累加字节数,再按Content-Length算百分比;若该 header 缺失,只能显示“已下载 XX KB” - 断点续传:先
HEAD请求确认Accept-Ranges: bytes和Content-Length;本地文件存在时用os.Stat获取已下载字节数done;GET 时加头Range: bytes=done-;服务端返回206 Partial Content后,必须解析Content-Range头确认起始偏移是否匹配,再用os.OpenFile(..., os.O_WRONLY|os.O_APPEND)追加 - 超时必须设:用自定义
http.Client{Timeout: 30 * time.Second},默认无超时,网络卡住就永久挂起
下载到 HTTP 响应流(web handler)要注意什么?
如果是写一个下载接口(比如 /download?id=123),重点不在“怎么取文件”,而在“怎么让浏览器乖乖保存而不是打开”。http.ServeFile 看似简单,但它默认不设 Content-Disposition: attachment,PDF/CSV 很可能直接在浏览器里渲染。
立即学习“go语言免费学习笔记(深入)”;
- 必须手动设置响应头:
w.Header().Set("Content-Disposition", `attachment; filename*=UTF-8''` + url.PathEscape(filename)),中文名只靠filename="xxx"会乱码或截断 -
Content-Type推荐设为application/octet-stream,避免 MIME 类型探测干扰attachment行为 - 所有
w.Header().Set()必须在io.Copy(w, f)之前调用,否则触发http: superfluous response.WriteHeader - 文件路径必须白名单校验或
filepath.Clean+ 根目录约束,http.ServeFile(w, r, r.URL.Path)是高危写法
最易被忽略的一点:服务端返回的 Content-Length 和实际写入文件的字节数可能不一致——比如中间件注入了额外 header、gzip 压缩未关闭、或代理截断了响应。上线前一定要用 curl -I 和 ls -l 对比验证。

















