You're reading a sample of this book. Grab the full version here.

2.9. Serving Static Files

Now let's improve the look and feel of the home page by adding some static CSS and image files to our project, along with a tiny bit of JavaScript to highlight the active navigation item.

If you're following along, you can grab the necessary files and extract them into the ui/static folder that we made earlier with the following commands:

$ cd $HOME/code/snippetbox
$ curl https://www.alexedwards.net/static/sb.v120.tar.gz | tar -xvz -C ./ui/static/

The contents of your ui/static directory should now look like this:

02.09-01.png

The http.FileServer Handler

Go's net/http package ships with a built-in http.FileServer handler which you can use to serve files over HTTP from a specific directory. Let’s add a new route to our application so that all requests which begin with "/static/" are handled using this, like so:

Method Pattern Handler Action
ANY / home Display the home page
ANY /snippet?id=1 showSnippet Display a specific snippet
POST /snippet/create createSnippet Create a new snippet
ANY /static/ http.FileServer Serve a specific static file

Remember: The pattern "/static/" is a subtree path pattern, so it acts a bit like there is a wildcard at the end.

To create a new http.FileServer handler, we need to use the http.FileServer() function like this:

fileServer := http.FileServer(http.Dir("./ui/static"))

When this handler receives a request, it will remove the leading slash from the URL path and then search the ./ui/static directory for the corresponding file to send to the user.

So, for this to work correctly, we must strip the leading "/static" from the URL path before passing it to http.FileServer. Otherwise it will be looking for a file which doesn't exist and the user will receive a 404 page not found response. Fortunately Go includes a http.StripPrefix() helper specifically for this task.

Open your main.go file and add the following code, so that the file ends up looking like this:

File: cmd/web/main.go
package main

import (
    "log"
    "net/http"
)

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/", home)
    mux.HandleFunc("/snippet", showSnippet)
    mux.HandleFunc("/snippet/create", createSnippet)

    // Create a file server which serves files out of the "./ui/static" directory.
    // Note that the path given to the http.Dir function is relative to the project
    // directory root.
    fileServer := http.FileServer(http.Dir("./ui/static/"))

    // Use the mux.Handle() function to register the file server as the handler for
    // all URL paths that start with "/static/". For matching paths, we strip the
    // "/static" prefix before the request reaches the file server.
    mux.Handle("/static/", http.StripPrefix("/static", fileServer))

    log.Println("Starting server on :4000")
    err := http.ListenAndServe(":4000", mux)
    log.Fatal(err)
}

Once that's complete, restart the application and open http://localhost:4000/static/ in your browser. You should see a navigable directory listing of the ui/static folder which looks like this:

02.09-02.png

Feel free to have a play around and browse through the directory listing to view individual files. For example, if you navigate to http://localhost:4000/static/css/main.css you should see the CSS file appear in your browser like so:

02.09-03.png

Using the Static Files

With the file server working properly, we can now update the ui/html/base.layout.tmpl file to make use of the static files:

File: ui/html/base.layout.tmpl
{{define "base"}}
<!doctype html>
<html lang='en'>
    <head>
        <meta charset='utf-8'>
        <title>{{template "title" .}} - Snippetbox</title>
        <!-- Link to the CSS stylesheet and favicon -->
        <link rel='stylesheet' href='/static/css/main.css'>
        <link rel='shortcut icon' href='/static/img/favicon.ico' type='image/x-icon'>
        <!-- Also link to some fonts hosted by Google -->
        <link rel='stylesheet' href='https://fonts.googleapis.com/css?family=Ubuntu+Mono:400,700'>
    </head>
    <body>
        <header>
            <h1><a href='/'>Snippetbox</a></h1>
        </header>
        <nav>
            <a href='/'>Home</a>
        </nav>
        <section>
            {{template "body" .}}
        </section>
        {{template "footer" .}}
        <!-- And include the JavaScript file -->
        <script src="/static/js/main.js" type="text/javascript"></script>
    </body>
</html>
{{end}}

Make sure you save the changes, then visit http://localhost:4000/. Your home page should now look like this:

02.09-04.png


Additional Information

Features and Functions

Go's file server has a few really nice features that are worth mentioning:

Serving Single Files

Sometimes you might want to serve a single file from within a handler. For this there's the http.ServeFile() function, which you can use like so:

func downloadHandler(w http.ResponseWriter, r *http.Request) {
    http.ServeFile(w, r, "./ui/static/file.zip")
}

But be aware: http.ServeFile() does not automatically sanitize the file path. If you're constructing a file path from untrusted user input, to avoid directory traversal attacks you must sanitize the input with filepath.Clean() before using it.