--- 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]]