http.FileServer 仅支持 GET 请求用于静态文件分发,不支持文件上传;上传需手动解析 multipart/form-data,调用 r.ParseMultipartForm(32 << 20) 并处理表单数据。

Go 的 http.FileServer 不能直接用于上传
很多人看到 http.FileServer 就以为能传文件,其实它只处理 GET 请求,负责静态文件分发,对 POST 或表单上传完全无感知。真正上传必须手动解析 multipart/form-data 请求体。
关键点:
- 用
r.ParseMultipartForm(32 预分配内存(32MB 是常见上限,超限会报 <code>http: request body too large) -
r.MultipartForm.File返回的是map[string][]*multipart.FileHeader,注意是切片 —— 即使单文件也要取[0] - 别直接用
fileHeader.Open()后 deferfclose:如果后续解码失败或校验不通过,文件可能已写一半,得先校验再开
保存上传文件时务必检查 FileHeader.Size 和扩展名
攻击者可伪造 Content-Type、修改后缀、甚至传恶意可执行文件。仅靠 fileHeader.Header.Get("Content-Type") 不可靠,它只是客户端声明的 MIME 类型。
实操建议:
立即学习“go语言免费学习笔记(深入)”;
- 用
fileHeader.Size > 10 拒绝超 10MB 的文件(按需调整) - 从
filepath.Base(fileHeader.Filename)提取原始名,再用strings.ToLower(filepath.Ext(name))取小写后缀 - 白名单校验:
if !slices.Contains([]string{".jpg", ".png", ".pdf"}, ext) { http.Error(w, "unsupported file type", http.StatusBadRequest); return } - 保存路径用
filepath.Join(uploadDir, uuid.NewString()+ext),避免原名冲突和路径遍历(../../etc/passwd类攻击)
下载文件要用 http.ServeContent 而非 http.ServeFile
http.ServeFile 简单但危险:它不做路径净化,若请求 /download?name=../config.yaml 可能泄露任意文件。而 http.ServeContent 允许你完全控制读取逻辑,并支持断点续传(Range 头)。
典型用法:
fi, err := os.Stat(filePath)
if os.IsNotExist(err) {
http.Error(w, "file not found", http.StatusNotFound)
return
}
w.Header().Set("Content-Disposition", fmt.Sprintf(`attachment; filename="%s"`, filepath.Base(filePath)))
w.Header().Set("Content-Type", "application/octet-stream")
http.ServeContent(w, r, filepath.Base(filePath), fi.ModTime(), bytes.NewReader(data))
注意:http.ServeContent 第四个参数是 modtime,它影响 Last-Modified 和缓存协商;第五个是 io.ReadSeeker,不能传普通 []byte —— 得包装成 bytes.NewReader(data) 或打开文件后用 os.File(它实现了 io.ReadSeeker)。
大文件上传/下载要设超时并流式处理
默认 HTTP 超时(如 30 秒)对百 MB 文件根本不够,且一次性读进内存会 OOM。必须流式 + 显式超时。
上传侧:
- 在 handler 开头加
ctx, cancel := context.WithTimeout(r.Context(), 5*time.Minute),后续所有 IO 操作都用该ctx - 用
fileHeader.Open()得到io.Reader后,用io.CopyN(dst, src, size)或带 buffer 的io.Copy,避免全量加载
下载侧:
- 设置
w.(http.Flusher).Flush()配合bufio.NewWriterSize(w, 64 控制缓冲区大小 - 不要用
ioutil.ReadFile加载整个文件再写 —— 改用http.ServeContent或io.Copy直接从*os.File流式输出
边界情况容易被忽略:客户端中断连接时,io.Copy 会返回 net/http: request canceled,此时应主动 cancel() 并清理临时资源(比如未完成的上传文件)。










