Skip to content
Go
Gin
响应渲染

响应渲染

Gin 内置支持以多种格式渲染响应,包括 JSON、XML、YAML 和 ProtoBuf。这使得构建支持内容协商的 API 变得简单直接。


渲染方法总览

方法 输出格式 Content-Type 适用场景
c.JSON(code, obj) JSON application/json REST API(最常用)
c.IndentedJSON(code, obj) 格式化 JSON application/json 开发/调试
c.PureJSON(code, obj) JSON(不转义 HTML) application/json 返回 HTML 片段
c.SecureJSON(code, obj) JSON(防劫持前缀) application/json JSONP 防护
c.AsciiJSON(code, obj) ASCII-JSON application/json 非 ASCII 字符转义
c.XML(code, obj) XML application/xml 遗留系统/SOAP
c.YAML(code, obj) YAML application/yaml 配置类接口
c.ProtoBuf(code, obj) Protocol Buffers application/protobuf 高性能微服务间通信
c.String(code, str) 纯文本 text/plain 简单文本响应
c.HTML(code, name, obj) HTML 模板 text/html 服务端渲染
c.Data(code, contentType, data) 自定义 自定义 任意二进制数据
c.File(filepath) 文件 推断 静态文件下载
c.Stream(step, contentType, reader) 流式 自定义 大文件传输

JSON 渲染

基本 JSON

r.GET("/json", func(c *gin.Context) {
    c.JSON(200, gin.H{
        "message": "hello",
        "count":   42,
    })
})
// → {"message":"hello","count":42}

格式化 JSON(开发环境)

r.GET("/pretty", func(c *gin.Context) {
    c.IndentedJSON(200, gin.H{
        "nested": gin.H{
            "key": "value",
        },
    })
})
// {
//     "nested": {
//         "key": "value"
//     }
// }

结构体序列化

type User struct {
    Name  string   `json:"name"`
    Age   int      `json:"age"`
    Email string   `json:"email,omitempty"` // 空值不序列化
    Roles []string `json:"roles,omitempty"`
}

r.GET("/user", func(c *gin.Context) {
    c.JSON(200, User{
        Name: "willow",
        Age:  25,
    })
})
// → {"name":"willow","age":25}

SecureJSON — JSON 劫持防护

c.SecureJSON() 在 JSON 前添加 while(1); 前缀,防止 JSON 劫持攻击:

r.GET("/secure", func(c *gin.Context) {
    c.SecureJSON(200, gin.H{"data": "sensitive"})
})
// → while(1);{"data":"sensitive"}

🔬 深入原理:JSON 劫持依赖于浏览器允许通过 <script> 标签跨域加载 JSON 并通过 __defineSetter__Object.prototype 重定义来窃取数据。while(1); 前缀让作为 <script> 加载的响应成为一个无限循环,从而阻止数据泄露。


PureJSON — 不转义 HTML

c.JSON() 会将 HTML 特殊字符(<>& 等)转义为 Unicode 实体。c.PureJSON() 则保持原样:

r.GET("/pure", func(c *gin.Context) {
    c.PureJSON(200, gin.H{"html": "<b>bold</b>"})
})
// → {"html":"<b>bold</b>"}

// 对比普通 JSON
// → {"html":"<b>bold</b>"}

AsciiJSON — 非 ASCII 转义

r.GET("/ascii", func(c *gin.Context) {
    c.AsciiJSON(200, gin.H{"lang": "中文"})
})
// → {"lang":"中文"}

XML 渲染

r.GET("/xml", func(c *gin.Context) {
    c.XML(200, gin.H{"message": "hello", "status": 200})
})
// <map>
//   <message>hello</message>
//   <status>200</status>
// </map>

YAML 渲染

r.GET("/yaml", func(c *gin.Context) {
    c.YAML(200, gin.H{"status": "ok", "services": []string{"api", "web"}})
})

ProtoBuf 渲染

适用于服务间高性能通信:

// 需要 .proto 文件及生成的代码
r.GET("/protobuf", func(c *gin.Context) {
    reps := []int64{1, 2, 3}
    data := &protoexample.Test{
        Label: proto.String("hello"),
        Reps:  reps,
    }
    c.ProtoBuf(200, data)
})

💡 ProtoBuf 产生更小的载荷和更快的序列化速度,但需要共享 schema 定义(.proto 文件)。


HTML 模板渲染

func main() {
    r := gin.Default()

    // 加载模板目录
    r.LoadHTMLGlob("templates/*")

    r.GET("/index", func(c *gin.Context) {
        c.HTML(200, "index.tmpl", gin.H{
            "title": "Gin Demo",
            "items": []string{"Go", "Gin", "Web"},
        })
    })
}
<!-- templates/index.tmpl -->
<html>
<body>
    <h1>{{ .title }}</h1>
    <ul>
    {{ range .items }}
        <li>{{ . }}</li>
    {{ end }}
    </ul>
</body>
</html>

🚨 陷阱:Go 的 html/template 默认转义 HTML 输出以防止 XSS,这是安全的。如果需要输出原始 HTML,在模板中使用 template.HTML 类型(但需谨慎)。


文件处理

// 返回单个文件
r.GET("/file", func(c *gin.Context) {
    c.File("./downloads/report.pdf")
})

// 返回文件流(支持 Range 请求)
r.GET("/stream", func(c *gin.Context) {
    f, _ := os.Open("./large-file.zip")
    defer f.Close()
    c.DataFromReader(200, -1, "application/zip", f, nil)
})

// 文件附件下载(Content-Disposition: attachment)
r.GET("/download", func(c *gin.Context) {
    c.FileAttachment("./downloads/report.pdf", "report-2024.pdf")
})

自定义渲染器

Gin 允许注册自定义 Render 实现:

type CustomRender struct {
    Data string
}

func (r CustomRender) Render(w http.ResponseWriter) error {
    r.WriteContentType(w)
    _, err := w.Write([]byte(r.Data))
    return err
}

func (r CustomRender) WriteContentType(w http.ResponseWriter) {
    w.Header().Set("Content-Type", "application/vnd.custom+json")
}

// 使用
r.GET("/custom", func(c *gin.Context) {
    c.Render(200, CustomRender{Data: `{"custom": "format"}`})
})

渲染选择指南

场景 推荐方法 原因
REST API 响应 c.JSON() 通用,浏览器友好
开发调试 c.IndentedJSON() 可读性好
前端已处理 HTML c.PureJSON() 不转义
JSONP 接口 c.SecureJSON() 防劫持
微服务间 RPC c.ProtoBuf() 高性能、小载荷
配置文件接口 c.YAML() 人类可读
文件下载 c.FileAttachment() 正确设置 Content-Disposition
流式传输 c.DataFromReader() 支持 Range,内存友好