Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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>
like image 941
nosequeldeebee Avatar asked Sep 26 '16 19:09

nosequeldeebee


1 Answers

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("Content-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)
like image 121
icza Avatar answered Sep 28 '22 08:09

icza