)
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):把数据写到wWriteContentType(w):写 Content-Type 头1.1.1 内置实现源码位置:render/render.go:17-35var ( _ 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.go1.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) }区别JSONPureJSON用途防止 XSS 注入到 HTML返回原始 JSON1.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 / arm64bytedance/sonic(JIT 加速)其他(如 386)encoding/json(标准库)这就是为什么Gin 在 benchmark 里 JSON 性能领先——它自动用上了最优实现。1.5 HTML 渲染源码位置:render/html.go1.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-1224func (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.Renderfunc (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 / String1.6.1 Reader(流式响应)源码位置:render/reader.gotype 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.gotype 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。区别只在序列化库:类型库XMLencoding/xmlYAMLgoccy/go-yaml(高性能)TOMLpelletier/go-toml/v2ProtoBufgoogle.golang.org/protobufMsgPackugorji/go/codecBSONmongo-driver/v2每种都对应一个 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; charsetutf-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 只需实现接口