vault backup: 2026-04-28 08:53:28
This commit is contained in:
@@ -0,0 +1,185 @@
|
||||
---
|
||||
tags: [后端, Go, Gin, 响应渲染, JSON]
|
||||
create time: 2026-04-28 00:00
|
||||
---
|
||||
|
||||
# 响应渲染
|
||||
|
||||
## 概述
|
||||
|
||||
Gin 提供了丰富的响应渲染方法——不仅是基础的 JSON 返回,还包括防劫持的 SecureJSON、保留中文的 PureJSON、ASCII 转换、XML/YAML/ProtoBuf 输出等。理解各渲染方法的差异和适用场景,能写出更健壮、兼容的 API。
|
||||
|
||||
思考题:为什么 Gin 要同时提供 `JSON` 和 `PureJSON`?它们什么时候输出相同的结果,什么时候不同?
|
||||
|
||||
## 正文
|
||||
|
||||
### 1. 标准 JSON 渲染
|
||||
|
||||
```go
|
||||
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)
|
||||
}
|
||||
```
|
||||
|
||||
输出:
|
||||
```json
|
||||
{"items":["a","b","c"],"message":"你好"}
|
||||
```
|
||||
|
||||
**注意:** 标准 `c.JSON()` 会对非 ASCII 字符做 Unicode 转义(`\uXXXX`),这是 RFC 4627 的要求,但现代浏览器和客户端都无需此限制。
|
||||
|
||||
### 2. PureJSON — 保留原始 Unicode
|
||||
|
||||
```go
|
||||
func handler(c *gin.Context) {
|
||||
c.PureJSON(http.StatusOK, gin.H{
|
||||
"message": "你好世界",
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
输出:
|
||||
```json
|
||||
{"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=...>` 跨域加载:
|
||||
|
||||
```go
|
||||
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 逃逸序列:
|
||||
|
||||
```go
|
||||
func asciiHandler(c *gin.Context) {
|
||||
c.AsciiJSON(200, gin.H{
|
||||
"name": "张三",
|
||||
"data": []int{1, 2, 3},
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
输出:
|
||||
```json
|
||||
{"data":[1,2,3],"name":"\xe5\xbc\xa0\xe4\xb8\x89"}
|
||||
```
|
||||
|
||||
> **注意:** `AsciiJSON` 使用的是 UTF-8 字节的十六进制转义(`\xe5\xbc\xa0...`),不是 `\uXXXX`。这是 Gin 的独特实现——客户端解码时需要特殊处理,一般不太常用。
|
||||
|
||||
### 5. XML / YAML / ProtoBuf 渲染
|
||||
|
||||
```go
|
||||
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 输出示例:**
|
||||
```xml
|
||||
<map><title>Go Guide</title><version>1.0</version></map>
|
||||
```
|
||||
|
||||
**YAML 输出示例:**
|
||||
```yaml
|
||||
map:
|
||||
title: Go Guide
|
||||
version: "1.0"
|
||||
```
|
||||
|
||||
> **提问:** Gin 的 XML 渲染用的是 `gin.H`(map[string]interface{}),输出的 XML 根节点总是 `<map>`。如果需要自定义 XML 标签,应该怎么改结构体?
|
||||
|
||||
答案:用结构体的 `xml` 标签:
|
||||
|
||||
```go
|
||||
type Article struct {
|
||||
Title string `xml:"title"`
|
||||
Version string `xml:"version,attr"` // attr 表示属性
|
||||
}
|
||||
```
|
||||
|
||||
### 6. JSONP — JSON with Padding
|
||||
|
||||
通过脚本回调的方式实现跨域 GET 请求。由于涉及 eval 执行,安全风险高,现代项目中已很少使用:
|
||||
|
||||
```go
|
||||
func jsonpHandler(c *gin.Context) {
|
||||
c.JSONP(http.StatusOK, gin.H{"callback": "getData"})
|
||||
}
|
||||
```
|
||||
|
||||
浏览器端:
|
||||
```html
|
||||
<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()` 的组合。
|
||||
Reference in New Issue
Block a user