How to set different content types when using HTML/TEMPLATE package in Go

huangapple go评论89阅读模式
英文:

How to set different content types when using HTML/TEMPLATE package in Go

问题

当尝试将值传递到.html代码中时,我使用了html/template包。

然而,我似乎无法设置在我的.html文件中引用的.css内容类型。它以纯文本形式提供给浏览器,因此格式被忽略。

在静态的.html文件中,我可以使用内置的http.Fileserver来处理内容类型,但模板化不起作用。我无法传递变量。它只显示为{{.}}

有没有办法将内置文件服务器http.Fileservercontent-type便利性与http.HandleFunc的模板能力结合起来?

以下是我目前的代码,没有使用http.Fileserver。注意,我的Go文件位于起始目录中,而index.html.css文件位于子目录/hello/中:

package main

import (
        "html/template"
        "io/ioutil"
        "log"
        "net/http"
)

var Message string = "test page"

func servePipe(w http.ResponseWriter, req *http.Request) {

        dat, err := ioutil.ReadFile("hello/index.html")
        if err != nil {
                log.Fatal("couldn't read file:", err)
        }   

        templ, err := template.New("Test").Parse(string(dat))

        if err != nil {
                log.Fatal("couldn't parse page:", err)
        }   

        templ.Execute(w, Message)

}

func main() {

        http.HandleFunc("/hello/", servePipe)
        http.ListenAndServe(":8080", nil)
}

以下是我的.html文件。HTML页面正常提供,没有任何问题。问题出在单独的.css文件上,它没有被提供(在.html文件中链接),因此格式化没有发生。

<!DOCTYPE html>
<html>
<head>
<title>Test Page</title>

        <link rel="stylesheet" type="text/css" href="style.css"/>
</head>

<body>

        <p>{{.}}</p>

</body>
</html>
英文:

When trying to pass a value into .html code I'm using the html/template package.

However, I can't seem to set the .css content type that is referenced in my .html file. It is being served to the browser as plain text and thus formatting is ignored.

In a static .html file I can use the built-in http.Fileserver which takes care of content types but then templating doesn't work. I can't pass in the variable. It just displays as {{.}}

Is there a way to combine the content-type convenience of the built-in fileserver http.Fileserver with the template ability of http.HandleFunc?

Here is my code as it stands without using http.Fileserver. Note, my Go file is in the starting directory and the index.html and .css files are in a subdirectory /hello/:

package main

import (
        &quot;html/template&quot;
        &quot;io/ioutil&quot;
        &quot;log&quot;
        &quot;net/http&quot;
)

var Message string = &quot;test page&quot;

