英文:
Optimising html/template Composition
问题
我正在寻找一种更好(更快、更有组织)的方法来拆分我的Go模板。我强烈倾向于使用html/template(或其包装器),因为我相信它的安全模型。
- 目前我使用
template.ParseGlob
在init()
函数中解析所有的模板文件。 - 我对生成的模板应用
template.Funcs
。 - 我在每个模板中设置一个
$title
(例如listing_payment.tmpl
),并将其传递给内容模板。 - 我了解到
html/template
一旦解析,会将模板缓存在内存中。 - 我的处理程序只调用
t.ExecuteTemplate(w, "name.tmpl", map[string]interface{})
,并且在每个请求中不进行任何愚蠢的解析。 - 我通过以下方式组合模板(这是我觉得有点笨拙的部分):
{{ $title := "Page Title" }}
{{ template "head" $title }}
{{ template "checkout" }}
{{ template "top" }}
{{ template "sidebar_details" . }}
{{ template "sidebar_payments" }}
{{ template "sidebar_bottom" }}
<div class="bordered-content">
...
{{ template "listing_content" . }}
...
</div>
{{ template "footer" }}
{{ template "bottom" }}
我的三个问题是:
- 这种方式的性能如何?多个
{{ template "name" }}
标签是否会导致每个请求的性能损耗?在对较重的页面进行压力测试时,我看到了很多write - broken pipe
错误。这可能只是由于套接字超时(即套接字在写入完成之前关闭)而不是某种每个请求的组合(如果我理解错了,请纠正我!)。 - 在html/template包的限制下,有没有更好的方法来实现这个?Django模板文档中的第一个示例接近我想要的。可以扩展基本布局并根据需要替换标题、侧边栏和内容块。
- 有点离题:当template.ExecuteTemplate在请求过程中返回错误时,有没有一种惯用的处理方式?如果我将writer传递给错误处理程序,页面上会出现混乱的内容(因为它会继续写入),但重定向似乎不符合惯用的HTTP方式。
英文:
I'm looking to see if there is a better (faster, more organised) way to split up my templates in Go. I strongly prefer to stick to html/template (or a wrapper thereof) since I trust its security model.
-
Right now I use
template.ParseGlob
to parse all of my template files in withininit()
. -
I apply template.Funcs to the resulting templates
-
I set a
$title
in each template (i.e.listing_payment.tmpl
) and pass this to the content template. -
I understand that
html/template
caches templates in memory once parsed -
My handlers only call
t.ExecuteTemplate(w, "name.tmpl", map[string]interface{})
and don't do any silly parsing on each request. -
I compose templates from multiple pieces (and this is the bit I find clunky) as below:
{{ $title := "Page Title" }} {{ template "head" $title }} {{ template "checkout" }} {{ template "top" }} {{ template "sidebar_details" . }} {{ template "sidebar_payments" }} {{ template "sidebar_bottom" }} <div class="bordered-content"> ... {{ template "listing_content" . }} ... </div> {{ template "footer"}} {{ template "bottom" }}
My three questions are:
-
Is this performant, or do the multiple
{{ template "name" }}
tags result in a potential per-request performance hit? I see a lot ofwrite - broken pipe
errors when stress testing heavier pages. This might just be due to socket timeouts (i.e. socket closing before the writer can finish) rather than some kind of per-request composition, though (correct me if otherwise!) -
Is there a better way to do this within the constraints of the html/template package? The first example in Django's template docs approaches what I'd like. Extend a base layout and replace the title, sidebar and content blocks as needed.
-
Somewhat tangential: when template.ExecuteTemplate returns an error during a request, is there an idiomatic way to handle it? If I pass the writer to an error handler I end up with soup on the page (because it just continues writing), but a re-direct doesn't seem like idiomatic HTTP.
答案1
得分: 12
通过Reddit上的一些帮助,我设法找到了一个相当明智(且高效)的方法,可以实现以下功能:
- 使用内容块构建布局
- 创建有效地“扩展”这些布局的模板
- 使用其他模板填充块(脚本、侧边栏等)
base.tmpl
<html>
<head>
{{ template "title" .}}
</head>
<body>
{{ template "scripts" . }}
{{ template "sidebar" . }}
{{ template "content" . }}
<footer>
...
</footer>
</body>
</html>
index.tmpl
{{ define "title"}}<title>首页</title>{{ end }}
// 我们必须在基本布局中定义每个块。
{{ define "scripts" }} {{ end }}
{{ define "sidebar" }}
// 我们有一个根据页面变化的两部分侧边栏
{{ template "sidebar_index" }}
{{ template "sidebar_base" }}
{{ end }}
{{ define "content" }}
{{ template "listings_table" . }}
{{ end }}
... 还有我们的 Go 代码,利用了在 这个 Stack Overflow 回答 中概述的 map[string]*template.Template
方法:
var templates map[string]*template.Template
var ErrTemplateDoesNotExist = errors.New("模板不存在。")
// 在程序初始化时加载模板
func init() {
if templates == nil {
templates = make(map[string]*template.Template)
}
templates["index.html"] = template.Must(template.ParseFiles("index.tmpl", "sidebar_index.tmpl", "sidebar_base.tmpl", "listings_table.tmpl", "base.tmpl"))
...
}
// renderTemplate 是 template.ExecuteTemplate 的包装函数。
func renderTemplate(w http.ResponseWriter, name string, data map[string]interface{}) error {
// 确保模板存在于映射中。
tmpl, ok := templates[name]
if !ok {
return ErrTemplateDoesNotExist
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
tmpl.ExecuteTemplate(w, "base", data)
return nil
}
根据初始基准测试(使用 wrk
),在处理大量负载时似乎更高效,这很可能是因为我们不需要在每个请求中传递整个 ParseGlob
的模板。它还使得编写模板本身变得更简单。
英文:
With some help on Reddit I managed to work out a fairly sensible (and performant) approach to this that allows:
- Building layouts with content blocks
- Creating templates that effectively "extend" these layouts
- Filling in blocks (scripts, sidebars, etc.) with other templates
base.tmpl
<html>
<head>
{{ template "title" .}}
</head>
<body>
{{ template "scripts" . }}
{{ template "sidebar" . }}
{{ template "content" . }}
<footer>
...
</footer>
</body>
</html>
index.tmpl
{{ define "title"}}<title>Index Page</title>{{ end }}
// We must define every block in the base layout.
{{ define "scripts" }} {{ end }}
{{ define "sidebar" }}
// We have a two part sidebar that changes depending on the page
{{ template "sidebar_index" }}
{{ template "sidebar_base" }}
{{ end }}
{{ define "content" }}
{{ template "listings_table" . }}
{{ end }}
... and our Go code, which leverages the map[string]*template.Template
approach outlined in this SO answer:
var templates map[string]*template.Template
var ErrTemplateDoesNotExist = errors.New("The template does not exist.")
// Load templates on program initialisation
func init() {
if templates == nil {
templates = make(map[string]*template.Template)
}
templates["index.html"] = template.Must(template.ParseFiles("index.tmpl", "sidebar_index.tmpl", "sidebar_base.tmpl", "listings_table.tmpl", "base.tmpl"))
...
}
// renderTemplate is a wrapper around template.ExecuteTemplate.
func renderTemplate(w http.ResponseWriter, name string, data map[string]interface{}) error {
// Ensure the template exists in the map.
tmpl, ok := templates[name]
if !ok {
return ErrTemplateDoesNotExist
}
w.Header().Set("Content-Type", "text/html; charset=utf-8")
tmpl.ExecuteTemplate(w, "base", data)
return nil
}
From initial benchmarks (using wrk
) it seems to be a fair bit more performant when it comes to heavy load, likely due to the fact that we're not passing around a whole ParseGlob
worth of templates every request. It also makes authoring the templates themselves a lot simpler.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论