A quick tour of doing web with golang, all living off the land with Go’s built-in standard library.
Packages
Working example, where the 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 systemNotFoundHandler
returns 404RedirectHandler
StripPrefix
removes a specified prefix from URL and feeds the request to handlerTimeoutHandler
runs a handler with atime.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 %} {{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 %} {{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 mapmap[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 %}