Gin 不能直接处理实时音视频流,因其设计面向短连接 HTTP 请求,不支持长连接、无 Content-Length 的流式传输,也无法解析 RTMP/WebRTC 协议;正确做法是用 Gin 做控制面(如生成 token、查询状态),将流媒体逻辑交给专用服务。

为什么不能直接用 gin.Default() 处理实时音视频流
Gin 的设计目标是处理短连接 HTTP 请求,所有中间件(包括默认的 gin.Recovery() 和 gin.Logger())都基于完整请求体解析。而音视频流(如 HLS 的 .ts 片段、WebRTC 的信令或 RTMP 推流)往往具备以下特征:
- 长连接、持续写入(比如
io.Copy往http.ResponseWriter写 TS 流时不能中断) - 无固定 Content-Length,需设置
Transfer-Encoding: chunked或禁用缓冲 - 客户端可能随时断连,服务端需感知并释放资源(Gin 默认不暴露底层
http.Hijacker) - RTMP/WebRTC 等协议根本不在 HTTP 范畴内,Gin 无法解析 C0/C1 握手包或 STUN/TURN 消息
强行用 c.Writer 写流,会触发 Gin 的响应体检查机制,导致 panic 或静默截断。
http.ResponseWriter 流式写入 TS/HLS 时的关键配置
若仅提供 HTTP-based 流(如静态 HLS 切片或简单 MPEG-TS 直播流),可绕过 Gin 路由中间件,直接使用原生 net/http 处理器,但必须注意:
- 调用
w.Header().Set("Content-Type", "video/mp2t")或"application/vnd.apple.mpegurl"(m3u8) - 必须关闭 Gin 的响应缓冲:在 handler 开头调用
c.Writer.Flush()并确保后续写入不被拦截 - 避免使用
c.String()/c.JSON()等封装方法,它们会覆盖 Header 并强制写入完整响应 - 对大文件流,需手动控制
io.Copy的 buffer size(默认 32KB 可能太小),防止阻塞
示例片段(非 Gin handler,而是裸 http.HandlerFunc):
立即学习“go语言免费学习笔记(深入)”;
func hlsStreamHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/vnd.apple.mpegurl")
w.Header().Set("Cache-Control", "no-cache")
// 关键:禁用 Gin 中间件后,此处才安全
f, _ := os.Open("./stream/index.m3u8")
defer f.Close()
io.Copy(w, f)
}
如何让 Gin 与真正流媒体服务共存
生产环境常见做法是分层:Gin 做控制面(API),流媒体逻辑交给专用服务(如 LiveKit、Mediasoup、或自研 RTMP 接收器)。Gin 只负责:
- 生成 token(如调用
livekit.AccessToken生成 WebRTC 加入凭证) - 转发推流地址(例如返回
{"rtmp_url": "rtmp://127.0.0.1:1935/live/abc"}) - 查询流状态(GET
/streams/:id→ 查询 Redis 中的活跃流列表) - 触发转码任务(POST
/transcode→ 向消息队列发 FFmpeg 命令)
此时 Gin 的路由和中间件完全可用,且不碰流数据本身。真正的流处理进程监听独立端口(如 1935/RTMP、8080/WebRTC),与 Gin 进程通过 IPC 或 HTTP API 通信。
上传大视频文件时 c.FormFile 的陷阱与替代方案
用户上传原始视频(如 MP4)用于转码,是常见需求,但 c.FormFile("file") 会把整个文件读进内存,几百 MB 就 OOM。正确做法是:
- 禁用 Gin 自动 multipart 解析:
r := gin.New()而非gin.Default(),不调用r.MaxMultipartMemory() - 改用
c.Request.MultipartReader()获取标准multipart.Reader - 逐 part 检查字段名(如
part.FormName() == "file"),再用io.Copy直接写入磁盘或 FFmpeg stdin - 务必设超时:
c.Request.Header.Set("Connection", "close")防止客户端慢速上传拖垮 goroutine
关键点:流式上传 ≠ 流式播放。前者是接收端行为,后者是发送端行为;Gin 只适合稳妥地“收”,不适合“发”实时流。
真正卡住多数人的不是代码怎么写,而是没想清楚 Gin 在整个音视频链路里的角色——它不该是流的搬运工,而是调度员和守门人。


