func servePipe(w http.ResponseWriter, req *http.Request) {

        dat, err := ioutil.ReadFile(&quot;hello/index.html&quot;)
        if err != nil {
                log.Fatal(&quot;couldn&#39;t read file:&quot;, err)
        }   

        templ, err := template.New(&quot;Test&quot;).Parse(string(dat))

        if err != nil {
                log.Fatal(&quot;couldn&#39;t parse page:&quot;, err)
        }   

        templ.Execute(w, Message)

}

func main() {

        http.HandleFunc(&quot;/hello/&quot;, servePipe)
        http.ListenAndServe(&quot;:8080&quot;, nil)
}

Here is my .html file. The html page is being served without any issues. It's the separate .css file that is not being served (linked in the .html file) thus the formatting doesn't occur.

&lt;!DOCTYPE html&gt;
&lt;html&gt;
&lt;head&gt;
&lt;title&gt;Test Page&lt;/title&gt;

        &lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;style.css&quot;/&gt;
&lt;/head&gt;

&lt;body&gt;

        &lt;p&gt;{{.}}&lt;/p&gt;

&lt;/body&gt;
&lt;/html&gt;

答案1

得分: 5

Template.Execute() 方法通过调用传递的 io.WriterWrite() 方法来“传递”内容,而在你的情况下,传递的是 http.ResponseWriter

如果在第一次调用 ResponseWriter.Write() 之前没有设置内容类型(在你的示例中没有设置),那么 net/http 包将尝试检测内容类型(基于写入的前 512 个字节),并自动为你设置内容类型(如果 ResponseWriter.WriteHeader() 没有被调用,则还会设置 WriteHeader(http.StatusOK))。

这在 http.ResponseWriter.Write() 的文档中有说明:

// Write 将数据作为 HTTP 回复的一部分写入连接。
//
// 如果尚未调用 WriteHeader,则 Write 在写入数据之前调用 WriteHeader(http.StatusOK)。
// 如果 Header 不包含 Content-Type 行,则 Write 会将 Content-Type 添加为将初始 512 个字节的写入数据传递给 DetectContentType 的结果。
//
// ...
Write([]byte) (int, error)

因此,如果你的 index.html 如下所示:

&lt;html&gt;
  &lt;body&gt;
	This is the body!
  &lt;/body&gt;
&lt;/html&gt;

那么它将被正确识别为 HTML 文档,因此 text/html 内容类型将被自动设置。如果对你来说没有发生这种情况,那就意味着你的 index.html 没有被识别为 HTML 文档。所以请确保你的模板文件是有效的 HTML / CSS / 等文档,内容类型将会被自动正确推断。

还要注意,如果在某些“极端”情况下无法正常工作,你可以在调用 Template.Execute() 之前手动设置内容类型,如下所示:

w.Header().Set("Content-Type", "text/html")
templ.Execute(w, Message)

顺便说一下:不要在处理程序中读取和解析模板,有关详细信息,请参阅相关问题:https://stackoverflow.com/questions/28451675/it-takes-too-much-time-when-using-template-package-to-generate-a-dynamic-web-p


如果你提供的模板引用其他模板,它们不会自动加载和执行。在这种情况下,你应该有一个“预加载”的模板,其中包含所有的模板。template.ParseFiles()template.ParseGlob() 是一次加载多个模板文件的好“工具”。

所以如果你的 index.html 引用了 style.css,那么你需要确保也提供了 style.css。如果它不是一个模板(而是一个静态文件),你可以使用这里介绍的多种选项之一来提供它:https://stackoverflow.com/questions/28899675/include-js-file-in-go-template

如果它也是一个模板,那么你还需要加载它并通过调用 Template.ExecuteTemplate() 来提供它。

一个示例解决方案是使用 template.ParseFiles() 加载两个模板,并使用路径来“指定”你想要提供的模板(从客户端/浏览器端)。例如,请求路径 /hello/index.html 可以提供 &quot;index.html&quot; 模板,请求路径 /hello/style.css(在接收和处理 index.html 后,浏览器将自动执行此操作)可以提供 &quot;style.css&quot; 模板。代码示例如下(为简洁起见,省略了错误检查):

parts := strings.Split(req.URL.Path, "/") // 路径的各个部分
templName := parts[len(parts)-1]          // 最后一个部分
templ.ExecuteTemplate(w, templName, someDataForTemplate)
英文:

Template.Execute() will "deliver" the content by calling the Write() method of the passed io.Writer which in your case is the http.ResponseWriter.

If you don't set the content type prior to the first call to ResponseWriter.Write() (you don't in your example), then the net/http package will attempt to detect the content type (based on the first 512 bytes written), and will set it automatically for you (along with WriteHeader(http.StatusOK) if ResponseWriter.WriteHeader() has not been called).

This is documented at http.ResponseWriter.Write():

// Write writes the data to the connection as part of an HTTP reply.
//
// If WriteHeader has not yet been called, Write calls
// WriteHeader(http.StatusOK) before writing the data. If the Header
// does not contain a Content-Type line, Write adds a Content-Type set
// to the result of passing the initial 512 bytes of written data to
// DetectContentType.
//
// ...
Write([]byte) (int, error)

So if your index.html would look like this:

&lt;html&gt;
  &lt;body&gt;
	This is the body!
  &lt;/body&gt;
&lt;/html&gt;

Then this would be properly detected as an HTML document and thus text/html content type would be automatically set. If this doesn't happen for you, that means your index.html is not recognized as an HTML document. So just make sure you make your template files valid HTML / CSS / etc. documents and content type will be inferred automatically and properly.

Also note that if it wouldn't work in some "extreme" cases, you can always "override" it by manually setting it prior to calling Template.Execute() like this:

w.Header().Set(&quot;Content-Type&quot;, &quot;text/html&quot;)
templ.Execute(w, Message)

Side note: don't read and parse templates inside your handlers, for details see related question: https://stackoverflow.com/questions/28451675/it-takes-too-much-time-when-using-template-package-to-generate-a-dynamic-web-p


If a template served by you references other templates, they will not be magically loaded and executed. In such cases you should have a "pre-loaded" templates containing all the templates. template.ParseFiles() and template.ParseGlob() are good "tools" to load multiple template files at once.

So if your index.html references style.css, then you have to take care about serving style.css too. If it is not a template (but e.g. a static file), you may serve it using multiple options presented here: https://stackoverflow.com/questions/28899675/include-js-file-in-go-template

If it is also a template, then you also have to load it and serve it by calling Template.ExecuteTemplate().

An example solution would be to load both using template.ParseFiles(), and use the path to "designate" which template you want to have served (from the client / browser side).

E.g. requesting the path /hello/index.html could serve the &quot;index.html&quot; template, requesting the path /hello/style.css (this will be done automatically by the browser after receiving and processing the index.html) could serve the &quot;style.css&quot; template. It could look like this (error checks omitted for brevity):

parts := strings.Split(req.URL.Path, &quot;/&quot;) // Parts of the path
templName := parts[len(parts)-1]          // The last part
templ.ExecuteTemplate(w, templName, someDataForTemplate)

答案2

得分: 1

模板化,即生成作为HTTP响应主体的HTML,与通过HTTP头传输的内容类型完全解耦。只需在通过tmpl.Execute(w, ...)生成主体之前将Content-Type设置为适当的类型:

w.Header().Set("Content-Type", "text/css")
英文:

Templating i.e. generating the HTML which is the body of the HTTP response is totally decoupeled from the content type which is transmitted via a HTTP header. Just set the Content-Type to whatever is appropriate before generating the body via tmpl.Execute(w, ...) :

w.Header().Set(&quot;Content-Type&quot;, &quot;text/css&quot;)

huangapple
  • 本文由 发表于 2016年9月27日 03:54:47
  • 转载请务必保留本文链接:https://go.coder-hub.com/39711287.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定