This repository has been archived on 2026-05-19. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
obsidian/BACKEND/GIN/9-response-rendering.md
T

5.2 KiB
Raw Blame History

tags, create time
tags create time
后端
Go
Gin
响应渲染
JSON
2026-04-28 00:00

响应渲染

概述

Gin 提供了丰富的响应渲染方法——不仅是基础的 JSON 返回,还包括防劫持的 SecureJSON、保留中文的 PureJSON、ASCII 转换、XML/YAML/ProtoBuf 输出等。理解各渲染方法的差异和适用场景,能写出更健壮、兼容的 API。

思考题:为什么 Gin 要同时提供 JSON 和 PureJSON?它们什么时候输出相同的结果,什么时候不同?

正文

1. 标准 JSON 渲染

func handler(c *gin.Context) {
    data := gin.H{
        "message": "你好",
        "items":   []string{"a", "b", "c"},
    }

    // 等价于 c.Render(200, render.JSON{Data: data})
    c.JSON(http.StatusOK, data)
}

输出:

{"items":["a","b","c"],"message":"你好"}

注意: 标准 c.JSON() 会对非 ASCII 字符做 Unicode 转义(\uXXXX),这是 RFC 4627 的要求,但现代浏览器和客户端都无需此限制。

2. PureJSON — 保留原始 Unicode

func handler(c *gin.Context) {
    c.PureJSON(http.StatusOK, gin.H{
        "message": "你好世界",
    })
}

输出:

{"message":"你好世界"}

PureJSON vs JSON 对比:

特性 c.JSON() c.PureJSON()
中文编码 你好 你好
HTML 转义 <script> → \<script\> 不转义
性能 略快(stdlib) 略慢(gjson)
安全性 更安全(防 XSS) 需注意 XSS

何时选哪个: 如果是面向浏览器的 SPA 应用,用 JSON(防 XSS);如果是移动端 API 或后端服务间调用,用 PureJSON(可读性更好、带宽更小)。

3. SecureJSON — 防 JSON 劫持

针对老式浏览器的安全保护——在输出前加上 )]}',\n 前缀,阻止 JSON 被邪恶的 <script src=...> 跨域加载:

func secureHandler(c *gin.Context) {
    c.SecureJSON(200, gin.H{"name": "wonder"})
}

输出:

)];},{"name":"wonder"}

适用场景:

  • API 需要被不受信任的第三方域名引用
  • 遗留系统无法升级 CSP 头
  • 安全合规要求严格的场景

不适用场景:

  • 现代 SPA(前后端分离,不用 script 标签加载 JSON)
  • 移动端 API
  • 需要解析该输出的自动化测试

4. AsciiJSON — 中文转 Unicode

与 PureJSON 相反,强制将所有非 ASCII 字符转为 Unicode 逃逸序列:

func asciiHandler(c *gin.Context) {
    c.AsciiJSON(200, gin.H{
        "name": "张三",
        "data": []int{1, 2, 3},
    })
}

输出:

{"data":[1,2,3],"name":"\xe5\xbc\xa0\xe4\xb8\x89"}

注意: AsciiJSON 使用的是 UTF-8 字节的十六进制转义(\xe5\xbc\xa0...),不是 \uXXXX。这是 Gin 的独特实现——客户端解码时需要特殊处理,一般不太常用。

5. XML / YAML / ProtoBuf 渲染

func multiFormat(c *gin.Context) {
    data := gin.H{"title": "Go Guide", "version": "1.0"}

    switch c.ContentType() {
    case "application/xml":
        c.XML(200, data)
    case "application/yaml":
        c.YAML(200, data)
    default:
        c.JSON(200, data)
    }
}

XML 输出示例:

<map><title>Go Guide</title><version>1.0</version></map>

YAML 输出示例:

map:
    title: Go Guide
    version: "1.0"

提问: Gin 的 XML 渲染用的是 gin.H(map[string]interface{}),输出的 XML 根节点总是 <map>。如果需要自定义 XML 标签,应该怎么改结构体?

答案:用结构体的 xml 标签:

type Article struct {
    Title   string `xml:"title"`
    Version string `xml:"version,attr"` // attr 表示属性
}

6. JSONP — JSON with Padding

通过脚本回调的方式实现跨域 GET 请求。由于涉及 eval 执行,安全风险高,现代项目中已很少使用:

func jsonpHandler(c *gin.Context) {
    c.JSONP(http.StatusOK, gin.H{"callback": "getData"})
}

浏览器端:

<script>
function getData(data) { console.log(data); }
</script>
<script src="https://api.example.com/data?callback=getData"></script>

安全警告: JSONP 要求回调函数名白名单校验,否则攻击者可构造 callback=<script>alert(1)</script> 注入 XSS。

7. 渲染方法速查

方法 内容类型 特点
c.JSON(code, obj) application/json 标准 JSON,ASCII 转义
c.PureJSON(code, obj) application/json 保留原始 Unicode
c.SecureJSON(code, obj) application/json 加前缀防劫持
c.AsciiJSON(code, obj) application/json 强制 ASCII 编码
c.XML(code, obj) application/xml XML 序列化
c.YAML(code, obj) application/x-yaml YAML 序列化
c.ProtoBuf(code, obj) application/x-protobuf Protobuf 序列化
c.String(code, format, vals) text/plain 字符串格式化
c.Data(code, data) 自定义 原始字节流
c.HTML(code, tmplName, obj) text/html HTML 模板渲染

思考题:如果你的 API 同时需要提供 JSON 和 CSV 两种格式,Gin 本身没有 c.CSV(),你应该怎么做?

提示:c.Data() 和 c.Writer.Write() 的组合。