Get Going with Web Servers in Go

By Aaron O. Ellis

Wednesday, November 13, 2013

Go, initially developed at Google, recently celebrated its fourth anniversary as an open source project. I have found Go to be unrivaled when building web services and see a bright future for it as micro service architecture gains wider appeal.

I wanted to share a few code snippets that I have found indispensable. I also have a boilerplate Go web server that shows them in action. Even more examples can be found on my GitHub project.

Hopefully this will help to get your first web server in Go going.

Execute a Template while Avoiding Globals

Are your files being polluted with globals? Attach variables and handlers to a struct:

type Website struct {
    attrs     map[string]interface{}
    homepage  *template.Template
}

func (web *Website) HomepageHandler(w http.ResponseWriter, r *http.Request) {
    web.homepage.Execute(w, web.attrs)
}

Execute Nested Templates

Getting nested templates to work in Go can be a frustrating experience. Instead of generating an error, many mistakes in templates will result in blank output. Here’s the boilerplate you’ll need to get started:

type Website struct {
    attrs     map[string]interface{}
    homepage  *template.Template
}

func (web *Website) HomepageHandler(w http.ResponseWriter, r *http.Request) {
    web.homepage.ExecuteTemplate(w, "base", web.attrs)
}

func Create(attrs map[string]interface{}) *Website {
    h := template.Must(template.New("home").ParseFiles("home.html", "base.html"))
    return &Website{attrs: attrs, homepage: h}
}

The file base.html contains:

{{ define "base" }}<!doctype html>
<html>
  <head>
    <meta charset="utf-8">
    <title>Nested Templates</title>
    {{ template "header" . }}
  </head>
  <body>
    {{ template "content" . }}
    <p>I am a paragraph from base.html</p>
    {{ template "script" . }}
  </body>
</html>{{ end }}

And the file home.html:

{{ define "header" }}{{ end }}

{{ define "content" }}
<p>I am a paragraph from home.html</p>
{{ end }}

{{ define "script" }}{{ end }}

Nested template files must be parsed together in the same template.Template and their define template actions (what’s between the double curly braces) must be coordinated. And don’t forget to call the top-level template (in this example it’s base) during execution.

Write a Status Code

Status codes must be written before any content. Once content is written, http.StatusOK will be set automatically by the ResponseWriter. To specify your own status code (these will do the same thing):

w.WriteHeader(http.StatusCreated)
w.WriteHeader(201)

There is also a built-in 404 response:

http.NotFound(w, r)

Write a Content-Type Header

This order of operations is also true for the content type of the response. Set it before any content is written or the type will be determined by the function DetectContentType:

w.Header().Set("Content-Type", "application/json")

Serve a Static Directory

To serve an entire directory (and its sub-directories) as static files:

handler := http.StripPrefix("/static/", http.FileServer(http.Dir("./static")))
http.Handle("/static/", handler)

Serve a Single File

If you need to serve a static file, but don’t need a full directory:

func IconHandler(w http.ResponseWriter, r *http.Request) {
    http.ServeFile(w, r, "favicon.ico")
}

Command Line Options

Instead of hard-coding your server port, use Go’s flag package to pass it on the command line:

port := flag.Int("port", 8000, "Server Port")
flag.Parse()
err := http.ListenAndServe(fmt.Sprintf(":%d", *port), nil)

The port can then be specified with:

./website --port=80

Log to a File

By default, Go’s log package will write to stdout. The following will append the log output to site.log, creating the file if necessary:

f, err := os.OpenFile("site.log", os.O_APPEND | os.O_WRONLY | os.O_CREATE, 0644)
if err != nil {
    panic(err)
}
defer f.Close()
log.SetOutput(f)

Deployment by Forking

Go’s net/http package contains a robust, production-ready server. While it might be a good idea to place the binary behind a more familiar server like nginx, or at least daemonize the server process, you can deploy using:

./website &