在 init 或处理函数中读取模板?

Read template in init or in handler function?

我正在为网站编写基本服务器。现在我面临一个(对我来说)困难的性能问题。是不是在init()函数中读取模板文件比较好?

// Initialize all pages of website
func init(){
 indexPageData, err := ioutil.ReadFile("./tpl/index.tpl")
 check(err)
}

或在 http.HandlerFunc?

func index(w http.ResponseWriter, req *http.Request){
  indexPageData, err := ioutil.ReadFile("./tpl/index.tpl")
  check(err)
  indexPageTpl := template.Must(template.New("index").Parse(string(indexPageData)))
  indexPageTpl.Execute(w, "test")
}

我认为在第一个示例中,服务器启动后您无需访问磁盘并提高请求的性能。
但是在开发过程中我想刷新浏览器并查看新内容。这可以通过第二个示例来完成。

有人有最先进的解决方案吗?或者从性能的角度来看,什么是正确的?

我们来分析一下性能:

我们将您的第一个解决方案命名为a(略有变化,见下文),您的第二个解决方案命名为b.

一个请求:
a: 一个磁盘访问
b: 访问一个磁盘

十个请求:
a: 一个磁盘访问
b: 十次磁盘访问

10 000 000 个请求:
a: 一个磁盘访问
b:10 000 000 次磁盘访问(这很慢)

因此,您的第一个解决方案的性能更好。但是您对最新数据的担忧呢?来自 func (t *Template) Execute(wr io.Writer, data interface{}) error 的文档:

Execute applies a parsed template to the specified data object, writing the output to wr. If an error occurs executing the template or writing its output, execution stops, but partial results may already have been written to the output writer. A template may be executed safely in parallel.

所以,发生的事情是这样的:

  1. 您从磁盘读取了一个模板
  2. 您将文件解析成模板
  3. 您选择要填空
  4. 的数据
  5. Execute 具有该数据的模板,结果被写入 io.Writer

您选择的数据是最新的。这与从磁盘重新读取模板甚至重新解析模板无关。这就是模板背后的全部理念:一次磁盘访问,一次解析,多个动态最终结果。

上面引用的文档告诉我们另一件事:

A template may be executed safely in parallel.

这非常有用,因为如果您有多个并行请求,那么您的 http.HandlerFunc 是 运行 并行的。

那么,现在该怎么办?
Read模板文件一次,
Parse模板一次,
Execute 每个请求的模板

我不确定你是否应该在 init() 函数中读取和解析,因为至少 Must 可以恐慌(并且不要在其中使用一些相对的,硬编码的路径!) - 我会尝试在更受控的环境中这样做,例如提供一个函数(如 New())来创建服务器的新实例并在其中执行这些操作。

编辑:我重新阅读了你的问题,我可能误解了你:

如果模板本身仍在开发中,那么是的,您必须在每次请求时阅读它以获得最新结果。这比每次更改模板都重新启动服务器更方便。对于生产,模板应该是固定的,只有数据应该改变。

抱歉,如果我误会了你。

切勿在生产环境中读取和解析请求处理程序中的模板文件,这非常糟糕(您应该始终避免这种情况)。开发的时候当然可以。

阅读此问题了解更多详情:

It takes too much time when using "template" package to generate a dynamic web page to client in golang

您可以通过多种方式解决这个问题。这里我列出了 4 个示例实现。

1。使用 "dev mode" 设置

你可以有一个常量或变量来告诉你是否 运行 处于开发模式,这意味着模板不会被缓存。

这是一个例子:

const dev = true

var indexTmpl *template.Template

func init() {
    if !dev { // Prod mode, read and cache template
        indexTmpl = template.Must(template.New("index").ParseFiles(".tpl/index.tpl"))
    }
}

func getIndexTmpl() *template.Template {
    if dev { // Dev mode, always read fresh template
        return template.Must(template.New("index").ParseFiles(".tpl/index.tpl"))
    } else { // Prod mode, return cached template
        return indexTmpl
    }
}

func indexHandler(w http.ResponseWriter, r *http.Request) {
    getIndexTmpl().Execute(w, "test")
}

2。如果您想要一个新模板,请在请求中指定(作为参数)

开发时,您可以指定一个额外的URL参数来指示读取新模板而不是使用缓存的模板,例如http://localhost:8080/index?dev=true

示例实现:

var indexTmpl *template.Template

func init() {
    indexTmpl = getIndexTmpl()
}

func getIndexTmpl() *template.Template {
    return template.Must(template.New("index").ParseFiles(".tpl/index.tpl"))
}

func indexHandler(w http.ResponseWriter, r *http.Request) {
    t := indexTmpl
    if r.FormValue("dev") != nil {
        t = getIndexTmpl()
    }
    t.Execute(w, "test")
}

3。根据主机决定

您还可以检查请求的主机名URL,如果是"localhost",您可以省略缓存并使用新模板。这需要最少的额外代码和工作量。请注意,您可能还想接受其他主机,例如"127.0.0.1"(由您决定要包括什么)。

示例实现:

var indexTmpl *template.Template

func init() {
    indexTmpl = getIndexTmpl()
}

func getIndexTmpl() *template.Template {
    return template.Must(template.New("index").ParseFiles(".tpl/index.tpl"))
}

func indexHandler(w http.ResponseWriter, r *http.Request) {
    t := indexTmpl
    if r.URL.Host == "localhost" || strings.HasPrefix(r.URL.Host, "localhost:") {
        t = getIndexTmpl()
    }
    t.Execute(w, "test")
}

4。检查上次修改的模板文件

您还可以在加载模板文件时存储模板文件的最后修改时间。每当请求模板时,您都可以检查源模板文件的最后修改时间。如果有变化,可以在执行前重新载入。

示例实现:

type mytempl struct {
    t       *template.Template
    lastmod time.Time
    mutex   sync.Mutex
}

var indexTmpl mytempl

func init() {
    // You may want to call this in init so first request won't be slow
    checkIndexTempl()
}

func checkIndexTempl() {
    nm := ".tpl/index.tpl"
    fi, err := os.Stat(nm)
    if err != nil {
        panic(err)
    }
    if indexTmpl.lastmod != fi.ModTime() {
        // Changed, reload. Don't forget the locking!
        indexTmpl.mutex.Lock()
        defer indexTmpl.mutex.Unlock()
        indexTmpl.t = template.Must(template.New("index").ParseFiles(nm))
        indexTmpl.lastmod = fi.ModTime()
    }
}

func indexHandler(w http.ResponseWriter, r *http.Request) {
    checkIndexTempl()
    indexTmpl.t.Execute(w, "test")
}