bigywater.tech
Gin · 41

HTML 模板:LoadHTMLGlob 的开销与模板函数

LoadHTMLGlob 什么时候解析模板?debug 与 release 差多少?放在子目录里的同名模板会怎样?出错的时候用户看到什么?这篇用真实的模板文件把 Gin 的 HTML 渲染一步步试出来。

1.7 vs 119.5 µs
release 与 debug 模式渲染一次的耗时(glob 匹配 5 个模板文件)
后一个覆盖
a/item.html 与 b/item.html 同名时,渲染出的是 B 目录的那个
200 + 空正文
模板名不存在时的响应

两种渲染器

实测 · 本机执行输出
  gin.go 的 LoadHTMLGlob(Gin v1.12.0):
     272 | func (engine *Engine) LoadHTMLGlob(pattern string) {
     273 |     left := engine.delims.Left
     274 |     right := engine.delims.Right
     275 |     templ := template.Must(template.New("").Delims(left, right).Funcs(engine.FuncMap).ParseGlob(pattern))
     276 | 
     277 |     if IsDebugging() {
     278 |         debugPrintLoadTemplate(templ)
     279 |         engine.HTMLRender = render.HTMLDebug{Glob: pattern, FuncMap: engine.FuncMap, Delims: engine.delims}
     280 |         return
     281 |     }
     282 | 
     283 |     engine.SetHTMLTemplate(templ)
     284 | }
  render/html.go:  54 | func (r HTMLProduction) Instance(name string, data any) Render {
  render/html.go:  63 | func (r HTMLDebug) Instance(name string, data any) Render {
  render/html.go:  71 | func (r HTMLDebug) loadTemplate() *template.Template {
  render/html.go:  76 |         return template.Must(template.New("").Delims(r.Delims.Left, r.Delims.Right).Funcs(r.FuncMap).ParseFiles(r.Files...))
  render/html.go:  79 |         return template.Must(template.New("").Delims(r.Delims.Left, r.Delims.Right).Funcs(r.FuncMap).ParseGlob(r.Glob))
  render/html.go:  82 |         return template.Must(template.New("").Delims(r.Delims.Left, r.Delims.Right).Funcs(r.FuncMap).ParseFS(

debug 模式用 HTMLDebug:每次渲染都重新 ParseGlob,所以改模板立刻生效,开发很方便;release 模式用 HTMLProduction,启动时解析一次。这也是线上必须设置 GIN_MODE=release 的原因之一:debug 模式每次渲染多出上百微秒的解析开销,还会有文件 I/O。

自动转义

实测 · 本机执行输出
    /i   → 200 Content-Type="text/html; charset=utf-8"
         <h1>标题</h1><p>正文 &lt;b&gt;粗体&lt;/b&gt; &amp; &#39;q&#39; &lt;script&gt;alert(1)&lt;/script&gt;</p>
    /u   → 200 Content-Type="text/html; charset=utf-8"
         <div class="u">ANN - <em>可信的 HTML</em></div>
    /u2  → 200 Content-Type="text/html; charset=utf-8"
         <div class="u">BOB - &lt;em&gt;不可信&lt;/em&gt;</div>
    /p   → 200 Content-Type="text/html; charset=utf-8"
         <html><body><main>嵌套模板</main></body></html>

用的是标准库的 html/template,会按上下文自动转义(HTML 文本、属性、JS、URL 各不相同)。用户输入里的 <script> 会变成实体。只有明确标成 template.HTML 的值才原样输出,所以不要把用户输入转成这个类型。

模板名与冲突

实测 · 本机执行输出
  GIN_MODE=release 改模板文件前 "<h1>T</h1><p>B</p>" → 改之后(不重启)"<h1>T</h1><p>B</p>"
  GIN_MODE=release 渲染一次 index.html(glob 匹配 5 个模板文件):中位 1.7 µs
  GIN_MODE=debug 改模板文件前 "<h1>T</h1><p>B</p>" → 改之后(不重启)"<h1>改过的模板 T</h1>"
  GIN_MODE=debug 渲染一次 index.html(glob 匹配 5 个模板文件):中位 119.5 µs

ParseGlob 用文件的 base name 做模板名,不含目录。tpl/*/*.html 下两个目录里的 item.html 会冲突,后一个静默覆盖前一个。要避免,在文件里用 {{define "a/item.html"}} 起带目录的唯一名字,再按这个名字渲染。

失败时的表现

实测 · 本机执行输出
    /a   → <i>B 目录的 item</i>
    /u   → <i>带目录前缀的名字</i>
    /uu  →

模板名不存在、渲染中途出错,Gin 都只是 c.Error 并 Abort:前者客户端看到 200 加空页面,后者看到截断的页面(模板边执行边写出)。这些错误只在 c.Errors 里,要靠错误处理中间件或日志发现。想避免半截页面,可以自己先 ExecuteTemplate 到缓冲区,成功后再写出。

函数与时机

实测 · 本机执行输出
    /nope     → 状态 200 正文 ""
    /partial  → 状态 200 正文 "ok"
    c.Errors: html/template: "nope.html" is undefined
    c.Errors: template: partial.html:1:13: executing "partial.html" at <.Missing.Field>: can't evaluate field Missing in type struct { X int }
    先 LoadHTMLGlob 后 SetFuncMap(模板里用了 upper)→ panic: template: user.html:1: function "upper" not defined

SetFuncMap 必须在 LoadHTMLGlob 之前调用,否则模板里引用的函数在解析时还不存在,LoadHTMLGlob 直接 panic(这是启动期的错误,不会漏到线上)。

分步演示

HTML 模板
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / Go 1.27.1(brew)/ Gin v1.12.0(go.mod 里 require,源码在模块缓存里);用 go run / go test 直接编译运行,需要库时经代理从 proxy.golang.org 下载;HTTP 实验用 httptest 或本机回环端口(9700 以上);计时用 time.Now / testing.B,数字每次运行会有小幅抖动。模板文件是实验时写在程序目录里的几个小文件;请求用 httptest 直接调用 ServeHTTP;耗时是 testing.Benchmark 的 5 轮中位数(每次迭代包含新建 httptest.NewRecorder);GIN_MODE 通过环境变量设置;其余每种情形各一次
    • 没有验证:模板嵌套在多级目录时 LoadHTMLFS / embed 的行为;{{block}} 与 {{template}} 的覆盖语义细节;自定义 Delims;热更新在 release 模式下的做法(如监听文件);大模板集合(几百个文件)的解析耗时;并发渲染的安全性(标准库文档保证模板可并发执行)
    京ICP备2026067502号-1京公网安备11010502063487号