nosequeldeebee nosequeldeebee - 2 months ago 15
HTML Question

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

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 (
"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)
}


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.

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

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

<body>

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

</body>
</html>

Answer

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:

<html>
  <body>
    This is the body!
  </body>
</html>

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("Conent-Type", "text/html")
templ.Execute(w, Message)

Side note: don't read and parse templates inside your handlers, for details see related question: It takes too much time when using "template" package to generate a dynamic web page to client in golang


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: 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 "index.html" 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 "style.css" template. It could look like this (error checks omitted for brevity):

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