Golang模板渲染方案及替代方案讨论
Golang模板渲染方案及替代方案讨论
我们来讨论一下模板库,在多重循环中使用 {{range}} 会因 HTML 转义和渲染到模板而变慢。在使用 html/template 渲染之前,是否有更好的方法一次性转义 Posts?
Forum.go
import("html/template")
var tmpl = template.Must(template.ParseFiles("index.html"))
type Thread struct {
Title string
}
type ListThreads struct {
Threads []*Thread
}
Posts := make([]*Thread, 30)
p := ListThreads{Posts}
tmpl.Execute(w, &p)
Template.html
{{ range .Threads }}
{{ .Title }}
{{ end }}
我也在考虑 Jet Template,它支持快速即时渲染以提高速度,值得一试吗?
更多关于Golang模板渲染方案及替代方案讨论的实战教程也可以访问 https://www.itying.com/category-94-b0.html
silvero:
在使用
html/template渲染之前,有没有更好的方法可以一次性转义Posts?
我发现 CSR(客户端渲染)通常比 SSR(服务器端渲染)更快。因此,我决定为此使用 AJAX 而不是 Go。如果你使用 Go 模板的 {{range}} 来创建列表,页面将在最后一条记录加载完成后才会完全渲染。以下是我使用 AJAX(CSR)处理 3000 条记录的尝试:https://form.go4webdev.org/aggrid。这里是使用 Go(SSR)的相同示例:https://goapi.go4webdev.org/ques。速度上没有太大差异,但 SSR 渲染某些部分会有延迟。
更多关于Golang模板渲染方案及替代方案讨论的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
在多重循环中使用 html/template 时,性能瓶颈通常出现在每个循环迭代中的转义操作。一次性转义整个数据集可以显著提升性能。以下是几种解决方案:
1. 预转义数据方案
在渲染前对数据进行预转义,避免模板引擎在循环中重复转义:
import (
"html/template"
"strings"
)
type SafeThread struct {
Title template.HTML // 使用 template.HTML 类型避免重复转义
}
type ListThreads struct {
Threads []*SafeThread
}
// 预转义函数
func escapeThreads(threads []*Thread) []*SafeThread {
safeThreads := make([]*SafeThread, len(threads))
for i, t := range threads {
safeThreads[i] = &SafeThread{
Title: template.HTML(template.HTMLEscapeString(t.Title)),
}
}
return safeThreads
}
// 使用示例
func renderThreads(w http.ResponseWriter, threads []*Thread) {
safeThreads := escapeThreads(threads)
p := ListThreads{Threads: safeThreads}
tmpl.Execute(w, &p)
}
模板保持不变:
{{ range .Threads }}
{{ .Title }}
{{ end }}
2. 使用 template.HTML 构建字符串
对于更复杂的场景,可以构建完整的 HTML 字符串:
import (
"bytes"
"html/template"
"strings"
)
func renderThreadsOptimized(w http.ResponseWriter, threads []*Thread) {
var buf bytes.Buffer
// 一次性构建所有线程的 HTML
for _, t := range threads {
buf.WriteString("<div>")
buf.WriteString(template.HTMLEscapeString(t.Title))
buf.WriteString("</div>\n")
}
// 将构建好的 HTML 作为安全内容传递
data := struct {
Content template.HTML
}{
Content: template.HTML(buf.String()),
}
tmpl := template.Must(template.New("index").Parse(`
<div class="threads-container">
{{.Content}}
</div>
`))
tmpl.Execute(w, data)
}
3. Jet Template 性能对比
Jet Template 确实在性能方面有优势,特别是在复杂循环场景中:
import (
"github.com/CloudyKit/jet/v6"
"net/http"
)
var jetViews = jet.NewSet(
jet.NewOSFileSystemLoader("./templates"),
jet.InDevelopmentMode(),
)
func renderWithJet(w http.ResponseWriter, threads []*Thread) {
t, err := jetViews.GetTemplate("index.jet")
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
}
vars := make(jet.VarMap)
vars.Set("threads", threads)
if err := t.Execute(w, vars, nil); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
}
Jet 模板文件 index.jet:
{{ range $i, $thread := threads }}
<div class="thread-{{ $i }}">
{{ $thread.Title }}
</div>
{{ end }}
4. 基准测试示例
展示性能差异的基准测试:
import (
"html/template"
"testing"
"bytes"
)
func BenchmarkHTMLTemplate(b *testing.B) {
tmpl := template.Must(template.New("test").Parse(`
{{ range .Threads }}
<div>{{ .Title }}</div>
{{ end }}
`))
threads := make([]*Thread, 1000)
for i := range threads {
threads[i] = &Thread{Title: "Test Thread " + string(rune(i))}
}
data := ListThreads{Threads: threads}
var buf bytes.Buffer
b.ResetTimer()
for i := 0; i < b.N; i++ {
tmpl.Execute(&buf, data)
buf.Reset()
}
}
func BenchmarkPreEscaped(b *testing.B) {
tmpl := template.Must(template.New("test").Parse(`
{{ range .Threads }}
<div>{{ .Title }}</div>
{{ end }}
`))
threads := make([]*Thread, 1000)
for i := range threads {
threads[i] = &Thread{Title: "Test Thread " + string(rune(i))}
}
safeThreads := escapeThreads(threads)
data := ListThreads{Threads: safeThreads}
var buf bytes.Buffer
b.ResetTimer()
for i := 0; i < b.N; i++ {
tmpl.Execute(&buf, data)
buf.Reset()
}
}
5. 混合方案
对于需要部分转义和部分安全内容的场景:
type MixedThread struct {
Title template.HTML
Description string // 这个字段会在模板中自动转义
UserInput string // 用户输入,需要转义
}
func prepareMixedData(threads []*Thread) []*MixedThread {
mixed := make([]*MixedThread, len(threads))
for i, t := range threads {
mixed[i] = &MixedThread{
Title: template.HTML(t.Title), // 假设 Title 已安全
Description: t.Description,
UserInput: t.UserInput,
}
}
return mixed
}
模板:
{{ range .Threads }}
<div class="thread">
<h2>{{ .Title }}</h2>
<p>{{ .Description }}</p>
<div class="user-content">{{ .UserInput }}</div>
</div>
{{ end }}
Jet Template 在大型循环和复杂模板中确实能提供更好的性能,特别是在不需要自动转义的场景中。预转义方案在保持 html/template 安全性的同时提供了性能提升。选择方案应根据具体需求:需要最高性能时考虑 Jet,需要平衡安全性和性能时使用预转义方案。

