render包负责把 Go 对象序列化到 HTTP 响应。和binding对称,这里也是接口 + 多实现的设计。
1.1Render接口
源码位置:render/render.go:9-15
// Render interface is to be implemented by JSON, XML, HTML, YAML and so on. type Render interface { // Render writes data with custom ContentType. Render(http.ResponseWriter) error // WriteContentType writes custom ContentType. WriteContentType(w http.ResponseWriter) }只有两个方法:
Render(w):把数据写到w
WriteContentType(w):写 Content-Type 头
1.1.1 内置实现
源码位置:render/render.go:17-35
var ( _ Render = (*JSON)(nil) _ Render = (*IndentedJSON)(nil) _ Render = (*SecureJSON)(nil) _ Render = (*JsonpJSON)(nil) _ Render = (*XML)(nil) _ Render = (*String)(nil) _ Render = (*Redirect)(nil) _ Render = (*Data)(nil) _ Render = (*HTML)(nil) _ HTMLRender = (*HTMLDebug)(nil) _ HTMLRender = (*HTMLProduction)(nil) _ Render = (*YAML)(nil) _ Render = (*Reader)(nil) _ Render = (*AsciiJSON)(nil) _ Render = (*ProtoBuf)(nil) _ Render = (*TOML)(nil) _ Render = (*PDF)(nil) )编译期断言:这些类型都实现了Render接口。如果你新增类型忘了实现,编译就过不去。
1.1.2writeContentType辅助
// render/render.go:37-42 func writeContentType(w http.ResponseWriter, value []string) { header := w.Header() if val := header["Content-Type"]; len(val) == 0 { header["Content-Type"] = value } }关键判断:如果用户已经手动设置了 Content-Type,就不覆盖。
1.2 Context 与 Render 的桥梁
源码位置:context.go:1201-1216
// Render writes the response headers and calls render.Render to render data. func (c *Context) Render(code int, r render.Render) { c.Status(code) // ① 设置状态码 if !bodyAllowedForStatus(code) { // ② 对于 204/304 等不允许 body 的状态码,只写 header r.WriteContentType(c.Writer) c.Writer.WriteHeaderNow() return } if err := r.Render(c.Writer); err != nil { // ③ 真正渲染 _ = c.Error(err) // ④ 渲染失败,收集错误 c.Abort() } }统一入口:所有c.JSON / c.XML / c.HTML / ...都通过c.Render(code, renderImpl)实现。
1.2.1 各种 Render 方法
源码位置:context.go:1255-1289(节选)
func (c *Context) JSON(code int, obj any) { c.Render(code, render.JSON{Data: obj}) } func (c *Context) IndentedJSON(code int, obj any) { c.Render(code, render.IndentedJSON{Data: obj}) } func (c *Context) SecureJSON(code int, obj any) { c.Render(code, render.SecureJSON{ Prefix: c.engine.secureJSONPrefix, Data: obj, }) } func (c *Context) PureJSON(code int, obj any) { c.Render(code, render.PureJSON{Data: obj}) } func (c *Context) XML(code int, obj any) { c.Render(code, render.XML{Data: obj}) } func (c *Context) YAML(code int, obj any) { c.Render(code, render.YAML{Data: obj}) } func (c *Context) TOML(code int, obj any) { c.Render(code, render.TOML{Data: obj}) }每个 API 都是一行包装,核心在具体 Render 实现里。
1.3 JSON 渲染详解
源码位置:render/json.go
1.3.1JSON(默认,HTML 转义)
type JSON struct { Data any } func (r JSON) Render(w http.ResponseWriter) error { return WriteJSON(w, r.Data) } func WriteJSON(w http.ResponseWriter, obj any) error { writeContentType(w, jsonContentType) jsonBytes, err := json.API.Marshal(obj) // ★ 用 codec/json 抽象 if err != nil { return err } _, err = w.Write(jsonBytes) return err }1.3.2PureJSON(不转义)
type PureJSON struct { Data any } func (r PureJSON) Render(w http.ResponseWriter) error { r.WriteContentType(w) encoder := json.API.NewEncoder(w) encoder.SetEscapeHTML(false) // ★ 关键:关闭 HTML 转义 return encoder.Encode(r.Data) }区别 |
|
|
|
|
|
|
|
|
|
|
|
用途 | 防止 XSS 注入到 HTML | 返回原始 JSON |
1.3.3IndentedJSON(缩进)
jsonBytes, err := json.API.MarshalIndent(r.Data, "", " ")是多了缩进。性能差,仅供调试。
1.3.4SecureJSON(防 JSON 劫持)
type SecureJSON struct { Prefix string Data any } func (r SecureJSON) Render(w http.ResponseWriter) error { r.WriteContentType(w) jsonBytes, _ := json.API.Marshal(r.Data) // 如果是数组,前面加 prefix(默认 "while(1);") if bytes.HasPrefix(jsonBytes, []byte("[")) && bytes.HasSuffix(jsonBytes, []byte("]")) { w.Write([]byte(r.Prefix)) } _, err := w.Write(jsonBytes) return err }为什么这么做?防止<script src="/api/data">这种JSON 劫持攻击——
浏览器解析while(1);[...]会死循环,无法被恶意页面窃取数据。
1.3.5AsciiJSON(非 ASCII 转义)
for _, r := range bytesconv.BytesToString(ret) { if r > unicode.MaxASCII { escapeBuf = fmt.Appendf(escapeBuf[:0], "\\u%04x", r) buffer.Write(escapeBuf) } else { buffer.WriteByte(byte(r)) } }把中文字符等转成\uXXXX,适合老式客户端。
1.3.6JsonpJSON(跨域回调)
func (r JsonpJSON) Render(w http.ResponseWriter) (err error) { r.WriteContentType(w) ret, err := json.API.Marshal(r.Data) if err != nil { return err } if r.Callback == "" { _, err = w.Write(ret) return err } callback := template.JSEscapeString(r.Callback) w.Write([]byte(callback)) w.Write([]byte("(")) w.Write(ret) w.Write([]byte(");")) return nil }输出:cb({"id":1,...});
💡 Context 上的JSONP会自动从 query 取callback参数,没有就退化成普通 JSON。
1.4 高性能 JSON:codec 抽象
源码位置:codec/json/json.go
// 简化示意 type API interface { Marshal(v any) ([]byte, error) Unmarshal(data []byte, v any) error NewEncoder(w io.Writer) Encoder NewDecoder(r io.Reader) Decoder // ... }Gin 通过这个抽象层,根据平台选择最佳 JSON 库:
平台 | 默认实现 |
amd64 / arm64 |
|
其他(如 386) |
|
📌这就是为什么Gin 在 benchmark 里 JSON 性能领先——它自动用上了最优实现。
1.5 HTML 渲染
源码位置:render/html.go
1.5.1 两层接口
// HTMLRender:工厂接口 type HTMLRender interface { Instance(name string, data any) Render } // HTMLProduction:生产环境(预解析模板) type HTMLProduction struct { Template *template.Template Delims Delims } // HTMLDebug:开发环境(每次请求都重新加载) type HTMLDebug struct { Files []string Glob string FileSystem http.FileSystem Patterns []string Delims Delims FuncMap template.FuncMap } // HTML:具体渲染实例 type HTML struct { Template *template.Template Name string Data any }1.5.2 Context 中的 HTML 方法
源码位置:context.go:1221-1224
func (c *Context) HTML(code int, name string, obj any) { instance := c.engine.HTMLRender.Instance(name, obj) c.Render(code, instance) }c.engine.HTMLRender在LoadHTMLGlob/LoadHTMLFiles时被设置:
- 生产:
HTMLProduction,启动时一次解析,后续复用
- 开发(debug 模式):
HTMLDebug,每次请求都重新加载模板(便于改模板即时生效)
1.5.3 HTML.Render
func (r HTML) Render(w http.ResponseWriter) error { r.WriteContentType(w) return r.Template.ExecuteTemplate(w, r.Name, r.Data) }直接复用标准库html/template。
1.6 Reader / Data / String
1.6.1 Reader(流式响应)
源码位置:render/reader.go
type Reader struct { ContentType string ContentLength int64 Reader io.Reader Headers map[string]string } func (r Reader) Render(w http.ResponseWriter) (err error) { r.WriteContentType(w) if r.ContentLength >= 0 { if r.Headers == nil { r.Headers = map[string]string{} } r.Headers["Content-Length"] = strconv.FormatInt(r.ContentLength, 10) } r.writeHeaders(w) _, err = io.Copy(w, r.Reader) return }适用:大文件、动态生成的内容、转发其他 Reader。
Context 上的对应方法:
func (c *Context) DataFromReader(code int, contentLength int64, contentType string, reader io.Reader, extraHeaders map[string]string) { c.Render(code, render.Reader{ ContentType: contentType, ContentLength: contentLength, Reader: reader, Headers: extraHeaders, }) }1.6.2c.Stream
// context.go:1378 func (c *Context) Stream(step func(w io.Writer) bool) bool { w := c.Writer clientGone := w.CloseNotify() for { select { case <-clientGone: return true default: keepOpen := step(w) w.Flush() // ★ 每次循环都 Flush if !keepOpen { return false } } } }💡CloseNotify监听客户端断开。每步写完都Flush,
是 SSE(Server-Sent Events)流式推送的关键。
1.6.3 Data / String
// render/data.go type Data struct { ContentType string Data []byte } // render/text.go type String struct { Format string Data []any }1.7 Redirect
源码位置:render/redirect.go
type Redirect struct { Code int Request *http.Request Location string } func (r Redirect) Render(w http.ResponseWriter) error { if (r.Code < 300 || r.Code > 308) && r.Code != 201 { panic(fmt.Sprintf("Cannot redirect with status code %d", r.Code)) } http.Redirect(w, r.Request, r.Location, r.Code) return nil }复用标准库http.Redirect。
1.8 XML / YAML / TOML / ProtoBuf / BSON
它们的结构几乎一样:实现Render和WriteContentType。区别只在序列化库:
类型 | 库 |
XML |
|
YAML |
|
TOML |
|
ProtoBuf |
|
MsgPack |
|
BSON |
|
每种都对应一个 MIME 常量(在binding/binding.go中)。
1.9 自定义 Render
实现Render接口即可。例如 CSV:
type CSV struct { Data []User } func (c CSV) WriteContentType(w http.ResponseWriter) { w.Header().Set("Content-Type", "text/csv; charset=utf-8") } func (c CSV) Render(w http.ResponseWriter) error { c.WriteContentType(w) ww := csv.NewWriter(w) _ = ww.Write([]string{"id", "name"}) for _, u := range c.Data { _ = ww.Write([]string{strconv.Itoa(u.ID), u.Name}) } ww.Flush() return nil } // 使用 r.GET("/csv", func(c *gin.Context) { c.Render(200, CSV{Data: users}) })1.10 整体流程:一次c.JSON调用
c.JSON(200, gin.H{"msg": "ok"}) │ ↓ context.go:1255 c.Render(200, render.JSON{Data: gin.H{"msg":"ok"}}) │ ↓ context.go:1202 c.Status(200) ← 设置 writermem.status │ ↓ r.WriteContentType(w) ← 设置 Content-Type: application/json │ ↓ render/json.go:57 r.Render(w) = WriteJSON(w, data) │ ↓ render/json.go:67 writeContentType(w, ...) ← 实际写 header jsonBytes := json.API.Marshal(data) w.Write(jsonBytes) ← 写 body │ ↓ response_writer.go:84 w.WriteHeaderNow() ← 自动写出 status line1.11 小结
- ✅
Render接口 + 13 种内置实现(JSON/XML/HTML/YAML/TOML/ProtoBuf/...)
- ✅ Context 上的所有响应 API 都是
c.Render(code, renderImpl)的包装
- ✅ JSON 通过
codec/json抽象,自动用 sonic 或标准库
- ✅ HTML 区分 Production / Debug,后者每次重新加载模板
- ✅ Reader / Stream 支持流式响应,适合大文件和 SSE
- ✅ 自定义 Render 只需实现接口