---
tags: [后端, Go, Gin, 响应渲染, JSON]
create time: 2026-04-28 00:00
---
# 响应渲染
## 概述
Gin 提供了丰富的响应渲染方法——不仅限于基础的 JSON 返回,还包括防劫持的 SecureJSON、保留原始 Unicode 的 PureJSON、强制 ASCII 编码的 AsciiJSON,以及 XML / YAML / ProtoBuf / CSV / HTML 等多格式输出。理解各渲染方法的差异和适用场景,能写出更健壮、兼容的 API。
```mermaid
flowchart TD
A[选择响应渲染方式] --> B{客户端/使用方}
B -- "浏览器 SPA" --> C[c.JSON]
B -- "移动端或\n微服务间调用" --> D[c.PureJSON]
B -- "第三方不可信域名引用" --> E[c.SecureJSON]
B -- "严格 ASCII-only" --> F[c.AsciiJSON]
B -- "多格式接口" --> G["Accept\n内容协商"]
B -- "老旧跨域 GET\n(已淘汰)" --> H[c.JSONP]
style H fill:#ffcccc
style C fill:#e0ffe0
style D fill:#e0ffe0
C --> I[设置状态码并写入 Body]
D --> I
E --> I
F --> I
G --> I
H --> I
```
> **思考题:** 为什么 Gin 要同时提供 `c.JSON()` 和 `c.PureJSON()`?它们在哪些情况下输出相同,哪些情况不同?
> [!tip]- 一句话区别
> `c.JSON()` 会对部分非 ASCII 字符做 `\uXXXX` 转义;`c.PureJSON()` 原样输出 UTF-8 字节流。纯英文数据两者完全一致。
>
> ```text
> # 中文场景
> c.JSON() → {"message":"\\u4f60\\u597d"}
> c.PureJSON()→ {"message":"你好"}
>
> # 纯英文场景(完全一致)
> c.JSON() → {"message":"hello"}
> c.PureJSON() → {"message":"hello"}
> ```
### 选型速查表
| 场景 | 推荐方法 | 原因 |
|------|----------|------|
| 面向浏览器的 SPA | `c.JSON()` | 额外防御层(HTML/Unicode 转义) |
| 移动端 API / 微服务 | `c.PureJSON()` | 可读性好、带宽小、性能高 |
| 第三方不可信域名引用 | `c.SecureJSON()` | 防止 `
```
> [!danger]- JSONP 安全风险与 CORS 替代方案
> JSONP 的本质是在服务端拼接 JavaScript 代码——如果回调名未做白名单校验,攻击者可以注入恶意脚本:
>
> ```text
> # 恶意请求
> ?callback=
>
> # 被注入的输出
> ({"msg":"hello"})
> ```
>
> **现代替代方案:**
>
> | 方案 | 优点 | 缺点 |
> |------|------|------|
> | **CORS** (`Access-Control-Allow-Origin: *`) | 安全、灵活、现代浏览器全支持 | 需要预检(OPTIONS)请求 |
> | **JSONP** | 兼容 IE6+ | 仅支持 GET、安全风险高、已淘汰 |
>
> **生产环境强烈建议改用 CORS**,配合 `Access-Control-Allow-Origin` 头使用。
### 7. 其他常用渲染方法一览
| 方法 | Content-Type | 典型用途 |
|------|-------------|---------|
| `c.String(code, fmt, a...)` | `text/plain; charset=utf-8` | 短文本 / 调试 |
| `c.Data(code, ct, bytes)` | 自定义 | 原始字节流(图片、PDF 等) |
| `c.File(filepath)` | 自动推断 | 静态资源下载 |
| `c.FileBinary(filepath)` | `application/octet-stream` | 二进制文件下载 |
| `c.FileAttachment(filepath, name)` | `application/octet-stream; attachment` | 强制下载弹窗 + 自定义文件名 |
| `c.HTML(code, tmpl, obj)` | `text/html` | HTML 模板渲染 |
| `c.ProtoBuf(code, pb)` | `application/x-protobuf` | Protobuf 序列化 |
> [!note]- File / FileBinary / FileAttachment 选型
> | 方法 | Content-Type | 浏览器行为 |
> |------|-------------|-----------|
> | `c.File(filepath)` | 按扩展名自动推断(`.jpg` → `image/jpeg`) | **可能直接在浏览器预览** |
> | `c.FileBinary(filepath)` | `application/octet-stream` | 触发下载,但无自定义文件名 |
> | `c.FileAttachment(filepath, name)` | `application/octet-stream; attachment` | 触发下载 + 指定弹出文件名 |
>
> 如果希望用户下载文件而非在浏览器中打开,优先用 `c.FileAttachment()`。如果需要指定弹窗时显示的文件名(例如 `report-2026Q2.pdf`),这个方法也最方便。
### 8. 自定义 CSV 渲染
Gin 没有内置 `c.CSV()`,但组合 `c.Data()` 即可轻松实现:
```go
import (
"bytes"
"encoding/csv"
"net/http"
"github.com/gin-gonic/gin"
)
func csvExport(c *gin.Context) {
var buf bytes.Buffer
w := csv.NewWriter(&buf)
w.Write([]string{"name", "age", "city"})
w.Write([]string{"Alice", "30", "Beijing"})
w.Write([]string{"Bob", "25", "Shanghai"})
w.Flush()
c.Header("Content-Disposition", "attachment; filename=data.csv")
c.Data(http.StatusOK, "text/csv; charset=utf-8", buf.Bytes())
}
```
> **思考题:** 上面几节我们讨论了如何用 `switch` 根据 `Accept` 头做内容协商。如果支持的格式超过三种,每个分支都要写一次 `switch`,比较繁琐。有没有更优雅的封装?
Gin 提供了 `c.Negotiate()` 方法,将协商逻辑和响应写入合并到一个调用中:
```go
func negotiateHandler(c *gin.Context) {
data := gin.H{"title": "Go Guide", "version": "1.0"}
c.Negotiate(http.StatusOK, gin.Negotiate{
Offered: []string{
"application/json",
"application/xml",
"application/yaml",
},
Handler: func() {
switch c.NegotiationFormat() {
case "application/json":
c.JSON(http.StatusOK, data)
case "application/xml":
c.XML(http.StatusOK, data)
case "application/yaml":
c.YAML(http.StatusOK, data)
default:
c.AbortWithError(http.StatusNotAcceptable,
errors.New("unsupported media type"))
}
},
})
}
```
`c.Negotiate()` 内部会先检查 `Accept` 头是否在 `Offered` 列表中——如果是则进入 `Handler` 写入对应响应;如果不是则从 `Offered` 中选第一个作为默认格式写入,最后自动设置正确的 `Content-Type` 响应头。**推荐将所有多格式接口统一用此模式编写。**
## 关联笔记
- [[BACKEND/GIN/0-overview]]
- [[BACKEND/GIN/2-context-request]]
- [[BACKEND/GIN/1-middleware]]
- [[BACKEND/GIN/3-error-handling]]