流式响应适合处理大数据或需要逐步返回的场景,避免内存占用过高。
package main
import (
"fmt"
"time"
"github.com/gin-gonic/gin"
)
func main() {
r := gin.Default()
r.GET("/stream", func(c *gin.Context) {
c.Stream(func(w io.Writer) bool {
fmt.Fprintf(w, "数据 %d\n", time.Now().Unix())
time.Sleep(time.Second)
return true
})
})
r.Run(":8080")
}
r.GET("/stream-json", func(c *gin.Context) {
c.Header("Content-Type", "application/json")
c.Writer.Write([]byte("["))
first := true
for i := 0; i < 100; i++ {
if !first {
c.Writer.Write([]byte(","))
}
first = false
item := fmt.Sprintf(`{"id":%d,"name":"用户%d"}`, i, i)
c.Writer.Write([]byte(item))
c.Writer.Flush()
time.Sleep(100 * time.Millisecond)
}
c.Writer.Write([]byte("]"))
})
r.GET("/download/stream", func(c *gin.Context) {
file, err := os.Open("large-file.bin")
if err != nil {
c.String(500, "文件打开失败")
return
}
defer file.Close()
info, _ := file.Stat()
c.Header("Content-Disposition", "attachment; filename=large-file.bin")
c.Header("Content-Type", "application/octet-stream")
c.Header("Content-Length", fmt.Sprintf("%d", info.Size()))
io.Copy(c.Writer, file)
})
r.GET("/users/stream", func(c *gin.Context) {
c.Header("Content-Type", "application/x-ndjson")
rows, err := db.Query("SELECT id, name, email FROM users")
if err != nil {
c.String(500, "查询失败")
return
}
defer rows.Close()
for rows.Next() {
var id int
var name, email string
rows.Scan(&id, &name, &email)
user := map[string]interface{}{
"id": id,
"name": name,
"email": email,
}
data, _ := json.Marshal(user)
c.Writer.Write(data)
c.Writer.Write([]byte("\n"))
c.Writer.Flush()
}
})
r.GET("/chunked", func(c *gin.Context) {
c.Header("Content-Type", "text/plain")
c.Header("Transfer-Encoding", "chunked")
for i := 0; i < 10; i++ {
chunk := fmt.Sprintf("Chunk %d: %s\n", i, time.Now().Format(time.RFC3339))
c.Writer.Write([]byte(chunk))
c.Writer.Flush()
time.Sleep(500 * time.Millisecond)
}
})
r.GET("/file/stream", func(c *gin.Context) {
file, err := os.Open("video.mp4")
if err != nil {
c.String(404, "文件不存在")
return
}
defer file.Close()
info, _ := file.Stat()
c.DataFromReader(200, info.Size(), "video/mp4", file, map[string]string{
"Content-Disposition": "inline; filename=video.mp4",
})
})
r.GET("/video", func(c *gin.Context) {
file, err := os.Open("video.mp4")
if err != nil {
c.String(404, "文件不存在")
return
}
defer file.Close()
info, _ := file.Stat()
fileSize := info.Size()
rangeHeader := c.GetHeader("Range")
if rangeHeader == "" {
c.DataFromReader(200, fileSize, "video/mp4", file, nil)
return
}
var start, end int64
fmt.Sscanf(rangeHeader, "bytes=%d-%d", &start, &end)
if end == 0 || end >= fileSize {
end = fileSize - 1
}
file.Seek(start, 0)
c.Header("Content-Range", fmt.Sprintf("bytes %d-%d/%d", start, end, fileSize))
c.Header("Content-Length", fmt.Sprintf("%d", end-start+1))
c.Header("Content-Type", "video/mp4")
io.CopyN(c.Writer, file, end-start+1)
})
r.GET("/events-stream", func(c *gin.Context) {
c.Header("Content-Type", "text/event-stream")
c.Header("Cache-Control", "no-cache")
c.Header("Connection", "keep-alive")
ticker := time.NewTicker(time.Second)
defer ticker.Stop()
for {
select {
case t := <-ticker.C:
fmt.Fprintf(c.Writer, "data: %s\n\n", t.Format(time.RFC3339))
c.Writer.Flush()
case <-c.Request.Context().Done():
return
}
}
})
流式响应适合大数据传输、实时更新等场景。使用 c.Stream()、c.DataFromReader() 或直接操作 c.Writer 实现流式输出。注意处理连接断开和内存管理。