render 包负责把 Go 对象序列化到 HTTP 响应 。和 binding 对称,这里也是接口 + 多实现的设计。
1.1 Render 接口
源码位置 :render/render.go:9-15
scss
// 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
ini
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.2 writeContentType 辅助
css
// 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
scss
// 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(节选)
scss
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.1 JSON(默认,HTML 转义)
go
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.2 PureJSON(不转义)
go
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)
}
| 区别 | JSON |
PureJSON |
|---|---|---|
< |
< |
< |
> |
> |
> |
& |
& |
& |
| 用途 | 防止 XSS 注入到 HTML | 返回原始 JSON |
1.3.3 IndentedJSON(缩进)
css
jsonBytes, err := json.API.MarshalIndent(r.Data, "", " ")
是多了缩进。性能差,仅供调试。
1.3.4 SecureJSON(防 JSON 劫持)
go
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.5 AsciiJSON(非 ASCII 转义)
go
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.6 JsonpJSON(跨域回调)
scss
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
scss
// 简化示意
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 | bytedance/sonic(JIT 加速) |
| 其他(如 386) | encoding/json(标准库) |
📌这就是为什么 Gin 在 benchmark 里 JSON 性能领先------它自动用上了最优实现。
1.5 HTML 渲染
源码位置 :render/html.go
1.5.1 两层接口
go
// 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
css
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
scss
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
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 上的对应方法:
go
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.2 c.Stream
go
// 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
go
// 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
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 | encoding/xml |
| YAML | goccy/go-yaml(高性能) |
| TOML | pelletier/go-toml/v2 |
| ProtoBuf | google.golang.org/protobuf |
| MsgPack | ugorji/go/codec |
| BSON | mongo-driver/v2 |
每种都对应一个 MIME 常量(在 binding/binding.go 中)。
1.9 自定义 Render
实现 Render 接口即可。例如 CSV:
go
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 调用
css
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 line
1.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 只需实现接口