A quick tour of building of doing web with golang, all living of the land with Go’s built-in standard library.

Packages

Working example, I have put my web server and templating code in source file $GOPATH/src/github.com/bm4cs/gotime/web/server.go. It does lots of things, but exports function StartServer (upper case first character means publically exported).

package web

func StartServer() {
  ...
}

The main func in $GOPATH/src/github.com/bm4cs/gotime/myapp/app.go can import the web package:

import (
	"github.com/bm4cs/gotime/web"
)

func main() {
	web.StartServer()
}

Handling Requests

The http package from the standard library, provides a ton a out of the box functionality. Writing Web Applications on golang.org is a very pragmatic guide.

func main() {

  http.Handle("/", &fooHandler{greeting: "sup dope"})

  // // the HandleFunc func
  // http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
  //   w.Write([]byte("gday golang"))
  // })

  http.ListenAndServe(":1337", nil)

}

type fooHandler struct {
  greeting string
}

func (h *fooHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) {
  w.Write([]byte(fmt.Sprintf("%v world", h.greeting)))
}

Note the nil passed to http.ServeAndListen, denote to use the default ServeMux.

ServeMux is an HTTP request multiplexer. It matches the URL of each incoming request against a list of registered patterns and calls the handler for the pattern that most closely matches the URL.

Several boilerplate handler implementations are provided:

  • FileServer serves HTTP requests with contents of the file system
  • NotFoundHandler returns 404
  • RedirectHandler
  • StripPrefix removes a specified prefix from URL and feeds the request to handler
  • TimeoutHandler runs a handler with a time.Duration which if expires results in a 503

File server manually:

http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    f, err := os.Open("public" + r.URL.Path)
    if err != nil {
      w.WriteHeader(http.StatusInternalServerError)
      log.Println(err)
    }

    defer f.Close()
    io.Copy(w, f)
  })

MIME type handling still needs to be added. Instead of coding this thing up further, lets put the built-in file server to work:

http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
  http.ServeFile(w, r, "public"+r.URL.Path)
})

Nice!

Finally to replace the standard ServeMux with a file system based equivalent:

http.ListenAndServe(":1337", http.FileServer(http.Dir("public")))

Templates

Go provides templating with the [text/template]() and html/template packages. Although similar, html/template will do additional HTML specifics such as character encoding and manage code injection. Templates are used by calling a parse then execute.

Loading a template in-memory:

templateString := `hello, rax refers to a register. This is a temporary storage location, which values can be written to, read from, or operated on.`

t, err := template.New("title").Parse(templateString)
if err != nil {
  fmt.Println(err)
}

err = t.Execute(os.Stdout, nil)
if err != nil {
  fmt.Println(err)
}

Loading a templates from the file system:

func populateTemplates() *template.Template {
  result := template.New("templates")
  const basePath = "web/templates"
  template.Must(result.ParseGlob(basePath + "/*.htm"))
  return result
}

func main() {
  templates := populateTemplates()
  http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
    requestedFile := r.URL.Path[1:]
    t := templates.Lookup(requestedFile + ".htm")
    if t != nil {
      _ := t.Execute(w, nil)
    } else {
      w.WriteHeader(http.StatusNotFound)
    }
  })

  http.Handle("/img/", http.FileServer(http.Dir("web/public")))
  http.Handle("/css/", http.FileServer(http.Dir("web/public")))
  http.ListenAndServe(":1337", nil)
}

Notes:

  • By placing templates on the file system e.g. ./web/templates/index.htm (relative to the binary), can hit http://localhost:1337/index (no htm suffix), which will in turn run the index.htm template.
  • template.Must asserts that templates can be resolved, or pulls the ejector seat on the app.’
  • convenient lookup up by name with Lookup
  • static assets such as images and CSS get routed to FileServer, due to having a more specific URL match

Subtemplates

In a nutshell:

{% raw %} <!DOCTYPE html> {{template “head”}} {{template “header”}}

{{template “scripts”}} {% endraw %}

In the templates base path (defined by ParseGlob on the template instance), in my case web/templates relative to the binary in $GOPATH/bin, I can create the 3 needed subtemplates, files called head, header and scripts. Thats it!

Template Composition (Layout Templates)

In a nutshell, is the reverse idea of a subtemplate, which defines a common base layout template, that every template is rendered into, here have a file called web/templates/_layout.htm:

{% raw %} <!DOCTYPE html> {{block “head” .}}{{end}} {{block “styles” .}}{{end}} {{block “header” .}}{{end}}

