
本文详解如何在 go web 服务中正确提供 mp4 视频文件,使其支持浏览器端的进度拖拽(seeking),关键在于遵循 http 范围请求(range requests)规范,并使用 http.servecontent 替代全量内存加载。
本文详解如何在 go web 服务中正确提供 mp4 视频文件,使其支持浏览器端的进度拖拽(seeking),关键在于遵循 http 范围请求(range requests)规范,并使用 http.servecontent 替代全量内存加载。
在 Go 中直接用 ioutil.ReadFile 将整个视频读入内存再一次性写出(如原始代码所示),不仅浪费资源、阻塞响应,更关键的是完全忽略 HTTP 的 Range 请求头机制——而正是该机制支撑了视频播放器的“拖动进度条”“快进/后退”等核心交互功能。
现代浏览器(Chrome、Firefox、Safari)在加载视频时,会自动发送带 Range: bytes=0- 或 Range: bytes=1024-2047 的请求,并期望服务端返回 206 Partial Content 状态码及对应字节段;同时,响应头中必须包含 Accept-Ranges: bytes,否则播放器将禁用 seek 控件。
✅ 正确做法是:让 Go 标准库替你处理 Range 协议。最简洁可靠的方案是使用 http.ServeContent:
package main
import (
"log"
"net/http"
"os"
"time"
)
func ServeHTTP(w http.ResponseWriter, r *http.Request) {
file, err := os.Open("./video.mp4")
if err != nil {
http.Error(w, "Video not found", http.StatusNotFound)
return
}
defer file.Close()
// ServeContent 自动解析 Range 头、返回 206、设置 Accept-Ranges 等
// 第三个参数为文件名(用于 Content-Disposition),第四个为最后修改时间(影响缓存和 ETag)
http.ServeContent(w, r, "video.mp4", time.Now(), file)
}
func main() {
http.HandleFunc("/", ServeHTTP)
log.Println("Server starting on :8080")
log.Fatal(http.ListenAndServe(":8080", nil))
}⚠️ 注意事项:
- 切勿使用 ioutil.ReadFile + bytes.Buffer 全量加载大视频:MP4 文件动辄数百 MB,极易触发 OOM;
- http.ServeContent 要求传入的 io.ReadSeeker(如 *os.File)支持随机读取(Seek),MP4 文件天然满足;
- 时间戳建议使用 file.Stat().ModTime() 而非 time.Now(),以启用强缓存与条件请求(如 If-Modified-Since);
- 若需自定义路径映射或权限控制,可封装逻辑后再调用 ServeContent,但不要绕过它自行解析 Range——标准库已完备实现 RFC 7233。
? 进阶提示:若需支持 HLS/DASH、转码、鉴权或 CDN 回源,应构建专用流媒体服务;但对静态 MP4 的基础 seek 支持,http.ServeContent 是 Go 生态中最轻量、最健壮、最符合 HTTP 语义的解决方案。


















