流式响应

流式响应适合处理大数据或需要逐步返回的场景,避免内存占用过高。

基本流式响应

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")
}

流式 JSON 响应

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)
    }
})

使用 DataFromReader

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)
})

Server-Sent Events 流

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 实现流式输出。注意处理连接断开和内存管理。