{{template “content” .}}
{{block “scripts” .}}{{end}} {% endraw %}

Defines a layout template. The block elements are shorthand for including define statements, which in essence only render the subtemplate if it’s defined (i.e. its optional in other words). The content subtemplate in the above example, however is not optional, as its not in a block. template mandatory, block optional.

In the above layout template, the only mandatory subtemplate is content. To define content to be rendered into the layout create a new template file web/templates/content/foo.htm, which surrounds everything in a define "content" tag:

{% raw %} {{define “content”}}

                    -@
.##@
.####@
@#####@
. *######@
.##@o@#####@
/############@
/##############@
@######@%######@
@######%#####o @######@ ######% -@#######h ######@.
/#####h
`*%@####@
@H@
*%#@
{{end}} {% endraw %}

At this stage the template engine nearly has everything it needs. Although a major problem awaits. If there are multiple templates that define the content, which will very likely be the case, which will win? By default, the last template loaded with the content define will win. This wont scale beyond a single page. So that individual content templates can be invoked as needed, each content template needs to be washed against the master layout template, and stored for latter use.

For each content template:

  • Clone an instance of the layout template
  • Using this layout instance, bind it to the content template
  • Store the resulting Template in a map map[string]*template.Template

Or in code (sans error handling):

func main() {
	templates := populateTemplatesWithLayout()

	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		requestedFile := r.URL.Path[1:]
		t := templates[requestedFile+".htm"]

		if t != nil {
			t.Execute(w, nil)
		} else {
			w.WriteHeader(http.StatusNotFound)
		}
	})

	http.Handle("/img/", http.FileServer(http.Dir("web/public")))
	http.Handle("/css/", http.FileServer(http.Dir("web/public")))
	http.ListenAndServe(":1337", nil)
}

func populateTemplatesWithLayout() map[string]*template.Template {
	result := make(map[string]*template.Template)
	const basePath = "web/templates"
	layout := template.Must(template.ParseFiles(basePath + "/_layout.htm"))
	template.Must(layout.ParseFiles(basePath + "/_header.htm"))

	dir, _ := os.Open(basePath + "/content")

	fis, _ := dir.Readdir(-1)

	for _, fi := range fis {
		f, _ := os.Open(basePath + "/content/" + fi.Name())
		content, _ := ioutil.ReadAll(f)
		f.Close()

		tmpl := template.Must(layout.Clone())
		tmpl.Parse(string(content))
		result[fi.Name()] = tmpl
	}

	return result
}

Data Driven Templates

Instead of passing nil to t.Execute(w, nil), can pass a data structure, so its data can be bound within the template.

t := templates[requestedFile+".htm"]
data := viewmodel.NewBase() // struct
t.Execute(w, data)

To consume the data in the templates is easy:

{% raw %} {{.Title}} {{template “_header.htm” .}} {{template “content” .}} {% endraw %}

The .Title property on the struct is used to set the page title. Note how the data context can be propagated down to subtemplates, using the dot . notation.

Pipelines

The ability to chain multiple commands up within a template expression:

{% raw %}{{cmd1 cmd2 cmd3}}{% endraw %}

These could be:

  • literals,
  • functions {% raw %}{{template "content"}}{% endraw %}
  • data fields {% raw %}{{.Title}}{% endraw %}
  • methods {% raw %}{{.PrintMsg "sup world"}}{% endraw %}
  • arguments {% raw %}{{ cmd1 cmd2 | cmd3 }}{% endraw %}

For the method example above:

type Data struct {}
func (d Data) PrintMsg(m string) {
  return m;
}

Whitespace escaping use the - (hypen) char. This will escape whitespace before and after the curly tags:

{% raw %} const templateString = {{- "Item Information" }} Name: {{ .Name }} Price: {{ printf "$%.2f" .Price }} Price (inc GST): {{ .PriceWithTax | printf "$%.2f" }} {% endraw %}

Here can see some data fields in play, and how to employ templating functions such as printf. Refer to functions for more.

Custom Template Functions

Allows you to package up view related functionality for invocation straight from templates. A map of function names (string) to funcs is registered with the template via the Funcs call:

fm := template.FuncMap{}
fm["calctax"] = func(price float32) float32 {
    return price * (1 + tax)
}
t := template.Must(template.New("").Funcs(fm).Parse(templateString))
t.Execute(os.Stdout, p)

To use it:

{% raw %} const templateString = Name: {{ .Name }} Price (inc GST): {{ calctax .Price | printf "$%.2f" }} {% endraw %}